|
36 | 36 | import org.basex.query.util.hash.*; |
37 | 37 | import org.basex.query.util.list.*; |
38 | 38 | import org.basex.query.util.parse.*; |
| 39 | +import org.basex.query.util.rex.*; |
39 | 40 | import org.basex.query.value.array.*; |
40 | 41 | import org.basex.query.value.item.*; |
41 | 42 | import org.basex.query.value.seq.*; |
@@ -110,6 +111,8 @@ public class QueryParser extends InputParser { |
110 | 111 | private QueryError alter; |
111 | 112 | /** Alternative position. */ |
112 | 113 | private int alterPos; |
| 114 | + /** Attribute value scanner. */ |
| 115 | + private AttributeValueScanner attributeValueScanner; |
113 | 116 |
|
114 | 117 | /** |
115 | 118 | * Constructor. |
@@ -3098,102 +3101,121 @@ private Expr dirElement(final boolean root) throws QueryException { |
3098 | 3101 | // parse attributes |
3099 | 3102 | boolean xmlDecl = false; // xml prefix explicitly declared? |
3100 | 3103 | ArrayList<QNm> atts = null; |
3101 | | - while(true) { |
3102 | | - final byte[] atn = qName(null); |
3103 | | - if(atn.length == 0) break; |
3104 | | - |
3105 | | - final ExprList attv = new ExprList(); |
3106 | | - consumeWS(); |
3107 | | - if(root) { |
3108 | | - if(!consume('=')) return null; |
3109 | | - } else { |
3110 | | - check('='); |
3111 | | - } |
3112 | | - consumeWS(); |
3113 | | - final int delim = consume(); |
3114 | | - if(!quote(delim)) throw error(NOQUOTE_X, found()); |
3115 | | - final TokenBuilder tb = new TokenBuilder(); |
3116 | 3104 |
|
3117 | | - boolean simple = true; |
| 3105 | + // remember start of attribute list |
| 3106 | + final int attrPos = pos; |
| 3107 | + for(int pass = 0; pass < 2; pass++) { |
| 3108 | + final boolean processXmlns = pass == 0; |
| 3109 | + pos = attrPos; |
3118 | 3110 | while(true) { |
3119 | | - while(!consume(delim)) { |
3120 | | - cp = current(); |
3121 | | - switch(cp) { |
3122 | | - case '{': |
3123 | | - if(next() == '{') { |
3124 | | - tb.add(consume()); |
3125 | | - consume(); |
3126 | | - } else { |
3127 | | - final byte[] text = tb.next(); |
3128 | | - if(text.length == 0) { |
3129 | | - add(attv, enclosedExpr()); |
3130 | | - simple = false; |
| 3111 | + final byte[] atn = qName(null); |
| 3112 | + if(atn.length == 0) break; |
| 3113 | + consumeWS(); |
| 3114 | + if(root) { |
| 3115 | + if(!consume('=')) return null; |
| 3116 | + } else { |
| 3117 | + check('='); |
| 3118 | + } |
| 3119 | + consumeWS(); |
| 3120 | + |
| 3121 | + final int len = attributeValueScanner().length(pos); |
| 3122 | + final boolean hasXmlnsPrefix = startsWith(atn, XMLNS_COLON); |
| 3123 | + final boolean isXmlns = hasXmlnsPrefix || eq(atn, XMLNS); |
| 3124 | + if(processXmlns != isXmlns) { |
| 3125 | + if(len >= 0) { |
| 3126 | + pos += len; |
| 3127 | + consumeWS(); |
| 3128 | + continue; |
| 3129 | + } |
| 3130 | + Util.debugln("Failed to detect attribute value length: " |
| 3131 | + + new String(input, pos, Math.min(20, input.length - pos)) + "..."); |
| 3132 | + } |
| 3133 | + |
| 3134 | + final ExprList attv = new ExprList(); |
| 3135 | + final int delim = consume(); |
| 3136 | + if(!quote(delim)) throw error(NOQUOTE_X, found()); |
| 3137 | + final TokenBuilder tb = new TokenBuilder(); |
| 3138 | + |
| 3139 | + boolean simple = true; |
| 3140 | + while(true) { |
| 3141 | + while(!consume(delim)) { |
| 3142 | + cp = current(); |
| 3143 | + switch(cp) { |
| 3144 | + case '{': |
| 3145 | + if(next() == '{') { |
| 3146 | + tb.add(consume()); |
| 3147 | + consume(); |
3131 | 3148 | } else { |
3132 | | - add(attv, Str.get(text)); |
| 3149 | + final byte[] text = tb.next(); |
| 3150 | + if(text.length == 0) { |
| 3151 | + add(attv, enclosedExpr()); |
| 3152 | + simple = false; |
| 3153 | + } else { |
| 3154 | + add(attv, Str.get(text)); |
| 3155 | + } |
3133 | 3156 | } |
3134 | | - } |
3135 | | - break; |
3136 | | - case '}': |
3137 | | - consume(); |
3138 | | - check('}'); |
3139 | | - tb.add('}'); |
3140 | | - break; |
3141 | | - case '<': |
3142 | | - case 0: |
3143 | | - throw error(NOQUOTE_X, found()); |
3144 | | - case '\n': |
3145 | | - case '\t': |
3146 | | - tb.add(' '); |
3147 | | - consume(); |
3148 | | - break; |
3149 | | - case '\r': |
3150 | | - if(next() != '\n') tb.add(' '); |
3151 | | - consume(); |
3152 | | - break; |
3153 | | - default: |
3154 | | - entity(tb); |
3155 | | - break; |
| 3157 | + break; |
| 3158 | + case '}': |
| 3159 | + consume(); |
| 3160 | + check('}'); |
| 3161 | + tb.add('}'); |
| 3162 | + break; |
| 3163 | + case '<': |
| 3164 | + case 0: |
| 3165 | + throw error(NOQUOTE_X, found()); |
| 3166 | + case '\n': |
| 3167 | + case '\t': |
| 3168 | + tb.add(' '); |
| 3169 | + consume(); |
| 3170 | + break; |
| 3171 | + case '\r': |
| 3172 | + if(next() != '\n') tb.add(' '); |
| 3173 | + consume(); |
| 3174 | + break; |
| 3175 | + default: |
| 3176 | + entity(tb); |
| 3177 | + break; |
| 3178 | + } |
3156 | 3179 | } |
| 3180 | + if(!consume(delim)) break; |
| 3181 | + tb.add(delim); |
3157 | 3182 | } |
3158 | | - if(!consume(delim)) break; |
3159 | | - tb.add(delim); |
3160 | | - } |
3161 | 3183 |
|
3162 | | - if(!tb.isEmpty()) add(attv, Str.get(tb.finish())); |
3163 | | - |
3164 | | - // parse namespace declarations |
3165 | | - final boolean pr = startsWith(atn, XMLNS_COLON); |
3166 | | - if(pr || eq(atn, XMLNS)) { |
3167 | | - if(!simple) throw error(NSCONS); |
3168 | | - final byte[] prefix = pr ? local(atn) : EMPTY; |
3169 | | - final byte[] uri = attv.isEmpty() ? EMPTY : ((Str) attv.get(0)).string(); |
3170 | | - if(eq(prefix, XML) && eq(uri, XML_URI)) { |
3171 | | - if(xmlDecl) throw error(DUPLNSDEF_X, XML); |
3172 | | - xmlDecl = true; |
3173 | | - } else { |
3174 | | - if(!Uri.get(uri).isValid()) throw error(INVURI_X, uri); |
3175 | | - if(pr) { |
3176 | | - if(uri.length == 0) throw error(NSEMPTYURI); |
3177 | | - if(eq(prefix, XML, XMLNS)) throw error(BINDXML_X, prefix); |
3178 | | - if(eq(uri, XML_URI)) throw error(BINDXMLURI_X_X, uri, XML); |
3179 | | - if(eq(uri, XMLNS_URI)) throw error(BINDXMLURI_X_X, uri, XMLNS); |
3180 | | - qc.ns.add(prefix, uri); |
| 3184 | + if(!tb.isEmpty()) add(attv, Str.get(tb.finish())); |
| 3185 | + |
| 3186 | + // parse namespace declarations |
| 3187 | + if(isXmlns) { |
| 3188 | + if(!simple) throw error(NSCONS); |
| 3189 | + final byte[] prefix = hasXmlnsPrefix ? local(atn) : EMPTY; |
| 3190 | + final byte[] uri = attv.isEmpty() ? EMPTY : ((Str) attv.get(0)).string(); |
| 3191 | + if(eq(prefix, XML) && eq(uri, XML_URI)) { |
| 3192 | + if(xmlDecl) throw error(DUPLNSDEF_X, XML); |
| 3193 | + xmlDecl = true; |
3181 | 3194 | } else { |
3182 | | - if(eq(uri, XML_URI)) throw error(XMLNSDEF_X, uri); |
3183 | | - sc.dirNS = uri; |
3184 | | - if(!sc.elemNsFixed) sc.elemNS = sc.dirNS; |
| 3195 | + if(!Uri.get(uri).isValid()) throw error(INVURI_X, uri); |
| 3196 | + if(hasXmlnsPrefix) { |
| 3197 | + if(uri.length == 0) throw error(NSEMPTYURI); |
| 3198 | + if(eq(prefix, XML, XMLNS)) throw error(BINDXML_X, prefix); |
| 3199 | + if(eq(uri, XML_URI)) throw error(BINDXMLURI_X_X, uri, XML); |
| 3200 | + if(eq(uri, XMLNS_URI)) throw error(BINDXMLURI_X_X, uri, XMLNS); |
| 3201 | + qc.ns.add(prefix, uri); |
| 3202 | + } else { |
| 3203 | + if(eq(uri, XML_URI)) throw error(XMLNSDEF_X, uri); |
| 3204 | + sc.dirNS = uri; |
| 3205 | + if(!sc.elemNsFixed) sc.elemNS = sc.dirNS; |
| 3206 | + } |
| 3207 | + if(ns.contains(prefix)) throw error(DUPLNSDEF_X, prefix); |
| 3208 | + ns.add(prefix, uri); |
3185 | 3209 | } |
3186 | | - if(ns.contains(prefix)) throw error(DUPLNSDEF_X, prefix); |
3187 | | - ns.add(prefix, uri); |
| 3210 | + } else { |
| 3211 | + final QNm attn = new QNm(atn); |
| 3212 | + if(atts == null) atts = new ArrayList<>(1); |
| 3213 | + atts.add(attn); |
| 3214 | + qnames.add(attn, false, info()); |
| 3215 | + add(cont, new CAttr(info(), false, attn, attv.finish())); |
3188 | 3216 | } |
3189 | | - } else { |
3190 | | - final QNm attn = new QNm(atn); |
3191 | | - if(atts == null) atts = new ArrayList<>(1); |
3192 | | - atts.add(attn); |
3193 | | - qnames.add(attn, false, info()); |
3194 | | - add(cont, new CAttr(info(), false, attn, attv.finish())); |
| 3217 | + if(!consumeWS()) break; |
3195 | 3218 | } |
3196 | | - if(!consumeWS()) break; |
3197 | 3219 | } |
3198 | 3220 |
|
3199 | 3221 | if(consume('/')) { |
@@ -3230,6 +3252,15 @@ private Expr dirElement(final boolean root) throws QueryException { |
3230 | 3252 | return new CElem(info(), false, name, ns, cont.finish()); |
3231 | 3253 | } |
3232 | 3254 |
|
| 3255 | + /** |
| 3256 | + * Returns the attribute value scanner. |
| 3257 | + * @return attribute value scanner |
| 3258 | + */ |
| 3259 | + private AttributeValueScanner attributeValueScanner() { |
| 3260 | + if(attributeValueScanner == null) attributeValueScanner = new AttributeValueScanner(input); |
| 3261 | + return attributeValueScanner; |
| 3262 | + } |
| 3263 | + |
3233 | 3264 | /** |
3234 | 3265 | * Parses the "DirElemContent" rule. |
3235 | 3266 | * @param name name of opening element |
|
0 commit comments