Skip to content

Commit 70407b2

Browse files
Add support for fixed default element namespace (#2618)
* add support for `fixed default element namespace` * refactoring of `QNmCache` and `QNmCheck` into `QNmResolver`
1 parent 70466e5 commit 70407b2

7 files changed

Lines changed: 153 additions & 124 deletions

File tree

basex-core/src/main/java/org/basex/query/QueryParser.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public class QueryParser extends InputParser {
9595
/** Output declarations. */
9696
private final HashMap<String, Object> sparams = new HashMap<>();
9797
/** QName cache. */
98-
private final QNmCache qnames = new QNmCache();
98+
private final QNmResolver qnames = new QNmResolver();
9999
/** Local variable. */
100100
private final LocalVars localVars = new LocalVars(this);
101101

@@ -261,7 +261,7 @@ private void finish(final MainModule mm) throws QueryException {
261261
}
262262

263263
// completes the parsing step
264-
qnames.assignURI(this, 0);
264+
qnames.resolve(this, 0, sc.elemNS);
265265
if(sc.elemNS != null) sc.ns.add(EMPTY, sc.elemNS, null);
266266
RecordType.resolveRefs(recordTypeRefs, namedRecordTypes);
267267
}
@@ -329,8 +329,10 @@ private void prolog1() throws QueryException {
329329
while(true) {
330330
final int p = pos;
331331
if(wsConsumeWs(DECLARE)) {
332-
if(wsConsumeWs(DEFAULT)) {
333-
if(!defaultNamespaceDecl() && !defaultCollationDecl() && !emptyOrderDecl() &&
332+
if(wsConsumeWs(FIXED)) {
333+
if(!wsConsumeWs(DEFAULT) || !defaultNamespaceDecl(true)) throw error(DECLINCOMPLETE);
334+
} else if(wsConsumeWs(DEFAULT)) {
335+
if(!defaultNamespaceDecl(false) && !defaultCollationDecl() && !emptyOrderDecl() &&
334336
!decimalFormatDecl(true)) throw error(DECLINCOMPLETE);
335337
} else if(wsConsumeWs(BOUNDARY_SPACE)) {
336338
boundarySpaceDecl();
@@ -529,10 +531,11 @@ private void boundarySpaceDecl() throws QueryException {
529531

530532
/**
531533
* Parses the "DefaultNamespaceDecl" rule.
534+
* @param fixed fixed default namespace flag
532535
* @return true if declaration was found
533536
* @throws QueryException query exception
534537
*/
535-
private boolean defaultNamespaceDecl() throws QueryException {
538+
private boolean defaultNamespaceDecl(final boolean fixed) throws QueryException {
536539
final boolean elem = wsConsumeWs(ELEMENT);
537540
if(!elem && !wsConsumeWs(FUNCTION)) return false;
538541
wsCheck(NAMESPACE);
@@ -542,7 +545,9 @@ private boolean defaultNamespaceDecl() throws QueryException {
542545

543546
if(elem) {
544547
if(!decl.add(ELEMENT)) throw error(DUPLNS);
548+
sc.elemNsFixed = fixed;
545549
sc.elemNS = uri.length == 0 ? null : uri;
550+
sc.dirNS = sc.elemNS;
546551
} else {
547552
if(!decl.add(FUNCTION)) throw error(DUPLNS);
548553
sc.funcNS = uri;
@@ -682,9 +687,13 @@ private void schemaImport() throws QueryException {
682687
prefix = ncName(NONAME_X, false);
683688
if(eq(prefix, XML, XMLNS)) throw error(BINDXML_X, prefix);
684689
wsCheck("=");
685-
} else if(wsConsumeWs(DEFAULT)) {
686-
wsCheck(ELEMENT);
687-
wsCheck(NAMESPACE);
690+
} else {
691+
final boolean fixed = wsConsumeWs(FIXED);
692+
if(fixed || wsConsumeWs(DEFAULT)) {
693+
if(fixed) wsCheck(DEFAULT);
694+
wsCheck(ELEMENT);
695+
wsCheck(NAMESPACE);
696+
}
688697
}
689698
final byte[] uri = uriLiteral();
690699
if(prefix != null && uri.length == 0) throw error(NSEMPTY);
@@ -2080,7 +2089,7 @@ private void validate() throws QueryException {
20802089

20812090
if(consume(TYPE)) {
20822091
final InputInfo ii = info();
2083-
qnames.add(eQName(SKIPCHECK, QNAME_X), ii);
2092+
qnames.add(eQName(SKIPCHECK, QNAME_X), sc, ii);
20842093
}
20852094
consume(STRICT);
20862095
consume(LAX);
@@ -2462,7 +2471,7 @@ private ExprInfo simpleNodeTest(final Kind kind, final boolean all) throws Query
24622471
}
24632472
}
24642473
// name test: prefix:name, name, Q{uri}name
2465-
qnames.add(name, kind == Kind.ELEMENT, ii);
2474+
if(kind == Kind.ELEMENT) qnames.add(name, sc, ii); else qnames.add(name, false, ii);
24662475
return nameTest.apply(name, scope);
24672476
}
24682477
}
@@ -3077,10 +3086,11 @@ private Expr dirElement(final boolean root) throws QueryException {
30773086
// cache namespace information
30783087
final int size = sc.ns.size();
30793088
final byte[] nse = sc.elemNS;
3089+
final byte[] nsd = sc.dirNS;
30803090
final int npos = qnames.size();
30813091

30823092
final QNm name = new QNm(qnm);
3083-
qnames.add(name, ii);
3093+
qnames.add(name, true, ii);
30843094

30853095
final Atts ns = new Atts();
30863096
final ExprList cont = new ExprList();
@@ -3170,7 +3180,8 @@ private Expr dirElement(final boolean root) throws QueryException {
31703180
sc.ns.add(prefix, uri);
31713181
} else {
31723182
if(eq(uri, XML_URI)) throw error(XMLNSDEF_X, uri);
3173-
sc.elemNS = uri;
3183+
sc.dirNS = uri;
3184+
if(!sc.elemNsFixed) sc.elemNS = sc.dirNS;
31743185
}
31753186
if(ns.contains(prefix)) throw error(DUPLNSDEF_X, prefix);
31763187
ns.add(prefix, uri);
@@ -3201,7 +3212,7 @@ private Expr dirElement(final boolean root) throws QueryException {
32013212
if(!eq(name.string(), close)) throw error(TAGWRONG_X_X, name.string(), close);
32023213
}
32033214

3204-
qnames.assignURI(this, npos);
3215+
qnames.resolve(this, npos, sc.dirNS);
32053216

32063217
// check for duplicate attribute names
32073218
if(atts != null) {
@@ -3215,6 +3226,7 @@ private Expr dirElement(final boolean root) throws QueryException {
32153226

32163227
sc.ns.size(size);
32173228
sc.elemNS = nse;
3229+
sc.dirNS = nsd;
32183230
return new CElem(info(), false, name, ns, cont.finish());
32193231
}
32203232

@@ -3356,7 +3368,7 @@ private Expr compConstructor() throws QueryException {
33563368
private Expr compElement() throws QueryException {
33573369
final Expr name = compName(NOELEMNAME, true);
33583370
if(name == null) return null;
3359-
if(name instanceof final QNm qnm) qnames.add(qnm, info());
3371+
if(name instanceof final QNm qnm) qnames.add(qnm, sc, info());
33603372
skipWs();
33613373
return current('{') ? new CElem(info(), true, name, new Atts(), enclosedExpr()) : null;
33623374
}

basex-core/src/main/java/org/basex/query/QueryText.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public interface QueryText {
6969
/** Parser token. */ String FALSE = "false";
7070
/** Parser token. */ String FINALLY = "finally";
7171
/** Parser token. */ String FIRST = "first";
72+
/** Parser token. */ String FIXED = "fixed";
7273
/** Parser token. */ String FN = "fn";
7374
/** Parser token. */ String FOR = "for";
7475
/** Parser token. */ String FROM = "from";

basex-core/src/main/java/org/basex/query/StaticContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ public final class StaticContext {
3636

3737
/** Default collation (default collection ({@link QueryText#COLLATION_URI}): {@code null}). */
3838
public Collation collation;
39+
/** Default element/type namespace fixed flag. */
40+
public boolean elemNsFixed;
3941
/** Default element/type namespace. */
4042
public byte[] elemNS;
43+
/** Direct element constructor default element/type namespace (differs from above if "fixed"). */
44+
public byte[] dirNS;
4145
/** Default function namespace. */
4246
public byte[] funcNS;
4347
/** Name of module (not assigned for main module). */
@@ -90,6 +94,7 @@ public StaticContext(final QueryContext qc) {
9094
void namespace(final String prefix, final String uri) throws QueryException {
9195
if(prefix.isEmpty()) {
9296
elemNS = uri.isEmpty() ? null : token(uri);
97+
dirNS = elemNS;
9398
} else if(uri.isEmpty()) {
9499
ns.delete(token(prefix));
95100
} else {

basex-core/src/main/java/org/basex/query/util/parse/QNmCache.java

Lines changed: 0 additions & 57 deletions
This file was deleted.

basex-core/src/main/java/org/basex/query/util/parse/QNmCheck.java

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package org.basex.query.util.parse;
2+
3+
import static org.basex.query.QueryError.*;
4+
5+
import java.util.*;
6+
7+
import org.basex.query.*;
8+
import org.basex.query.value.item.*;
9+
import org.basex.util.*;
10+
11+
/**
12+
* Resolves namespace URIs of QNames whose resolution must be deferred until the surrounding
13+
* namespace context is known.
14+
*
15+
* @author BaseX Team, BSD License
16+
* @author Christian Gruen
17+
*/
18+
public final class QNmResolver {
19+
/**
20+
* Entry for a QName whose namespace URI still needs to be resolved.
21+
* @param name QName to be resolved
22+
* @param nsElem flag for assigning default element namespace
23+
* @param info input info (can be {@code null})
24+
*/
25+
private record Entry(QNm name, boolean nsElem, InputInfo info) { }
26+
/** QNames to be resolved. */
27+
private final ArrayList<Entry> entries = new ArrayList<>();
28+
29+
/**
30+
* Adds a QName unless it already has a namespace URI or it can be immediately assigned the fixed
31+
* default namespace URI. This method must not be called for the element names of direct element
32+
* constructors.
33+
* @param name QName
34+
* @param sc static context
35+
* @param info input info (can be {@code null})
36+
*/
37+
public void add(final QNm name, final StaticContext sc, final InputInfo info) {
38+
if(sc.elemNsFixed && !name.hasPrefix() && !name.hasURI()) name.uri(sc.elemNS);
39+
else add(name, true, info);
40+
}
41+
42+
/**
43+
* Adds a QName unless it already has a namespace URI.
44+
* @param name qname
45+
* @param nsElem default check
46+
* @param info input info (can be {@code null})
47+
*/
48+
public void add(final QNm name, final boolean nsElem, final InputInfo info) {
49+
if(!name.hasURI()) entries.add(new Entry(name, nsElem, info));
50+
}
51+
52+
/**
53+
* Finalizes the QNames by assigning namespace URIs.
54+
* @param qp query parser
55+
* @param npos first entry to be checked
56+
* @param elemNS default element namespace
57+
* @throws QueryException query exception
58+
*/
59+
public void resolve(final QueryParser qp, final int npos, final byte[] elemNS)
60+
throws QueryException {
61+
for(int i = entries.size() - 1; i >= npos; --i) {
62+
final Entry entry = entries.get(i);
63+
if(entry.name.hasPrefix()) {
64+
entry.name.uri(qp.sc.ns.uri(entry.name.prefix()));
65+
if(npos == 0 && !entry.name.hasURI())
66+
throw qp.error(NOURI_X, entry.info, entry.name.prefix());
67+
} else if(entry.nsElem) {
68+
entry.name.uri(elemNS);
69+
}
70+
if(entry.name.hasURI()) entries.remove(i);
71+
}
72+
}
73+
74+
/**
75+
* Returns the number of remaining QNames.
76+
* @return number
77+
*/
78+
public int size() {
79+
return entries.size();
80+
}
81+
}

0 commit comments

Comments
 (0)