package org.basex.query;
import static org.basex.query.QueryText.*;
import static org.basex.query.util.Err.*;
import static org.basex.util.Token.*;
import static org.basex.util.ft.FTFlag.*;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.basex.core.Prop;
import org.basex.core.User;
import org.basex.io.IO;
import org.basex.io.IOFile;
import org.basex.io.serial.SerializerProp;
import org.basex.query.expr.And;
import org.basex.query.expr.Arith;
import org.basex.query.expr.CAttr;
import org.basex.query.expr.CComm;
import org.basex.query.expr.CDoc;
import org.basex.query.expr.CElem;
import org.basex.query.expr.CNSpace;
import org.basex.query.expr.CPI;
import org.basex.query.expr.CTxt;
import org.basex.query.expr.Calc;
import org.basex.query.expr.Cast;
import org.basex.query.expr.Castable;
import org.basex.query.expr.Catch;
import org.basex.query.expr.CmpG;
import org.basex.query.expr.CmpN;
import org.basex.query.expr.CmpV;
import org.basex.query.expr.Concat;
import org.basex.query.expr.Context;
import org.basex.query.expr.Except;
import org.basex.query.expr.Expr;
import org.basex.query.expr.Extension;
import org.basex.query.expr.Filter;
import org.basex.query.expr.If;
import org.basex.query.expr.Instance;
import org.basex.query.expr.InterSect;
import org.basex.query.expr.List;
import org.basex.query.expr.LitMap;
import org.basex.query.expr.Or;
import org.basex.query.expr.Pragma;
import org.basex.query.expr.Quantifier;
import org.basex.query.expr.Range;
import org.basex.query.expr.Root;
import org.basex.query.expr.Switch;
import org.basex.query.expr.SwitchCase;
import org.basex.query.expr.Treat;
import org.basex.query.expr.Try;
import org.basex.query.expr.TypeCase;
import org.basex.query.expr.TypeSwitch;
import org.basex.query.expr.Unary;
import org.basex.query.expr.Union;
import org.basex.query.expr.VarRef;
import org.basex.query.flwor.For;
import org.basex.query.flwor.ForLet;
import org.basex.query.flwor.GFLWOR;
import org.basex.query.flwor.Let;
import org.basex.query.flwor.OrderBy;
import org.basex.query.flwor.OrderByExpr;
import org.basex.query.flwor.OrderByStable;
import org.basex.query.ft.FTAnd;
import org.basex.query.ft.FTContains;
import org.basex.query.ft.FTContent;
import org.basex.query.ft.FTDistance;
import org.basex.query.ft.FTExpr;
import org.basex.query.ft.FTExtensionSelection;
import org.basex.query.ft.FTMildNot;
import org.basex.query.ft.FTNot;
import org.basex.query.ft.FTOptions;
import org.basex.query.ft.FTOr;
import org.basex.query.ft.FTOrder;
import org.basex.query.ft.FTScope;
import org.basex.query.ft.FTWeight;
import org.basex.query.ft.FTWindow;
import org.basex.query.ft.FTWords;
import org.basex.query.ft.FTWords.FTMode;
import org.basex.query.ft.ThesQuery;
import org.basex.query.ft.Thesaurus;
import org.basex.query.func.*;
import org.basex.query.item.Atm;
import org.basex.query.item.AtomType;
import org.basex.query.item.Dbl;
import org.basex.query.item.Dec;
import org.basex.query.item.Empty;
import org.basex.query.item.FuncType;
import org.basex.query.item.Int;
import org.basex.query.item.MapType;
import org.basex.query.item.NodeType;
import org.basex.query.item.QNm;
import org.basex.query.item.SeqType;
import org.basex.query.item.SeqType.Occ;
import org.basex.query.item.Str;
import org.basex.query.item.Type;
import org.basex.query.item.Types;
import org.basex.query.item.Uri;
import org.basex.query.item.Value;
import org.basex.query.item.map.Map;
import org.basex.query.path.Axis;
import org.basex.query.path.AxisStep;
import org.basex.query.path.KindTest;
import org.basex.query.path.NameTest;
import org.basex.query.path.Path;
import org.basex.query.path.Test;
import org.basex.query.up.expr.Delete;
import org.basex.query.up.expr.Insert;
import org.basex.query.up.expr.Rename;
import org.basex.query.up.expr.Replace;
import org.basex.query.up.expr.Transform;
import org.basex.query.util.Err;
import org.basex.query.util.TypedFunc;
import org.basex.query.util.Var;
import org.basex.query.util.VarStack;
import org.basex.query.util.format.DecFormatter;
import org.basex.query.util.pkg.JarDesc;
import org.basex.query.util.pkg.JarParser;
import org.basex.query.util.pkg.Package;
import org.basex.query.util.pkg.Package.Component;
import org.basex.query.util.pkg.Package.Dependency;
import org.basex.query.util.pkg.PkgParser;
import org.basex.query.util.pkg.PkgText;
import org.basex.query.util.pkg.PkgValidator;
import org.basex.util.Array;
import org.basex.util.Atts;
import org.basex.util.InputInfo;
import org.basex.util.InputParser;
import org.basex.util.JarLoader;
import org.basex.util.Reflect;
import org.basex.util.TokenBuilder;
import org.basex.util.Util;
import org.basex.util.XMLToken;
import org.basex.util.ft.FTOpt;
import org.basex.util.ft.FTUnit;
import org.basex.util.ft.Language;
import org.basex.util.ft.Stemmer;
import org.basex.util.ft.StopWords;
import org.basex.util.ft.Tokenizer;
import org.basex.util.hash.TokenSet;
import org.basex.util.list.ObjList;
import org.basex.util.list.StringList;
import org.basex.util.list.TokenList;
/**
* Parser for XQuery expressions.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public class QueryParser extends InputParser {
/** QName check: URI is mandatory. */
private static final byte[] URICHECK = {};
/** QName check: skip namespace check. */
private static final byte[] SKIPCHECK = {};
/** Reserved function names (sorted). */
private static final byte[][] KEYWORDS = {
NodeType.ATT.string(), NodeType.COM.string(), NodeType.DOC.string(),
NodeType.ELM.string(), AtomType.EMP.string(), FuncType.ANY_FUN.string(),
token(IF), AtomType.ITEM.string(),
NodeType.NSP.string(), NodeType.NOD.string(), NodeType.PI.string(),
token(SCHEMAATTRIBUTE), token(SCHEMAELEMENT), token(SWITCH),
NodeType.TXT.string(), token(TYPESWITCH)
};
/** Query context. */
final QueryContext ctx;
/** Temporary token builder. */
private final TokenBuilder tok = new TokenBuilder();
/** Modules loaded by the current file. */
private final TokenList modules = new TokenList();
/** Name of current module. */
private QNm module;
/** Alternative error output. */
private Err alter;
/** Alternative error description. */
private QNm alterFunc;
/** Alternative position. */
private int ap;
/** Declared serialization options. */
private final StringList serial = new StringList();
/** Declaration flag. */
private boolean declElem;
/** Declaration flag. */
private boolean declFunc;
/** Declaration flag. */
private boolean declColl;
/** Declaration flag. */
private boolean declConstr;
/** Declaration flag. */
private boolean declSpaces;
/** Declaration flag. */
private boolean declOrder;
/** Declaration flag. */
private boolean declReval;
/** Declaration flag. */
private boolean declGreat;
/** Declaration flag. */
private boolean declPres;
/** Declaration flag. */
private boolean declBase;
/** Declaration flag. */
private boolean declItem;
/** Declaration flag. */
private boolean declVars;
/** Cached QNames. */
private final ObjList<QNmCheck> names = new ObjList<QNmCheck>();
/**
* Constructor.
* @param q query
* @param c query context
* @throws QueryException query exception
*/
public QueryParser(final String q, final QueryContext c)
throws QueryException {
super(q, c.sc.baseIO());
ctx = c;
// parse pre-defined external variables
final String bind = ctx.context.prop.get(Prop.BINDINGS).trim();
final StringBuilder key = new StringBuilder();
final StringBuilder val = new StringBuilder();
boolean first = true;
final int sl = bind.length();
for(int s = 0; s < sl; s++) {
final char ch = bind.charAt(s);
if(first) {
if(ch == '=') {
first = false;
} else {
key.append(ch);
}
} else {
if(ch == ',') {
if(s + 1 == sl || bind.charAt(s + 1) != ',') {
bind(key, val);
key.setLength(0);
val.setLength(0);
first = true;
continue;
}
// commas are escaped by a second comma
s++;
}
val.append(ch);
}
}
bind(key, val);
}
/**
* Binds the specified variable.
* If a URI is specified, the query is treated as a module.
* @param key key
* @param val value
* @throws QueryException query exception
*/
final void bind(final StringBuilder key, final StringBuilder val)
throws QueryException {
final String k = key.toString().trim();
if(!k.isEmpty()) ctx.bind(k, new Atm(token(val.toString())));
}
/**
* Parses the specified query or module.
* If a URI is specified, the query is treated as a module.
* @param input optional input file
* @param uri module uri.
* @return resulting expression
* @throws QueryException query exception
*/
public final Expr parse(final IO input, final byte[] uri)
throws QueryException {
file = input;
if(!more()) error(QUERYEMPTY);
// checks if the query string contains invalid characters
for(int p = 0; p < ql;) {
// only retrieve code points for large character codes (faster)
int cp = query.charAt(p);
final boolean hs = cp >= Character.MIN_HIGH_SURROGATE;
if(hs) cp = query.codePointAt(p);
if(!XMLToken.valid(cp)) {
qp = p;
error(QUERYINV, cp);
}
p += hs ? Character.charCount(cp) : 1;
}
final Expr expr = parse(uri);
if(more()) {
if(alter != null) error();
error(QUERYEND, rest());
}
// completes the parsing step
assignURI(0);
ctx.funcs.check();
ctx.vars.checkUp();
if(ctx.sc.nsElem != null) ctx.sc.ns.add(EMPTY, ctx.sc.nsElem, null);
// set default decimal format
final byte[] empty = new QNm(EMPTY).eqname();
if(ctx.sc.decFormats.get(empty) == null) {
ctx.sc.decFormats.add(empty, new DecFormatter());
}
return expr;
}
/**
* Parses the specified query and starts with the "Module" rule.
* If a URI is specified, the query is treated as a module.
* @param u module uri
* @return resulting expression
* @throws QueryException query exception
*/
public final Expr parse(final byte[] u) throws QueryException {
try {
Expr expr = null;
versionDecl();
if(u == null) {
expr = mainModule();
if(expr == null) {
if(alter != null) error();
else error(EXPREMPTY);
}
} else {
moduleDecl(u);
}
return expr;
} catch(final QueryException ex) {
mark();
ex.pos(this);
throw ex;
}
}
/**
* Parses the "VersionDecl" rule.
* @throws QueryException query exception
*/
private void versionDecl() throws QueryException {
final int p = qp;
if(!wsConsumeWs(XQUERY)) return;
final boolean version = wsConsumeWs(VERSION);
if(version) {
// parse xquery version
final String ver = string(stringLiteral());
if(ver.equals(XQ10)) ctx.xquery3 = false;
else if(eq(ver, XQ11, XQ30)) ctx.xquery3 = true;
else error(XQUERYVER, ver);
}
// parse xquery encoding (ignored, as input always comes in as string)
if((version || ctx.xquery3) && wsConsumeWs(ENCODING)) {
final String enc = string(stringLiteral());
if(!supported(enc)) error(XQUERYENC2, enc);
} else if(!version) {
qp = p;
return;
}
wsCheck(";");
}
/**
* Parses the "MainModule" rule.
* Parses the "Setter" rule.
* Parses the "QueryBody (= Expr)" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr mainModule() throws QueryException {
prolog1();
prolog2();
return expr();
}
/**
* Parses the "ModuleDecl" rule.
* @param u module uri (may be empty)
* @throws QueryException query exception
*/
private void moduleDecl(final byte[] u) throws QueryException {
wsCheck(MODULE);
wsCheck(NSPACE);
skipWS();
final byte[] name = ncName(XPNAME);
wsCheck(IS);
final byte[] uri = stringLiteral();
if(uri.length == 0) error(NSMODURI);
module = new QNm(name, uri);
ctx.sc.ns.add(name, uri, input());
skipWS();
check(';');
prolog1();
prolog2();
// check if import and declaration uri match
// skip test if module has not been imported (in this case, URI is empty)
if(u.length != 0 && !eq(u, module.uri())) {
final boolean admin = ctx.context.user.perm(User.ADMIN);
error(WRONGMODULE, module.uri(), admin ? file.toString() : file.name());
}
}
/**
* Parses the "Prolog" rule.
* Parses the "Setter" rule.
* @throws QueryException query exception
*/
private void prolog1() throws QueryException {
while(true) {
final int p = qp;
if(wsConsumeWs(DECLARE)) {
if(wsConsumeWs(DEFAULT)) {
if(!defaultNamespaceDecl() && !defaultCollationDecl()
&& !emptyOrderDecl() && !(ctx.xquery3 && decimalFormatDecl(true)))
error(DECLINCOMPLETE);
} else if(wsConsumeWs(BOUNDARY)) {
boundarySpaceDecl();
} else if(wsConsumeWs(BASEURI)) {
baseURIDecl();
} else if(wsConsumeWs(CONSTRUCTION)) {
constructionDecl();
} else if(wsConsumeWs(ORDERING)) {
orderingModeDecl();
} else if(wsConsumeWs(REVALIDATION)) {
revalidationDecl();
} else if(wsConsumeWs(COPYNS)) {
copyNamespacesDecl();
} else if(ctx.xquery3 && wsConsumeWs(DECFORMAT)) {
decimalFormatDecl(false);
} else if(wsConsumeWs(NSPACE)) {
namespaceDecl();
} else if(wsConsumeWs(FTOPTION)) {
final FTOpt fto = new FTOpt();
while(ftMatchOption(fto));
ctx.ftopt.copy(fto);
} else {
qp = p;
return;
}
} else if(wsConsumeWs(IMPORT)) {
if(wsConsumeWs(SCHEMA)) {
schemaImport();
} else if(wsConsumeWs(MODULE)) {
moduleImport();
} else {
qp = p;
return;
}
} else {
return;
}
skipWS();
check(';');
}
}
/**
* Parses the "Prolog" rule.
* @throws QueryException query exception
*/
private void prolog2() throws QueryException {
while(true) {
final int p = qp;
if(!wsConsumeWs(DECLARE)) return;
if(ctx.xquery3 && wsConsumeWs(CONTEXT)) {
contextItemDecl();
} else if(wsConsumeWs(VARIABLE)) {
varDecl();
} else if(wsConsumeWs(UPDATING)) {
ctx.updating = true;
wsCheck(FUNCTION);
functionDecl(true);
} else if(wsConsumeWs(FUNCTION)) {
functionDecl(false);
} else if(wsConsumeWs(OPTION)) {
optionDecl();
} else if(wsConsumeWs(DEFAULT)) {
error(PROLOGORDER);
} else if(curr('%')) {
annotatedDecl();
} else {
qp = p;
return;
}
skipWS();
check(';');
}
}
/**
* Parses the "AnnotatedDecl" rule.
* @throws QueryException query exception
*/
private void annotatedDecl() throws QueryException {
annotation();
if(wsConsumeWs(VARIABLE)) {
varDecl();
} else if(wsConsumeWs(FUNCTION)) {
functionDecl(false);
} else {
error(VARFUNC);
}
}
/**
* Parses the "Annotation" rule.
* @throws QueryException query exception
*/
private void annotation() throws QueryException {
// XQuery30: annotations (currently ignored)
check('%');
eQName(QNAMEINV, ctx.sc.nsFunc);
if(wsConsumeWs(PAR1)) {
do literal(); while(wsConsumeWs(COMMA));
wsCheck(PAR2);
}
skipWS();
}
/**
* Parses the "NamespaceDecl" rule.
* @throws QueryException query exception
*/
private void namespaceDecl() throws QueryException {
final byte[] pref = ncName(XPNAME);
wsCheck(IS);
final byte[] uri = stringLiteral();
if(ctx.sc.ns.staticURI(pref) != null) error(DUPLNSDECL, pref);
ctx.sc.ns.add(pref, uri, input());
}
/**
* Parses the "RevalidationDecl" rule.
* @throws QueryException query exception
*/
private void revalidationDecl() throws QueryException {
if(declReval) error(DUPLREVAL);
declReval = true;
if(wsConsumeWs(STRICT) || wsConsumeWs(LAX)) error(NOREVAL);
wsCheck(SKIP);
}
/**
* Parses the "BoundarySpaceDecl" rule.
* @throws QueryException query exception
*/
private void boundarySpaceDecl() throws QueryException {
if(declSpaces) error(DUPLBOUND);
declSpaces = true;
final boolean spaces = wsConsumeWs(PRESERVE);
if(!spaces) wsCheck(STRIP);
ctx.sc.spaces = spaces;
}
/**
* Parses the "DefaultNamespaceDecl" rule.
* @return true if declaration was found
* @throws QueryException query exception
*/
private boolean defaultNamespaceDecl() throws QueryException {
final boolean elem = wsConsumeWs(ELEMENT);
if(!elem && !wsConsumeWs(FUNCTION)) return false;
wsCheck(NSPACE);
final byte[] uri = stringLiteral();
if(eq(XMLURI, uri)) error(BINDXMLURI, uri, XML);
if(eq(XMLNSURI, uri)) error(BINDXMLURI, uri, XMLNS);
if(elem) {
if(declElem) error(DUPLNS);
declElem = true;
ctx.sc.nsElem = uri.length == 0 ? null : uri;
} else {
if(declFunc) error(DUPLNS);
declFunc = true;
ctx.sc.nsFunc = uri.length == 0 ? null : uri;
}
return true;
}
/**
* Parses the "OptionDecl" rule.
* @throws QueryException query exception
*/
private void optionDecl() throws QueryException {
skipWS();
final QNm name = eQName(QNAMEINV, URICHECK);
final byte[] val = stringLiteral();
if(ctx.xquery3 && eq(name.prefix(), OUTPUT)) {
// output declaration
final String key = string(name.local());
if(module != null) error(MODOUT);
if(ctx.serProp == null) ctx.serProp = new SerializerProp();
if(ctx.serProp.get(key) == null) error(OUTWHICH, key);
if(serial.contains(key)) error(OUTDUPL, key);
ctx.serProp.set(key, string(val));
serial.add(key);
} else if(eq(name.prefix(), DB)) {
// project-specific declaration
final String key = string(uc(name.local()));
final Object obj = ctx.context.prop.get(key);
if(obj == null) error(NOOPTION, key);
// cache old value (to be reset after query evaluation)
ctx.globalOpt.put(key, obj);
ctx.dbOptions.put(key, string(val));
}
// ignore unknown options
}
/**
* Parses the "OrderingModeDecl" rule.
* @throws QueryException query exception
*/
private void orderingModeDecl() throws QueryException {
if(declOrder) error(DUPLORD);
declOrder = true;
ctx.sc.ordered = wsConsumeWs(ORDERED);
if(!ctx.sc.ordered) wsCheck(UNORDERED);
}
/**
* Parses the "emptyOrderDecl" rule.
* @return true if declaration was found
* @throws QueryException query exception
*/
private boolean emptyOrderDecl() throws QueryException {
if(!wsConsumeWs(ORDER)) return false;
wsCheck(EMPTYORD);
if(declGreat) error(DUPLORDEMP);
declGreat = true;
ctx.sc.orderGreatest = wsConsumeWs(GREATEST);
if(!ctx.sc.orderGreatest) wsCheck(LEAST);
return true;
}
/**
* Parses the "copyNamespacesDecl" rule.
* Parses the "PreserveMode" rule.
* Parses the "InheritMode" rule.
* @throws QueryException query exception
*/
private void copyNamespacesDecl() throws QueryException {
if(declPres) error(DUPLCOPYNS);
declPres = true;
ctx.sc.nsPreserve = wsConsumeWs(PRESERVE);
if(!ctx.sc.nsPreserve) wsCheck(NOPRESERVE);
wsCheck(COMMA);
ctx.sc.nsInherit = wsConsumeWs(INHERIT);
if(!ctx.sc.nsInherit) wsCheck(NOINHERIT);
}
/**
* Parses the "DecimalFormatDecl" rule.
* @param def default flag
* @return true if declaration was found
* @throws QueryException query exception
*/
private boolean decimalFormatDecl(final boolean def) throws QueryException {
if(def && !wsConsumeWs(DECFORMAT)) return false;
// use empty name for default declaration
final QNm name = def ? new QNm() : eQName(QNAMEINV, null);
// check if format has already been declared
if(ctx.sc.decFormats.get(name.eqname()) != null) error(DECDUPL);
// create new format
final HashMap<String, String> map = new HashMap<String, String>();
// collect all property declarations
int n;
do {
n = map.size();
skipWS();
final String prop = string(ncName(null));
for(final String s : DECFORMATS) {
if(!prop.equals(s)) continue;
if(map.get(s) != null) error(DECDUPLPROP, s);
wsCheck(IS);
map.put(s, string(stringLiteral()));
break;
}
if(map.isEmpty()) error(NODECLFORM, prop);
} while(n != map.size());
// completes the format declaration
ctx.sc.decFormats.add(name.eqname(), new DecFormatter(input(), map));
return true;
}
/**
* Parses the "DefaultCollationDecl" rule.
* @return query expression
* @throws QueryException query exception
*/
private boolean defaultCollationDecl() throws QueryException {
if(!wsConsumeWs(COLLATION)) return false;
if(declColl) error(DUPLCOLL);
declColl = true;
final byte[] cl = ctx.sc.baseURI().resolve(
Uri.uri(stringLiteral())).string();
if(!eq(URLCOLL, cl)) error(COLLWHICH, cl);
return true;
}
/**
* Parses the "BaseURIDecl" rule.
* @throws QueryException query exception
*/
private void baseURIDecl() throws QueryException {
if(declBase) error(DUPLBASE);
declBase = true;
final byte[] base = stringLiteral();
if(base.length != 0) ctx.sc.baseURI(string(base));
}
/**
* Parses the "SchemaImport" rule.
* Parses the "SchemaPrefix" rule.
* @throws QueryException query exception
*/
private void schemaImport() throws QueryException {
if(wsConsumeWs(NSPACE)) {
ncName(XPNAME);
wsCheck(IS);
} else if(wsConsumeWs(DEFAULT)) {
wsCheck(ELEMENT);
wsCheck(NSPACE);
}
final byte[] ns = stringLiteral();
if(ns.length == 0) error(NSEMPTY);
if(wsConsumeWs(AT)) {
do stringLiteral(); while(wsConsumeWs(COMMA));
}
error(IMPLSCHEMA);
}
/**
* Parses the "ModuleImport" rule.
* @throws QueryException query exception
*/
private void moduleImport() throws QueryException {
byte[] ns = EMPTY;
if(wsConsumeWs(NSPACE)) {
ns = ncName(XPNAME);
wsCheck(IS);
}
final byte[] uri = trim(stringLiteral());
if(uri.length == 0) error(NSMODURI);
if(modules.contains(uri)) error(DUPLMODULE, uri);
if(ns != EMPTY) ctx.sc.ns.add(ns, uri, input());
try {
if(wsConsumeWs(AT)) {
do {
module(stringLiteral(), uri);
} while(wsConsumeWs(COMMA));
return;
}
if(startsWith(uri, JAVAPRE)) {
// check for Java modules
final String path = string(substring(uri, JAVAPRE.length));
final Class<?> clz = Reflect.find(path);
if(clz == null) error(NOMODULE, uri);
// class must be directly derived from JavaModule
if(clz.getSuperclass() != QueryModule.class)
error(NOCONS, path, QueryModule.class);
final QueryModule jm = (QueryModule) Reflect.get(clz);
if(jm == null) error(NOINV, uri);
jm.init(ctx, input());
// add all public methods of the class
final ArrayList<Method> list = new ArrayList<Method>();
for(final Method m : clz.getMethods()) {
if(m.getDeclaringClass() == clz) list.add(m);
}
// put class into module cache
ctx.javaModules.put(jm, list);
return;
}
// search for uri in namespace dictionary
final TokenSet pkgs = ctx.context.repo.nsDict().get(uri);
if(pkgs != null) {
// load packages with modules having the given uri
for(final byte[] pkg : pkgs) {
if(pkg != null) loadPackage(pkg, new TokenSet(), new TokenSet());
}
return;
}
// check statically known modules
boolean found = false;
for(final byte[] u : MODULES) found |= eq(uri, u);
// check pre-declared modules
final byte[] path = ctx.modDeclared.get(uri);
if(path != null) module(path, uri);
// module not found: show error
else if(!found) error(NOMODULE, uri);
} catch(final StackOverflowError ex) {
error(CIRCMODULE);
}
}
/**
* Parses the specified module.
* @param path file path
* @param uri module uri
* @throws QueryException query exception
*/
private void module(final byte[] path, final byte[] uri)
throws QueryException {
final byte[] u = ctx.modParsed.get(path);
if(u != null) {
if(!eq(uri, u)) error(WRONGMODULE, uri, path);
return;
}
ctx.modParsed.add(path, uri);
// check specified path and path relative to query file
final IO io = io(string(path));
String qu = null;
try {
qu = string(io.read());
} catch(final IOException ex) {
final boolean admin = ctx.context.user.perm(User.ADMIN);
error(NOMODULEFILE, admin ? io.path() : io.name());
}
final StaticContext sc = ctx.sc;
ctx.sc = new StaticContext();
new QueryParser(qu, ctx).parse(io, uri);
ctx.sc = sc;
modules.add(uri);
}
/**
* Loads a package from package repository.
* @param pkgName package name
* @param pkgsToLoad list with packages to be loaded
* @param pkgsLoaded already loaded packages
* @throws QueryException query exception
*/
private void loadPackage(final byte[] pkgName, final TokenSet pkgsToLoad,
final TokenSet pkgsLoaded) throws QueryException {
// return if package is already loaded
if(pkgsLoaded.id(pkgName) != 0) return;
// find package in package dictionary
final byte[] pDir = ctx.context.repo.pkgDict().get(pkgName);
if(pDir == null) error(NECPKGNOTINST, pkgName);
final IOFile pkgDir = ctx.context.repo.path(string(pDir));
// parse package descriptor
final IO pkgDesc = new IOFile(pkgDir, PkgText.DESCRIPTOR);
if(!pkgDesc.exists()) Util.debug(PkgText.MISSDESC, string(pkgName));
final Package pkg = new PkgParser(ctx.context.repo, input()).parse(pkgDesc);
// check if package contains a jar descriptor
final IOFile jarDesc = new IOFile(pkgDir, PkgText.JARDESC);
// add jars to classpath
if(jarDesc.exists()) loadJars(jarDesc, pkgDir, string(pkg.abbrev));
// package has dependencies -> they have to be loaded first => put package
// in list with packages to be loaded
if(pkg.dep.size() != 0) pkgsToLoad.add(pkgName);
for(final Dependency d : pkg.dep) {
if(d.pkg != null) {
// we consider only package dependencies here
final byte[] depPkg = new PkgValidator(
ctx.context.repo, input()).depPkg(d);
if(depPkg == null) {
error(NECPKGNOTINST, string(d.pkg));
} else {
if(pkgsToLoad.id(depPkg) != 0) error(CIRCMODULE);
loadPackage(depPkg, pkgsToLoad, pkgsLoaded);
}
}
}
for(final Component comp : pkg.comps) {
final String path = new IOFile(new IOFile(pkgDir, string(pkg.abbrev)),
string(comp.file)).path();
module(token(path), comp.uri);
}
if(pkgsToLoad.id(pkgName) != 0) pkgsToLoad.delete(pkgName);
pkgsLoaded.add(pkgName);
}
/**
* Loads the jar files registered in jarDesc.
* @param jarDesc jar descriptor
* @param pkgDir package directory
* @param modDir module directory
* @throws QueryException query exception
*/
private void loadJars(final IOFile jarDesc,
final IOFile pkgDir, final String modDir) throws QueryException {
final ObjList<URL> urls = new ObjList<URL>();
// add existing URLs
if(ctx.jars != null) for(final URL u : ctx.jars.getURLs()) urls.add(u);
// add new URLs
final JarDesc desc = new JarParser(ctx.context, input()).parse(jarDesc);
for(final byte[] u : desc.jars) {
// assumes that jar is in the directory containing the xquery modules
final IOFile path = new IOFile(new IOFile(pkgDir, modDir), string(u));
try {
urls.add(new URL(IO.FILEPREF + path));
} catch(final MalformedURLException ex) {
Util.errln(ex.getMessage());
}
}
// add jars to classpath
ctx.jars = new JarLoader(urls.toArray(new URL[urls.size()]));
}
/**
* Parses the "VarDecl" rule.
* @throws QueryException query exception
*/
private void contextItemDecl() throws QueryException {
wsCheck(ITEMM);
if(declItem) error(DUPLITEM);
declItem = true;
if(module != null) error(DECITEM);
final SeqType st = optAsType();
if(st != null && st.type == AtomType.EMP) error(NOTYPE, st);
ctx.sc.initType = st;
if(!wsConsumeWs(EXTERNAL)) wsCheck(ASSIGN);
else if(!wsConsumeWs(ASSIGN)) return;
ctx.ctxItem = check(single(), NOVARDECL);
}
/**
* Parses the "VarDecl" rule.
* @throws QueryException query exception
*/
private void varDecl() throws QueryException {
final Var v = typedVar();
if(module != null && !eq(v.name.uri(), module.uri())) error(MODNS, v);
// check if variable has already been declared
final Var old = ctx.vars.get(v.name);
// throw no error if a variable has been externally bound
if(old != null && old.declared) error(VARDEFINE, old);
(old != null ? old : v).declared = true;
if(wsConsumeWs(EXTERNAL)) {
// bind value with new type
if(old != null && v.type != null) old.reset(v.type, ctx);
// bind default value
if(ctx.xquery3 && wsConsumeWs(ASSIGN)) {
v.bind(check(single(), NOVARDECL), ctx);
}
} else {
wsCheck(ASSIGN);
v.bind(check(single(), NOVARDECL), ctx);
}
// bind variable if not done yet
if(old == null) ctx.vars.updateGlobal(v);
}
/**
* Parses a variable declaration with optional type.
* @return parsed variable
* @throws QueryException query exception
*/
private Var typedVar() throws QueryException {
return Var.create(ctx, input(), varName(), optAsType());
}
/**
* Parses an optional SeqType declaration.
* @return type if preceded by {@code as}, {@code null} otherwise
* @throws QueryException query exception
*/
private SeqType optAsType() throws QueryException {
return wsConsumeWs(AS) ? sequenceType() : null;
}
/**
* Parses the "ConstructionDecl" rule.
* @throws QueryException query exception
*/
private void constructionDecl() throws QueryException {
if(declConstr) error(DUPLCONS);
declConstr = true;
ctx.sc.construct = wsConsumeWs(PRESERVE);
if(!ctx.sc.construct) wsCheck(STRIP);
}
/**
* Parses the "FunctionDecl" rule.
* @param up updating flag
* @throws QueryException query exception
*/
private void functionDecl(final boolean up) throws QueryException {
skipWS();
final QNm name = eQName(FUNCNAME, ctx.sc.nsFunc);
if(keyword(name)) error(RESERVED, name);
if(module != null && !eq(name.uri(), module.uri())) error(MODNS, name);
wsCheck(PAR1);
final VarStack vl = ctx.vars.cache(4);
final Var[] args = paramList();
wsCheck(PAR2);
final UserFunc func = new UserFunc(input(), name, args, optAsType(), true);
func.updating = up;
ctx.funcs.add(func, input());
if(!wsConsumeWs(EXTERNAL)) func.expr = enclosed(NOFUNBODY);
ctx.vars.reset(vl);
}
/**
* Checks if the specified name equals reserved function names.
* @param name name to be checked
* @return result of check
*/
private static boolean keyword(final QNm name) {
if(name.hasPrefix()) return false;
final byte[] str = name.string();
for(final byte[] key : KEYWORDS) if(eq(key, str)) return true;
return false;
}
/**
* Parses a ParamList.
* @return declared variables
* @throws QueryException query exception
*/
private Var[] paramList() throws QueryException {
Var[] args = { };
skipWS();
while(true) {
if(curr() != '$') {
if(args.length == 0) break;
check('$');
}
final Var var = typedVar();
ctx.vars.add(var);
for(final Var v : args)
if(v.name.eq(var.name)) error(FUNCDUPL, var);
args = Array.add(args, var);
if(!consume(',')) break;
skipWS();
}
return args;
}
/**
* Parses the "EnclosedExpr" rule.
* @param err error message
* @return query expression
* @throws QueryException query exception
*/
private Expr enclosed(final Err err) throws QueryException {
wsCheck(BRACE1);
final Expr e = check(expr(), err);
wsCheck(BRACE2);
return e;
}
/**
* Parses the "Expr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr expr() throws QueryException {
final Expr e = single();
if(e == null) {
if(more()) return null;
if(alter != null) error();
else error(NOEXPR);
}
if(!wsConsume(COMMA)) return e;
Expr[] l = { e };
do l = add(l, single()); while(wsConsume(COMMA));
return new List(input(), l);
}
/**
* Parses the "ExprSingle" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr single() throws QueryException {
alter = null;
Expr e = flwor();
if(e == null) e = quantified();
if(e == null) e = switchh();
if(e == null) e = typeswitch();
if(e == null) e = iff();
if(e == null) e = tryCatch();
if(e == null) e = insert();
if(e == null) e = deletee();
if(e == null) e = rename();
if(e == null) e = replace();
if(e == null) e = transform();
if(e == null) e = or();
return e;
}
/**
* Parses the "FLWORExpr" rule.
* Parses the "WhereClause" rule.
* Parses the "OrderByClause" rule.
* Parses the "OrderSpecList" rule.
* Parses the "GroupByClause" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr flwor() throws QueryException {
// XQuery30: tumbling window, sliding window, count, allowing empty
// (still to be parsed and implemented)
final int s = ctx.vars.size();
final ForLet[] fl = forLet();
if(fl == null) return null;
Expr where = null;
if(wsConsumeWs(WHERE)) {
ap = qp;
where = check(single(), NOWHERE);
alter = NOWHERE;
}
Var[][] group = null;
if(ctx.xquery3 && wsConsumeWs(GROUP)) {
wsCheck(BY);
ap = qp;
Var[] grp = null;
do grp = groupSpec(fl, grp); while(wsConsume(COMMA));
// find all non-grouping variables that aren't shadowed
final ObjList<Var> ng = new ObjList<Var>();
Map ngp = Map.EMPTY;
for(final ForLet f : fl) {
vars: for(final Var v : f.vars()) {
final Value old = ngp.get(v.name, null);
final int pos = old.isItem() ? (int) old.itemAt(0).itr(null) : -1;
// number of entries is expected to be tiny, so linear search is fast
for(final Var g : grp) {
if(v.is(g)) {
if(pos >= 0) {
ng.delete(pos);
ngp = ngp.delete(old.itemAt(0), null);
}
continue vars;
}
}
if(pos >= 0) ng.set(pos, v);
else {
ng.add(v);
ngp = ngp.insert(v.name, Int.get(ng.size() - 1), null);
}
}
}
// add new copies for all non-grouping variables
final Var[] ngrp = new Var[ng.size()];
for(int i = ng.size(); --i >= 0;) {
final Var v = ng.get(i);
// if one groups variables such as $x as xs:integer, then the resulting
// sequence isn't compatible with the type and can't be assigned
ngrp[i] = Var.create(ctx, input(), v.name, v.type != null
&& v.type.one() ? SeqType.get(v.type.type, Occ.OM) : null);
ctx.vars.add(ngrp[i]);
}
group = new Var[][]{ grp, ng.toArray(new Var[ng.size()]), ngrp };
alter = GRPBY;
}
OrderBy[] order = null;
final boolean stable = wsConsumeWs(STABLE);
if(stable) wsCheck(ORDER);
if(stable || wsConsumeWs(ORDER)) {
wsCheck(BY);
ap = qp;
do order = orderSpec(order); while(wsConsume(COMMA));
if(order != null) order = Array.add(order, new OrderByStable(input()));
alter = ORDERBY;
}
if(!wsConsumeWs(RETURN)) {
if(alter != null) error();
error(where == null ? FLWORWHERE : order == null ? FLWORORD : FLWORRET);
}
final Expr ret = check(single(), NORETURN);
ctx.vars.size(s);
return GFLWOR.get(fl, where, order, group, ret, input());
}
/**
* Parses the "ForClause" rule.
* Parses the "PositionalVar" rule.
* Parses the "LetClause" rule.
* Parses the "FTScoreVar" rule.
* @return query expression
* @throws QueryException query exception
*/
private ForLet[] forLet() throws QueryException {
ForLet[] fl = null;
boolean comma = false;
while(true) {
final boolean fr = wsConsumeWs(FOR, DOLLAR, NOFOR);
boolean score = !fr && wsConsumeWs(LET, SCORE, NOLET);
if(score) wsCheck(SCORE);
else if(!fr && !wsConsumeWs(LET, DOLLAR, NOLET)) return fl;
do {
if(comma && !fr) score = wsConsumeWs(SCORE);
final QNm name = varName();
final SeqType type = score ? SeqType.DBL : optAsType();
final Var var = Var.create(ctx, input(), name, type);
final Var ps = fr && wsConsumeWs(AT) ? Var.create(ctx, input(),
varName(), SeqType.ITR) : null;
final Var sc = fr && wsConsumeWs(SCORE) ? Var.create(ctx, input(),
varName(), SeqType.DBL) : null;
wsCheck(fr ? IN : ASSIGN);
final Expr e = check(single(), NOVARDECL);
ctx.vars.add(var);
if(ps != null) {
if(name.eq(ps.name)) error(DUPLVAR, var);
ctx.vars.add(ps);
}
if(sc != null) {
if(name.eq(sc.name)) error(DUPLVAR, var);
if(ps != null && ps.name.eq(sc.name)) error(DUPLVAR, ps);
ctx.vars.add(sc);
}
fl = fl == null ? new ForLet[1] : Arrays.copyOf(fl, fl.length + 1);
fl[fl.length - 1] = fr ? new For(input(), e, var, ps, sc) : new Let(
input(), e, var, score);
score = false;
comma = true;
} while(wsConsume(COMMA));
comma = false;
}
}
/**
* Parses the "OrderSpec" rule.
* Parses the "OrderModifier" rule.
* @param order order array
* @return new order array
* @throws QueryException query exception
*/
private OrderBy[] orderSpec(final OrderBy[] order) throws QueryException {
final Expr e = check(single(), ORDERBY);
boolean desc = false;
if(!wsConsumeWs(ASCENDING)) desc = wsConsumeWs(DESCENDING);
boolean least = !ctx.sc.orderGreatest;
if(wsConsumeWs(EMPTYORD)) {
least = !wsConsumeWs(GREATEST);
if(least) wsCheck(LEAST);
}
if(wsConsumeWs(COLLATION)) {
final byte[] coll = stringLiteral();
if(!eq(URLCOLL, coll)) error(INVCOLL, coll);
}
if(e.isEmpty()) return order;
final OrderBy ord = new OrderByExpr(input(), e, desc, least);
return order == null ? new OrderBy[] { ord } : Array.add(order, ord);
}
/**
* Parses the "GroupingSpec" rule.
* @param fl for/let clauses
* @param group grouping specification
* @return new group array
* @throws QueryException query exception
*/
private Var[] groupSpec(final ForLet[] fl, final Var[] group)
throws QueryException {
final Var v = checkVar(varName(), GVARNOTDEFINED);
// the grouping variable has to be declared by the same FLWOR expression
boolean dec = false;
for(final ForLet f : fl) {
if(f.declares(v)) {
dec = true;
break;
}
}
if(!dec) error(GVARNOTDEFINED, v);
if(wsConsumeWs(COLLATION)) {
final byte[] coll = stringLiteral();
if(!eq(URLCOLL, coll)) error(INVCOLL, coll);
}
return group == null ? new Var[] { v } : Array.add(group, v);
}
/**
* Parses the "QuantifiedExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr quantified() throws QueryException {
final boolean some = wsConsumeWs(SOME, DOLLAR, NOSOME);
if(!some && !wsConsumeWs(EVERY, DOLLAR, NOSOME)) return null;
final int s = ctx.vars.size();
For[] fl = { };
do {
final Var var = typedVar();
wsCheck(IN);
final Expr e = check(single(), NOSOME);
ctx.vars.add(var);
fl = Array.add(fl, new For(input(), e, var));
} while(wsConsume(COMMA));
wsCheck(SATISFIES);
final Expr e = check(single(), NOSOME);
ctx.vars.size(s);
return new Quantifier(input(), fl, e, !some);
}
/**
* Parses the "SwitchExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr switchh() throws QueryException {
if(!wsConsumeWs(SWITCH, PAR1, TYPEPAR)) return null;
wsCheck(PAR1);
final Expr expr = check(expr(), NOSWITCH);
SwitchCase[] exprs = { };
wsCheck(PAR2);
// collect all cases
Expr[] cases;
do {
cases = new Expr[1];
while(wsConsumeWs(CASE)) cases = add(cases, single());
if(cases.length == 1) {
// add default case
if(exprs.length == 0) error(WRONGCHAR, CASE, found());
wsCheck(DEFAULT);
}
wsCheck(RETURN);
cases[0] = single();
exprs = Array.add(exprs, new SwitchCase(input(), cases));
} while(cases.length != 1);
return new Switch(input(), expr, exprs);
}
/**
* Parses the "TypeswitchExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr typeswitch() throws QueryException {
if(!wsConsumeWs(TYPESWITCH, PAR1, TYPEPAR)) return null;
wsCheck(PAR1);
final Expr ts = check(expr(), NOTYPESWITCH);
wsCheck(PAR2);
TypeCase[] cases = { };
final int s = ctx.vars.size();
boolean cs;
do {
cs = wsConsumeWs(CASE);
if(!cs) wsCheck(DEFAULT);
skipWS();
QNm name = null;
if(curr('$')) {
name = varName();
if(cs) wsCheck(AS);
}
final Var v = Var.create(ctx, input(), name, cs ? sequenceType() : null);
if(name != null) ctx.vars.add(v);
wsCheck(RETURN);
final Expr ret = check(single(), NOTYPESWITCH);
cases = Array.add(cases, new TypeCase(input(), v, ret));
ctx.vars.size(s);
} while(cs);
if(cases.length == 1) error(NOTYPESWITCH);
return new TypeSwitch(input(), ts, cases);
}
/**
* Parses the "IfExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr iff() throws QueryException {
if(!wsConsumeWs(IF, PAR1, IFPAR)) return null;
wsCheck(PAR1);
final Expr iff = check(expr(), NOIF);
wsCheck(PAR2);
if(!wsConsumeWs(THEN)) error(NOIF);
final Expr thn = check(single(), NOIF);
if(!wsConsumeWs(ELSE)) error(NOIF);
final Expr els = check(single(), NOIF);
return new If(input(), iff, thn, els);
}
/**
* Parses the "OrExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr or() throws QueryException {
final Expr e = and();
if(!wsConsumeWs(OR)) return e;
Expr[] list = { e };
do list = add(list, and()); while(wsConsumeWs(OR));
return new Or(input(), list);
}
/**
* Parses the "AndExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr and() throws QueryException {
final Expr e = comparison();
if(!wsConsumeWs(AND)) return e;
Expr[] list = { e };
do list = add(list, comparison()); while(wsConsumeWs(AND));
return new And(input(), list);
}
/**
* Parses the "ComparisonExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr comparison() throws QueryException {
final Expr e = ftContains();
if(e != null) {
for(final CmpV.Op c : CmpV.Op.values())
if(wsConsumeWs(c.name)) return new CmpV(input(), e, check(ftContains(),
CMPEXPR), c);
for(final CmpN.Op c : CmpN.Op.values())
if(wsConsumeWs(c.name)) return new CmpN(input(), e, check(ftContains(),
CMPEXPR), c);
for(final CmpG.Op c : CmpG.Op.values())
if(wsConsume(c.name)) return new CmpG(input(), e, check(ftContains(),
CMPEXPR), c);
}
return e;
}
/**
* Parses the "FTContainsExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr ftContains() throws QueryException {
final Expr e = stringConcat();
final int p = qp;
// use "=>" and "<-" as unofficial shortcuts for full-text expressions
if(consume('=') && consume('>') || consume('<') && consume('-')) {
skipWS();
} else if(!wsConsumeWs(CONTAINS) || !wsConsumeWs(TEXT)) {
qp = p;
return e;
}
final FTExpr select = ftSelection(false);
if(wsConsumeWs(WITHOUT)) {
wsCheck(CONTENT);
union();
error(FTIGNORE);
}
return new FTContains(e, select, input());
}
/**
* Parses the "StringConcatExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr stringConcat() throws QueryException {
final Expr e = range();
if(!consume(CONCAT)) return e;
Expr[] list = { e };
do list = add(list, range()); while(wsConsume(CONCAT));
return new Concat(input(), list);
}
/**
* Parses the "RangeExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr range() throws QueryException {
final Expr e = additive();
if(!wsConsumeWs(TO)) return e;
return new Range(input(), e, check(additive(), INCOMPLETE));
}
/**
* Parses the "AdditiveExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr additive() throws QueryException {
Expr e = multiplicative();
while(true) {
final Calc c = consume('+') ? Calc.PLUS : consume('-') ? Calc.MINUS
: null;
if(c == null) break;
e = new Arith(input(), e, check(multiplicative(), CALCEXPR), c);
}
return e;
}
/**
* Parses the "MultiplicativeExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr multiplicative() throws QueryException {
Expr e = union();
while(e != null) {
final Calc c = consume('*') ? Calc.MULT : wsConsumeWs(DIV) ? Calc.DIV
: wsConsumeWs(IDIV) ? Calc.IDIV : wsConsumeWs(MOD) ? Calc.MOD : null;
if(c == null) break;
e = new Arith(input(), e, check(union(), CALCEXPR), c);
}
return e;
}
/**
* Parses the "UnionExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr union() throws QueryException {
final Expr e = intersect();
if(e == null || !isUnion()) return e;
Expr[] list = { e };
do list = add(list, intersect()); while(isUnion());
return new Union(input(), list);
}
/**
* Checks if a union operator is found.
* @return result of check
* @throws QueryException query exception
*/
private boolean isUnion() throws QueryException {
if(wsConsumeWs(UNION)) return true;
final int p = qp;
if(consume(PIPE) && !consume(PIPE)) return true;
qp = p;
return false;
}
/**
* Parses the "IntersectExceptExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr intersect() throws QueryException {
final Expr e = instanceoff();
if(wsConsumeWs(INTERSECT)) {
Expr[] list = { e };
do list = add(list, instanceoff()); while(wsConsumeWs(INTERSECT));
return new InterSect(input(), list);
} else if(wsConsumeWs(EXCEPT)) {
Expr[] list = { e };
do list = add(list, instanceoff()); while(wsConsumeWs(EXCEPT));
return new Except(input(), list);
} else {
return e;
}
}
/**
* Parses the "InstanceofExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr instanceoff() throws QueryException {
final Expr e = treat();
if(!wsConsumeWs(INSTANCE)) return e;
wsCheck(OF);
return new Instance(input(), e, sequenceType());
}
/**
* Parses the "TreatExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr treat() throws QueryException {
final Expr e = castable();
if(!wsConsumeWs(TREAT)) return e;
wsCheck(AS);
return new Treat(input(), e, sequenceType());
}
/**
* Parses the "CastableExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr castable() throws QueryException {
final Expr e = cast();
if(!wsConsumeWs(CASTABLE)) return e;
wsCheck(AS);
return new Castable(input(), e, simpleType());
}
/**
* Parses the "CastExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr cast() throws QueryException {
final Expr e = unary();
if(!wsConsumeWs(CAST)) return e;
wsCheck(AS);
return new Cast(input(), e, simpleType());
}
/**
* Parses the "UnaryExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr unary() throws QueryException {
boolean minus = false;
boolean found = false;
do {
skipWS();
if(consume('-')) {
minus ^= true;
found = true;
} else if(consume('+')) {
found = true;
} else {
final Expr e = value();
return found ? new Unary(input(), check(e, EVALUNARY), minus) : e;
}
} while(true);
}
/**
* Parses the "ValueExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr value() throws QueryException {
validate();
final Expr e = path();
return e != null ? e : extension();
}
/**
* Parses the "ValidateExpr" rule.
* @throws QueryException query exception
*/
private void validate() throws QueryException {
if(wsConsumeWs(VALIDATE)) {
if(!wsConsumeWs(STRICT) && !wsConsumeWs(LAX) && wsConsumeWs(TYPE)) {
final QNm qnm = eQName(QNAMEINV, SKIPCHECK);
names.add(new QNmCheck(qnm));
error(NOSCHEMA, qnm);
}
wsCheck(BRACE1);
check(single(), NOVALIDATE);
wsCheck(BRACE2);
error(IMPLVAL);
}
}
/**
* Parses the "ExtensionExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr extension() throws QueryException {
final Expr[] pragmas = pragma();
return pragmas.length == 0 ? null : new Extension(input(), pragmas,
enclosed(NOPRAGMA));
}
/**
* Parses the "Pragma" rule.
* @return array of pragmas
* @throws QueryException query exception
*/
private Expr[] pragma() throws QueryException {
Expr[] pragmas = { };
while(wsConsumeWs(PRAGMA)) {
final QNm name = eQName(QNAMEINV, URICHECK);
char c = curr();
if(c != '#' && !ws(c)) error(PRAGMAINV);
tok.reset();
while(c != '#' || next() != ')') {
if(c == 0) error(PRAGMAINV);
tok.add(consume());
c = curr();
}
pragmas = add(pragmas, new Pragma(name, tok.trim().finish(), input()));
qp += 2;
}
return pragmas;
}
/**
* Parses the "PathExpr" rule.
* Parses the "RelativePathExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr path() throws QueryException {
// XQuery30: bang operator (still to be parsed and implemented)
checkInit();
final int s = consume('/') ? consume('/') ? 2 : 1 : 0;
if(s > 0) checkAxis(s == 2 ? Axis.DESC : Axis.CHILD);
qm = qp;
final Expr ex = step();
if(ex == null) {
if(s == 2) {
if(more()) checkInit();
error(PATHMISS, found());
}
return s == 1 ? new Root(input()) : null;
}
final boolean slash = consume('/');
final boolean step = ex instanceof AxisStep;
if(!slash && s == 0 && !step) return ex;
Expr[] list = { };
if(s == 2) list = add(list, descOrSelf());
final Expr root = s > 0 ? new Root(input()) : !step ? ex : null;
if(root != ex) list = add(list, ex);
if(slash) {
do {
final boolean desc = consume('/');
qm = qp;
if(desc) list = add(list, descOrSelf());
checkAxis(desc ? Axis.DESC : Axis.CHILD);
final Expr st = step();
if(st == null) error(PATHMISS, found());
// skip context nodes
if(!(st instanceof Context)) list = add(list, st);
} while(consume('/'));
}
// if no location steps have been added, add trailing self::node() step as
// replacement for context node to bring results in order
if(list.length == 0) {
list = add(list, AxisStep.get(input(), Axis.SELF, Test.NOD));
}
return Path.get(input(), root, list);
}
/**
* Returns a standard descendant-or-self::node() step.
* @return step
*/
private AxisStep descOrSelf() {
return AxisStep.get(input(), Axis.DESCORSELF, Test.NOD);
}
// methods for query suggestions
/**
* Performs an optional check init.
*/
protected void checkInit() { }
/**
* Performs an optional axis check.
* @param axis axis
*/
@SuppressWarnings("unused")
protected void checkAxis(final Axis axis) { }
/**
* Performs an optional test check.
* @param test node test
* @param attr attribute flag
*/
@SuppressWarnings("unused")
protected void checkTest(final Test test, final boolean attr) { }
/**
* Checks a predicate.
* @param open open flag
*/
@SuppressWarnings("unused")
protected void checkPred(final boolean open) { }
/**
* Parses the "StepExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr step() throws QueryException {
final Expr e = postfix();
return e != null ? e : axisStep();
}
/**
* Parses the "AxisStep" rule.
* @return query expression
* @throws QueryException query exception
*/
private AxisStep axisStep() throws QueryException {
Axis ax = null;
Test test = null;
if(wsConsume(DOT2)) {
ax = Axis.PARENT;
test = Test.NOD;
checkTest(test, false);
} else if(consume('@')) {
ax = Axis.ATTR;
test = nodeTest(true, true);
checkTest(test, true);
if(test == null) {
--qp;
error(NOATTNAME);
}
} else {
for(final Axis a : Axis.values()) {
if(wsConsumeWs(a.name, COLS, NOLOCSTEP)) {
wsConsume(COLS);
ap = qp;
ax = a;
test = nodeTest(a == Axis.ATTR, true);
checkTest(test, a == Axis.ATTR);
break;
}
}
}
if(ax == null) {
ax = Axis.CHILD;
test = nodeTest(false, true);
if(test != null && test.type == NodeType.ATT) ax = Axis.ATTR;
checkTest(test, ax == Axis.ATTR);
}
if(test == null) return null;
Expr[] pred = { };
while(wsConsume(BR1)) {
checkPred(true);
pred = add(pred, expr());
wsCheck(BR2);
checkPred(false);
}
return AxisStep.get(input(), ax, test, pred);
}
/**
* Parses the "NodeTest" rule.
* Parses the "NameTest" rule.
* Parses the "KindTest" rule.
* @param att attribute flag
* @param all check all tests, or only names
* @return query expression
* @throws QueryException query exception
*/
private Test nodeTest(final boolean att, final boolean all)
throws QueryException {
final int p = qp;
if(consume('*')) {
// name test: *
if(!consume(':')) return new NameTest(att);
// name test: *:name
return new NameTest(new QNm(ncName(QNAMEINV)), NameTest.Name.NAME, att);
}
final QNm name = eQName(null, SKIPCHECK);
if(name != null) {
final int p2 = qp;
if(all && wsConsumeWs(PAR1)) {
final NodeType type = NodeType.find(name);
if(type != null) {
tok.reset();
while(!consume(PAR2)) {
if(!more()) error(TESTINCOMPLETE);
tok.add(consume());
}
skipWS();
return tok.trim().size() == 0 ? Test.get(type) : kindTest(type,
tok.finish());
}
} else {
qp = p2;
// name test: prefix:name, name
if(name.hasPrefix() || !consume(':')) {
skipWS();
names.add(new QNmCheck(name, !att));
return new NameTest(name, NameTest.Name.STD, att);
}
// name test: prefix:*
if(consume('*')) {
final QNm nm = new QNm(concat(name.string(), COLON));
names.add(new QNmCheck(nm, !att));
return new NameTest(nm, NameTest.Name.NS, att);
}
}
} else if(ctx.xquery3 && quote(curr())) {
// name test: '':*
final byte[] u = stringLiteral();
if(consume(':') && consume('*')) {
final QNm nm = new QNm(COLON, u);
return new NameTest(nm, NameTest.Name.NS, att);
}
}
qp = p;
return null;
}
/**
* Parses the "FilterExpr" rule.
* Parses the "Predicate" rule.
* @return postfix expression
* @throws QueryException query exception
*/
private Expr postfix() throws QueryException {
Expr e = primary(), old;
do {
old = e;
if(wsConsume(BR1)) {
if(e == null) error(PREDMISSING);
Expr[] pred = { };
do {
pred = add(pred, expr());
wsCheck(BR2);
} while(wsConsume(BR1));
e = new Filter(input(), e, pred);
} else if(e != null) {
final Expr[] args = argumentList(e);
if(args == null) break;
final Var[] part = new Var[args.length];
final boolean pt = partial(args, part);
e = new DynamicFunc(input(), e, args);
if(pt) e = new PartFunc(input(), e, part);
}
} while(e != old);
return e;
}
/**
* Fills gaps from place-holders with variable references.
* @param args argument array
* @param vars variables array
* @return variables bound
*/
private boolean partial(final Expr[] args, final Var[] vars) {
final InputInfo ii = input();
boolean found = false;
for(int i = 0; i < args.length; i++) {
if(args[i] == null) {
vars[i] = ctx.uniqueVar(ii, null);
args[i] = new VarRef(ii, vars[i]);
found = true;
}
}
return found;
}
/**
* Parses the "PrimaryExpr" rule.
* Parses the "VarRef" rule.
* Parses the "ContextItem" rule.
* Parses the "Literal" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr primary() throws QueryException {
skipWS();
final char c = curr();
// variables
if(c == '$') return new VarRef(input(), checkVar(varName(), VARUNDEF));
// parentheses
if(c == '(' && next() != '#') return parenthesized();
// direct constructor
if(c == '<') return constructor();
// function item
if(ctx.xquery3) {
final Expr e = functionItem();
if(e != null) return e;
}
// function call
Expr e = functionCall();
if(e != null) return e;
// computed constructors
e = compConstructor();
if(e != null) return e;
// ordered expression
if(wsConsumeWs(ORDERED, BRACE1, INCOMPLETE) ||
wsConsumeWs(UNORDERED, BRACE1, INCOMPLETE)) return enclosed(NOENCLEXPR);
// map literal
if(wsConsumeWs(MAPSTR, BRACE1, INCOMPLETE)) return mapLiteral();
// context item
if(c == '.' && !digit(next())) {
if(next() == '.') return null;
consume('.');
return new Context(input());
}
// literals
return literal();
}
/**
* Parses a literal map.
* @return map literal
* @throws QueryException query exception
*/
private Expr mapLiteral() throws QueryException {
wsCheck(BRACE1);
Expr[] args = { };
if(!wsConsume(BRACE2)) {
do {
args = add(args, check(single(), INVMAPKEY));
wsCheck(ASSIGN);
args = add(args, check(single(), INVMAPVAL));
} while(wsConsume(COMMA));
wsCheck(BRACE2);
}
return new LitMap(input(), args);
}
/**
* Parses the "FunctionItemExpr" rule.
* Parses the "NamedFunctionRef" rule.
* Parses the "LiteralFunctionItem" rule.
* Parses the "InlineFunction" rule.
* @return query expression, or {@code null}
* @throws QueryException query exception
*/
private Expr functionItem() throws QueryException {
skipWS();
final int pos = qp;
// parse annotations
while(curr('%')) annotation();
// inline function
if(wsConsume(FUNCTION) && wsConsume(PAR1)) {
final int s = ctx.vars.size();
final Var[] args = paramList();
wsCheck(PAR2);
final SeqType type = optAsType();
final Expr body = enclosed(NOFUNBODY);
ctx.vars.size(s);
return new InlineFunc(input(), type, args, body);
}
// named function reference
qp = pos;
final QNm name = eQName(null, ctx.sc.nsFunc);
if(name != null && consume('#')) {
final long card = ((Int) numericLiteral(true)).itr(null);
if(card < 0 || card > Integer.MAX_VALUE) error(FUNCUNKNOWN, name);
return UserFuncs.get(name, card, false, ctx, input());
}
qp = pos;
return null;
}
/**
* Parses the "Literal" rule.
* @return query expression, or {@code null}
* @throws QueryException query exception
*/
private Expr literal() throws QueryException {
final char c = curr();
// literals
if(digit(c) || c == '.') return numericLiteral(false);
// strings
if(!quote(c)) return null;
final int p = qp;
final byte[] s = stringLiteral();
final int q = qp;
if(consume(':')) {
// check for EQName
if(!consume('=')) {
qp = p;
return null;
}
qp = q;
}
return Str.get(s);
}
/**
* Parses the "NumericLiteral" rule.
* Parses the "DecimalLiteral" rule.
* Parses the "IntegerLiteral" rule.
* @param itr integer flag
* @return query expression
* @throws QueryException query exception
*/
private Expr numericLiteral(final boolean itr) throws QueryException {
tok.reset();
while(digit(curr())) tok.add(consume());
final boolean dec = consume('.');
if(dec) {
// decimal literal
if(itr) error(NUMBERITR);
tok.add('.');
while(digit(curr()))
tok.add(consume());
}
if(XMLToken.isNCStartChar(curr())) return checkDbl();
if(dec) return new Dec(tok.finish());
final long l = toLong(tok.finish());
if(l == Long.MIN_VALUE) error(RANGE, tok);
return Int.get(l);
}
/**
* Parses the "DoubleLiteral" rule. Checks if a number is followed by a
* whitespace.
* @return expression
* @throws QueryException query exception
*/
private Expr checkDbl() throws QueryException {
if(!consume('e') && !consume('E')) error(NUMBERWS);
tok.add('e');
if(curr('+') || curr('-')) tok.add(consume());
final int s = tok.size();
while(digit(curr()))
tok.add(consume());
if(s == tok.size()) error(NUMBERINC, tok);
if(XMLToken.isNCStartChar(curr())) error(NUMBERWS);
return Dbl.get(tok.finish(), input());
}
/**
* Parses the "StringLiteral" rule.
* @return query expression
* @throws QueryException query exception
*/
private byte[] stringLiteral() throws QueryException {
skipWS();
final char del = curr();
if(!quote(del)) error(NOQUOTE, found());
consume();
tok.reset();
while(true) {
while(!consume(del)) {
if(!more()) error(NOQUOTE, found());
entity(tok);
}
if(!consume(del)) break;
tok.add(del);
}
return tok.finish();
}
/**
* Parses the "VarName" rule.
* @return query expression
* @throws QueryException query exception
*/
private QNm varName() throws QueryException {
wsCheck(DOLLAR);
skipWS();
return eQName(NOVARNAME, null);
}
/**
* Parses the "ParenthesizedExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr parenthesized() throws QueryException {
wsCheck(PAR1);
final Expr e = expr();
wsCheck(PAR2);
return e == null ? Empty.SEQ : e;
}
/**
* Parses the "FunctionCall" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr functionCall() throws QueryException {
final int p = qp;
final QNm name = eQName(null, ctx.sc.nsFunc);
if(name != null && !keyword(name)) {
final Expr[] args = argumentList(name.string());
if(args != null) {
alter = FUNCUNKNOWN;
alterFunc = name;
ap = qp;
final Var[] vars = new Var[args.length];
final boolean part = partial(args, vars);
final TypedFunc f = ctx.funcs.get(name, args, false, ctx, input());
if(f != null) {
alter = null;
return part ? new PartFunc(input(), f, vars) : f.fun;
}
}
}
qp = p;
return null;
}
/**
* Parses the "ArgumentList" rule.
* @param name name of the function (item)
* @return array of arguments, place-holders '?' are represented as
* {@code null} entries
* @throws QueryException query exception
*/
private Expr[] argumentList(final Object name) throws QueryException {
if(!wsConsume(PAR1)) return null;
Expr[] args = { };
if(!wsConsume(PAR2)) {
do {
Expr arg = null;
if(!wsConsume(PLHOLDER) && (arg = single()) == null)
error(FUNCMISS, name);
// speeding up array creation
final int a = args.length;
final Expr[] tmp = new Expr[a + 1];
System.arraycopy(args, 0, tmp, 0, a);
tmp[a] = arg;
args = tmp;
} while(wsConsume(COMMA));
if(!wsConsume(PAR2)) error(FUNCMISS, name);
}
return args;
}
/**
* Parses the "Constructor" rule.
* Parses the "DirectConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr constructor() throws QueryException {
check('<');
return consume('!') ? dirComment() : consume('?') ? dirPI() : dirElement();
}
/**
* Parses the "DirElemConstructor" rule.
* Parses the "DirAttributeList" rules.
* @return query expression
* @throws QueryException query exception
*/
private Expr dirElement() throws QueryException {
// cache namespace information
final int s = ctx.sc.ns.size();
final byte[] nse = ctx.sc.nsElem;
final int npos = names.size();
final QNm tag = new QNm(qName(TAGNAME));
names.add(new QNmCheck(tag));
consumeWS();
final Atts ns = new Atts();
Expr[] cont = { };
// parse attributes...
boolean xmlDecl = false; // xml prefix explicitly declared?
while(true) {
final byte[] atn = qName(null);
if(atn.length == 0) break;
Expr[] attv = { };
consumeWS();
check('=');
consumeWS();
final char delim = consume();
if(!quote(delim)) error(NOQUOTE, found());
final TokenBuilder tb = new TokenBuilder();
boolean simple = true;
do {
while(!consume(delim)) {
final char ch = curr();
if(ch == '{') {
if(next() == '{') {
tb.add(consume());
consume();
} else {
final byte[] text = tb.finish();
if(text.length != 0) {
attv = add(attv, Str.get(text));
} else {
attv = add(attv, enclosed(NOENCLEXPR));
simple = false;
}
tb.reset();
}
} else if(ch == '}') {
consume();
check('}');
tb.add('}');
} else if(ch == '<' || ch == 0) {
error(NOQUOTE, found());
} else if(ch == '\n' || ch == '\t') {
tb.add(' ');
consume();
} else if(ch == '\r') {
if(next() != '\n') tb.add(' ');
consume();
} else {
entity(tb);
}
}
if(!consume(delim)) break;
tb.add(delim);
} while(true);
if(tb.size() != 0) attv = add(attv, Str.get(tb.finish()));
// parse namespace declarations
final boolean pr = startsWith(atn, XMLNSC);
if(pr || eq(atn, XMLNS)) {
if(!simple) error(NSCONS);
final byte[] pref = pr ? local(atn) : EMPTY;
final byte[] uri = attv.length == 0 ? EMPTY : ((Str) attv[0]).string();
if(eq(pref, XML) && eq(uri, XMLURI)) {
if(xmlDecl) error(DUPLNSDEF, XML);
xmlDecl = true;
} else {
if(pr) {
if(uri.length == 0) error(NSEMPTYURI);
if(eq(pref, XML) || eq(pref, XMLNS)) error(BINDXML, pref);
if(eq(XMLURI, uri)) error(BINDXMLURI, uri, XML);
if(eq(XMLNSURI, uri)) error(BINDXMLURI, uri, XMLNS);
ctx.sc.ns.add(pref, uri);
} else {
ctx.sc.nsElem = uri;
}
if(ns.get(pref) != -1) error(DUPLNSDEF, pref);
ns.add(pref, uri);
}
} else {
final QNm attn = new QNm(atn);
names.add(new QNmCheck(attn, false));
cont = add(cont, new CAttr(input(), false, attn, attv));
}
if(!consumeWS()) break;
}
if(consume('/')) {
check('>');
} else {
check('>');
while(curr() != '<' || next() != '/') {
final Expr e = dirElemContent(tag.string());
if(e == null) continue;
cont = add(cont, e);
}
qp += 2;
final byte[] close = qName(TAGNAME);
consumeWS();
check('>');
if(!eq(tag.string(), close)) error(TAGWRONG, tag.string(), close);
}
assignURI(npos);
ctx.sc.ns.size(s);
ctx.sc.nsElem = nse;
return new CElem(input(), tag, ns, cont);
}
/**
* Parses the "DirElemContent" rule.
* @param tag opening tag
* @return query expression
* @throws QueryException query exception
*/
private Expr dirElemContent(final byte[] tag) throws QueryException {
final TokenBuilder tb = new TokenBuilder();
boolean strip = true;
do {
final char c = curr();
if(c == '<') {
if(wsConsume(CDATA)) {
tb.add(cDataSection());
strip = false;
} else {
final Str txt = text(tb, strip);
return txt != null ? txt : next() == '/' ? null : constructor();
}
} else if(c == '{') {
if(next() == '{') {
tb.add(consume());
consume();
} else {
final Str txt = text(tb, strip);
return txt != null ? txt : enclosed(NOENCLEXPR);
}
} else if(c == '}') {
consume();
check('}');
tb.add('}');
} else if(c != 0) {
strip &= !entity(tb);
} else {
error(NOCLOSING, tag);
}
} while(true);
}
/**
* Returns a string item.
* @param tb token builder
* @param strip strip flag
* @return text or {@code null}
*/
private Str text(final TokenBuilder tb, final boolean strip) {
final byte[] t = tb.finish();
return t.length == 0 || strip && !ctx.sc.spaces && ws(t) ?
null : Str.get(t);
}
/**
* Parses the "DirCommentConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr dirComment() throws QueryException {
check('-');
check('-');
final TokenBuilder tb = new TokenBuilder();
do {
while(not('-'))
tb.add(consume());
consume();
if(consume('-')) {
check('>');
return new CComm(input(), Str.get(tb.finish()));
}
tb.add('-');
} while(true);
}
/**
* Parses the "DirPIConstructor" rule.
* Parses the "DirPIContents" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr dirPI() throws QueryException {
final byte[] str = ncName(INVALPI);
if(eq(lc(str), XML)) error(PIXML, str);
final boolean space = skipWS();
final TokenBuilder tb = new TokenBuilder();
do {
while(not('?')) {
if(!space) error(PIWRONG);
tb.add(consume());
}
consume();
if(consume('>')) {
return new CPI(input(), Str.get(str), Str.get(tb.finish()));
}
tb.add('?');
} while(true);
}
/**
* Parses the "CDataSection" rule.
* @return CData
* @throws QueryException query exception
*/
private byte[] cDataSection() throws QueryException {
final TokenBuilder tb = new TokenBuilder();
while(true) {
while(not(']')) {
char ch = consume();
if(ch == '\r') {
ch = '\n';
if(curr(ch)) consume();
}
tb.add(ch);
}
consume();
if(curr(']') && next() == '>') {
qp += 2;
return tb.finish();
}
tb.add(']');
}
}
/**
* Parses the "ComputedConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compConstructor() throws QueryException {
final int p = qp;
if(wsConsumeWs(DOCUMENT)) return consume(compDoc(), p);
if(wsConsumeWs(ELEMENT)) return consume(compElement(), p);
if(wsConsumeWs(ATTRIBUTE)) return consume(compAttribute(), p);
if(wsConsumeWs(NSPACE)) return consume(compNamespace(), p);
if(wsConsumeWs(TEXT)) return consume(compText(), p);
if(wsConsumeWs(COMMENT)) return consume(compComment(), p);
if(wsConsumeWs(PI)) return consume(compPI(), p);
return null;
}
/**
* Consumes the specified expression or resets the query position.
* @param expr expression
* @param p query position
* @return expression or {@code null}
*/
private Expr consume(final Expr expr, final int p) {
if(expr == null) qp = p;
return expr;
}
/**
* Parses the "CompDocConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compDoc() throws QueryException {
if(!wsConsume(BRACE1)) return null;
final Expr e = check(expr(), NODOCCONS);
wsCheck(BRACE2);
return new CDoc(input(), e);
}
/**
* Parses the "CompElemConstructor" rule.
* Parses the "ContextExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compElement() throws QueryException {
skipWS();
Expr name;
final QNm qn = eQName(null, SKIPCHECK);
if(qn != null) {
name = qn;
names.add(new QNmCheck(qn));
} else {
if(!wsConsume(BRACE1)) return null;
name = check(expr(), NOTAG);
wsCheck(BRACE2);
}
if(!wsConsume(BRACE1)) return null;
final Expr e = expr();
wsCheck(BRACE2);
return new CElem(input(), name, null,
e == null ? new Expr[0] : new Expr[] { e });
}
/**
* Parses the "CompAttrConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compAttribute() throws QueryException {
skipWS();
Expr name;
final QNm qn = eQName(null, SKIPCHECK);
if(qn != null) {
name = qn;
names.add(new QNmCheck(qn, false));
} else {
if(!wsConsume(BRACE1)) return null;
name = expr();
wsCheck(BRACE2);
}
if(!wsConsume(BRACE1)) return null;
final Expr e = expr();
wsCheck(BRACE2);
return new CAttr(input(), true, name, e == null ? Empty.SEQ : e);
}
/**
* Parses the "CompNamespaceConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compNamespace() throws QueryException {
if(!ctx.xquery3) return null;
skipWS();
Expr name;
final byte[] str = ncName(null);
if(str.length != 0) {
name = Str.get(str);
} else {
if(!wsConsume(BRACE1)) return null;
name = check(expr(), NSWRONG);
wsCheck(BRACE2);
}
if(!wsConsume(BRACE1)) return null;
final Expr e = expr();
wsCheck(BRACE2);
return new CNSpace(input(), name, e == null ? Empty.SEQ : e);
}
/**
* Parses the "CompTextConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compText() throws QueryException {
if(!wsConsume(BRACE1)) return null;
final Expr e = check(expr(), NOTXTCONS);
wsCheck(BRACE2);
return new CTxt(input(), e);
}
/**
* Parses the "CompCommentConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compComment() throws QueryException {
if(!wsConsume(BRACE1)) return null;
final Expr e = check(expr(), NOCOMCONS);
wsCheck(BRACE2);
return new CComm(input(), e);
}
/**
* Parses the "CompPIConstructor" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr compPI() throws QueryException {
skipWS();
Expr name;
final byte[] str = ncName(null);
if(str.length != 0) {
name = Str.get(str);
} else {
if(!wsConsume(BRACE1)) return null;
name = check(expr(), PIWRONG);
wsCheck(BRACE2);
}
if(!wsConsume(BRACE1)) return null;
final Expr e = expr();
wsCheck(BRACE2);
return new CPI(input(), name, e == null ? Empty.SEQ : e);
}
/**
* Parses the "SimpleType" rule.
* @return sequence type
* @throws QueryException query exception
*/
private SeqType simpleType() throws QueryException {
skipWS();
final QNm name = eQName(TYPEINVALID, ctx.sc.nsElem);
final Type t = Types.find(name, true);
if(t == null) error(TYPEUNKNOWN, name);
if(t == AtomType.AAT || t == AtomType.NOT) error(CASTUNKNOWN, name);
skipWS();
return SeqType.get(t, consume('?') ? Occ.ZO : Occ.O);
}
/**
* Parses the "SequenceType" rule.
* Parses the "OccurrenceIndicator" rule.
* Parses the "KindTest" rule.
* @return sequence type
* @throws QueryException query exception
*/
private SeqType sequenceType() throws QueryException {
skipWS();
final Type t = itemType();
// parse occurrence indicator
skipWS();
final Occ occ = consume('?') ? Occ.ZO : consume('+') ? Occ.OM :
consume('*') ? Occ.ZM : Occ.O;
if(t == AtomType.EMP && occ != Occ.O) error(EMPTYSEQOCC, t);
final KindTest kt = tok.size() == 0 ? null : kindTest(t, tok.finish());
tok.reset();
// use empty name test if types are different
skipWS();
return SeqType.get(t, occ, kt == null ? null : kt.extype == null ||
t == kt.extype || !kt.extype.isNode() ? kt.name : new QNm());
}
/**
* Parses the "ItemType" rule.
* @return item type
* @throws QueryException query exception
*/
private Type itemType() throws QueryException {
skipWS();
// parenthesized type
if(consume(PAR1)) {
final Type ret = itemType();
wsCheck(PAR2);
return ret;
}
boolean ann = false;
while(curr('%')) {
annotation();
ann = true;
}
final QNm name = eQName(TYPEINVALID, null);
// parse non-atomic types
final boolean atom = !wsConsumeWs(PAR1);
Type t = Types.find(name, atom);
if(ann && (t == null || !t.isFunction())) error(NOANN);
tok.reset();
if(!atom) {
if(t != null && t.isFunction()) {
// function type
if(!wsConsume(ASTERISK)) {
if(t.isMap()) {
final Type key = itemType();
if(key == null) throw error(MAPTKV, name.string());
if(!key.instanceOf(AtomType.AAT)) throw error(MAPTAAT, key);
wsCheck(COMMA);
t = MapType.get((AtomType) key, sequenceType());
if(!wsConsume(PAR2)) error(FUNCMISS, name.string());
} else {
// function type
SeqType[] args = { };
if(!wsConsume(PAR2)) {
// function has got arguments
do {
args = Array.add(args, sequenceType());
} while(wsConsume(COMMA));
if(!wsConsume(PAR2)) error(FUNCMISS, name.string());
}
wsCheck(AS);
t = FuncType.get(sequenceType(), args);
}
} else if(!wsConsume(PAR2)) {
error(FUNCMISS, name.string());
}
} else {
int par = 0;
while(par != 0 || !consume(PAR2)) {
switch(curr()) {
case '(':
par++;
break;
case ')':
par--;
break;
case '\0':
error(FUNCMISS, name.string());
}
tok.add(consume());
}
}
}
if(t == null) {
if(atom) error(TYPEUNKNOWN, name);
error(NOTYPE,
new TokenBuilder(name.string()).add('(').add(tok.finish()).add(')'));
}
return t;
}
/**
* Checks the arguments of the kind test.
* @param t type
* @param k kind arguments
* @return arguments
* @throws QueryException query exception
*/
private KindTest kindTest(final Type t, final byte[] k)
throws QueryException {
byte[] nm = trim(k);
// processing-instruction test
if(t == NodeType.PI) {
final boolean s = startsWith(k, '\'') || startsWith(k, '"');
nm = trim(delete(delete(k, '\''), '"'));
if(!XMLToken.isNCName(nm)) {
if(s) error(XPINVNAME, nm);
error(TESTINVALID, t, k);
}
return new KindTest((NodeType) t, new QNm(nm), null);
}
// element/attribute test
final boolean elm = t == NodeType.ELM;
if(!elm && t != NodeType.ATT) error(TESTINVALID, t, k);
Type tp = t;
final int i = indexOf(nm, ',');
if(i != -1) {
byte[] type = trim(substring(nm, i + 1));
if(elm && endsWith(type, '?')) type = substring(type, 0, type.length - 1);
final QNm qnm = new QNm(type, ctx);
if(qnm.hasPrefix() && !qnm.hasURI()) error(NOURI, qnm);
if(!eq(qnm.uri(), XSURI)) error(TYPEUNDEF, type);
final byte[] ln = qnm.local();
tp = Types.find(qnm, true);
if(tp == null && !eq(ln, AtomType.ATY.string()) &&
!eq(ln, AtomType.AST.string()) && !eq(ln, AtomType.UTY.string()))
error(VARUNDEF, qnm);
if(tp == AtomType.ATM || tp == AtomType.AAT) tp = null;
nm = trim(substring(nm, 0, i));
}
if(nm.length == 1 && nm[0] == '*')
return new KindTest((NodeType) t, null, tp);
if(!XMLToken.isEQName(nm)) error(TESTINVALID, t, k);
// extract uri and local name
final QNm qnm;
if(quote(nm[0])) {
final int c = lastIndexOf(nm, ':');
qnm = new QNm(substring(nm, c + 1), substring(nm, 1, c - 1));
} else {
qnm = new QNm(nm, ctx);
}
if(!qnm.hasURI()) {
if(qnm.hasPrefix()) error(NOURI, qnm);
if(elm) qnm.uri(ctx.sc.nsElem);
}
return new KindTest((NodeType) t, qnm, tp);
}
/**
* Parses the "TryCatch" rules.
* @return query expression
* @throws QueryException query exception
*/
private Expr tryCatch() throws QueryException {
if(!ctx.xquery3 || !wsConsumeWs(TRY)) return null;
final Expr tr = enclosed(NOENCLEXPR);
wsCheck(CATCH);
Catch[] ct = { };
do {
QNm[] codes = { };
do {
skipWS();
final Test test = nodeTest(false, false);
if(test == null) error(NOCATCH);
codes = Array.add(codes, test.name);
} while(wsConsumeWs(PIPE));
final Catch c = new Catch(input(), codes, ctx);
final int s = c.prepare(ctx);
c.expr = enclosed(NOENCLEXPR);
ctx.vars.size(s);
ct = Array.add(ct, c);
} while(wsConsumeWs(CATCH));
return new Try(input(), tr, ct);
}
/**
* Parses the "FTSelection" rules.
* @param prg pragma flag
* @return query expression
* @throws QueryException query exception
*/
private FTExpr ftSelection(final boolean prg) throws QueryException {
FTExpr expr = ftOr(prg);
FTExpr old;
FTExpr first = null;
boolean ordered = false;
do {
old = expr;
if(wsConsumeWs(ORDERED)) {
ordered = true;
old = null;
} else if(wsConsumeWs(WINDOW)) {
expr = new FTWindow(input(), expr, additive(), ftUnit());
} else if(wsConsumeWs(DISTANCE)) {
final Expr[] rng = ftRange(false);
if(rng == null) error(FTRANGE);
expr = new FTDistance(input(), expr, rng, ftUnit());
} else if(wsConsumeWs(AT)) {
final boolean start = wsConsumeWs(START);
final boolean end = !start && wsConsumeWs(END);
if(!start && !end) error(INCOMPLETE);
expr = new FTContent(input(), expr, start, end);
} else if(wsConsumeWs(ENTIRE)) {
wsCheck(CONTENT);
expr = new FTContent(input(), expr, false, false);
} else {
final boolean same = wsConsumeWs(SAME);
final boolean diff = !same && wsConsumeWs(DIFFERENT);
if(same || diff) {
FTUnit unit = null;
if(wsConsumeWs(SENTENCE)) unit = FTUnit.SENTENCE;
else if(wsConsumeWs(PARAGRAPH)) unit = FTUnit.PARAGRAPH;
else error(INCOMPLETE);
expr = new FTScope(input(), expr, unit, same);
}
}
if(first == null && old != null && old != expr) first = expr;
} while(old != expr);
if(ordered) {
if(first == null) return new FTOrder(input(), expr);
first.expr[0] = new FTOrder(input(), first.expr[0]);
}
return expr;
}
/**
* Parses the "FTOr" rule.
* @param prg pragma flag
* @return query expression
* @throws QueryException query exception
*/
private FTExpr ftOr(final boolean prg) throws QueryException {
final FTExpr e = ftAnd(prg);
if(!wsConsumeWs(FTOR)) return e;
FTExpr[] list = { e };
do list = Array.add(list, ftAnd(prg)); while(wsConsumeWs(FTOR));
return new FTOr(input(), list);
}
/**
* Parses the "FTAnd" rule.
* @param prg pragma flag
* @return query expression
* @throws QueryException query exception
*/
private FTExpr ftAnd(final boolean prg) throws QueryException {
final FTExpr e = ftMildNot(prg);
if(!wsConsumeWs(FTAND)) return e;
FTExpr[] list = { e };
do list = Array.add(list, ftMildNot(prg)); while(wsConsumeWs(FTAND));
return new FTAnd(input(), list);
}
/**
* Parses the "FTMildNot" rule.
* @param prg pragma flag
* @return query expression
* @throws QueryException query exception
*/
private FTExpr ftMildNot(final boolean prg) throws QueryException {
final FTExpr e = ftUnaryNot(prg);
if(!wsConsumeWs(NOT)) return e;
FTExpr[] list = { };
do {
wsCheck(IN);
list = Array.add(list, ftUnaryNot(prg));
} while(wsConsumeWs(NOT));
// convert "A not in B not in ..." to "A not in(B or ...)"
return new FTMildNot(input(), e, list.length == 1 ? list[0] : new FTOr(
input(), list));
}
/**
* Parses the "FTUnaryNot" rule.
* @param prg pragma flag
* @return query expression
* @throws QueryException query exception
*/
private FTExpr ftUnaryNot(final boolean prg) throws QueryException {
final boolean not = wsConsumeWs(FTNOT);
final FTExpr e = ftPrimaryWithOptions(prg);
return not ? new FTNot(input(), e) : e;
}
/**
* Parses the "FTPrimaryWithOptions" rule.
* @param prg pragma flag
* @return query expression
* @throws QueryException query exception
*/
private FTExpr ftPrimaryWithOptions(final boolean prg) throws QueryException {
FTExpr expr = ftPrimary(prg);
final FTOpt fto = new FTOpt();
boolean found = false;
while(ftMatchOption(fto)) found = true;
// check if specified language is not available
if(fto.ln == null) fto.ln = Language.def();
if(!Tokenizer.supportFor(fto.ln)) error(FTNOTOK, fto.ln);
if(fto.is(ST) && fto.sd == null && !Stemmer.supportFor(fto.ln))
error(FTNOSTEM, fto.ln);
// consume weight option
if(wsConsumeWs(WEIGHT)) expr = new FTWeight(input(), expr,
enclosed(NOENCLEXPR));
// skip options if none were specified...
return found ? new FTOptions(input(), expr, fto) : expr;
}
/**
* Parses the "FTPrimary" rule.
* @param prg pragma flag
* @return query expression
* @throws QueryException query exception
*/
private FTExpr ftPrimary(final boolean prg) throws QueryException {
final Expr[] pragmas = pragma();
if(pragmas.length != 0) {
wsCheck(BRACE1);
final FTExpr e = ftSelection(true);
wsCheck(BRACE2);
return new FTExtensionSelection(input(), pragmas, e);
}
if(wsConsumeWs(PAR1)) {
final FTExpr e = ftSelection(false);
wsCheck(PAR2);
return e;
}
skipWS();
final Expr e = curr('{') ? enclosed(NOENCLEXPR)
: quote(curr()) ? Str.get(stringLiteral()) : null;
if(e == null) error(prg ? NOPRAGMA : NOENCLEXPR);
// FTAnyAllOption
FTMode mode = FTMode.M_ANY;
if(wsConsumeWs(ALL)) {
mode = wsConsumeWs(WORDS) ? FTMode.M_ALLWORDS : FTMode.M_ALL;
} else if(wsConsumeWs(ANY)) {
mode = wsConsumeWs(WORD) ? FTMode.M_ANYWORD : FTMode.M_ANY;
} else if(wsConsumeWs(PHRASE)) {
mode = FTMode.M_PHRASE;
}
// FTTimes
Expr[] occ = null;
if(wsConsumeWs(OCCURS)) {
occ = ftRange(false);
if(occ == null) error(FTRANGE);
wsCheck(TIMES);
}
return new FTWords(input(), e, mode, occ);
}
/**
* Parses the "FTRange" rule.
* @param i accept only integers ("FTLiteralRange")
* @return query expression
* @throws QueryException query exception
*/
private Expr[] ftRange(final boolean i) throws QueryException {
final Expr[] occ = { Int.get(1), Int.get(Long.MAX_VALUE)};
if(wsConsumeWs(EXACTLY)) {
occ[0] = ftAdditive(i);
occ[1] = occ[0];
} else if(wsConsumeWs(AT)) {
if(wsConsumeWs(LEAST)) {
occ[0] = ftAdditive(i);
} else {
wsCheck(MOST);
occ[0] = Int.get(0);
occ[1] = ftAdditive(i);
}
} else if(wsConsumeWs(FROM)) {
occ[0] = ftAdditive(i);
wsCheck(TO);
occ[1] = ftAdditive(i);
} else {
return null;
}
return occ;
}
/**
* Returns an argument of the "FTRange" rule.
* @param i accept only integers
* @return query expression
* @throws QueryException query exception
*/
private Expr ftAdditive(final boolean i) throws QueryException {
if(!i) return additive();
skipWS();
tok.reset();
while(digit(curr()))
tok.add(consume());
if(tok.size() == 0) error(INTEXP);
return Int.get(toLong(tok.finish()));
}
/**
* Parses the "FTUnit" rule.
* @return query expression
* @throws QueryException query exception
*/
private FTUnit ftUnit() throws QueryException {
if(wsConsumeWs(WORDS)) return FTUnit.WORD;
if(wsConsumeWs(SENTENCES)) return FTUnit.SENTENCE;
if(wsConsumeWs(PARAGRAPHS)) return FTUnit.PARAGRAPH;
error(INCOMPLETE);
return null;
}
/**
* Parses the "FTMatchOption" rule.
* @param opt options instance
* @return false if no options were found
* @throws QueryException query exception
*/
private boolean ftMatchOption(final FTOpt opt) throws QueryException {
if(!wsConsumeWs(USING)) return false;
if(wsConsumeWs(LOWERCASE)) {
if(opt.isSet(LC) || opt.isSet(UC) || opt.isSet(CS)) error(FTDUP, CASE);
opt.set(CS, true);
opt.set(LC, true);
} else if(wsConsumeWs(UPPERCASE)) {
if(opt.isSet(LC) || opt.isSet(UC) || opt.isSet(CS)) error(FTDUP, CASE);
opt.set(CS, true);
opt.set(UC, true);
} else if(wsConsumeWs(CASE)) {
if(opt.isSet(LC) || opt.isSet(UC) || opt.isSet(CS)) error(FTDUP, CASE);
opt.set(CS, wsConsumeWs(SENSITIVE));
if(!opt.is(CS)) wsCheck(INSENSITIVE);
} else if(wsConsumeWs(DIACRITICS)) {
if(opt.isSet(DC)) error(FTDUP, DIACRITICS);
opt.set(DC, wsConsumeWs(SENSITIVE));
if(!opt.is(DC)) wsCheck(INSENSITIVE);
} else if(wsConsumeWs(LANGUAGE)) {
if(opt.ln != null) error(FTDUP, LANGUAGE);
final byte[] lan = stringLiteral();
opt.ln = Language.get(string(lan));
if(opt.ln == null) error(FTNOTOK, lan);
} else if(wsConsumeWs(OPTION)) {
optionDecl();
} else {
final boolean using = !wsConsumeWs(NO);
if(wsConsumeWs(STEMMING)) {
if(opt.isSet(ST)) error(FTDUP, STEMMING);
opt.set(ST, using);
} else if(wsConsumeWs(THESAURUS)) {
if(opt.th != null) error(FTDUP, THESAURUS);
opt.th = new ThesQuery();
if(using) {
final boolean par = wsConsume(PAR1);
if(!wsConsumeWs(DEFAULT)) ftThesaurusID(opt.th);
while(par && wsConsume(COMMA))
ftThesaurusID(opt.th);
if(par) wsCheck(PAR2);
}
} else if(wsConsumeWs(STOP)) {
// add union/except
wsCheck(WORDS);
if(opt.sw != null) error(FTDUP, STOP + ' ' + WORDS);
opt.sw = new StopWords();
if(wsConsumeWs(DEFAULT)) {
if(!using) error(FTSTOP);
} else {
boolean union = false;
boolean except = false;
while(using) {
if(wsConsume(PAR1)) {
do {
final byte[] sl = stringLiteral();
if(except) opt.sw.delete(sl);
else if(!union || opt.sw.id(sl) == 0) opt.sw.add(sl);
} while(wsConsume(COMMA));
wsCheck(PAR2);
} else if(wsConsumeWs(AT)) {
String fn = string(stringLiteral());
if(ctx.stop != null) fn = ctx.stop.get(fn);
final IO fl = io(fn);
if(!opt.sw.read(fl, except)) error(NOSTOPFILE, fl);
} else if(!union && !except) {
error(FTSTOP);
}
union = wsConsumeWs(UNION);
except = !union && wsConsumeWs(EXCEPT);
if(!union && !except) break;
}
}
} else if(wsConsumeWs(WILDCARDS)) {
if(opt.isSet(WC)) error(FTDUP, WILDCARDS);
if(opt.is(FZ)) error(FTFZWC);
opt.set(WC, using);
} else if(wsConsumeWs(FUZZY)) {
if(opt.isSet(FZ)) error(FTDUP, FUZZY);
if(opt.is(WC)) error(FTFZWC);
opt.set(FZ, using);
} else {
error(FTMATCH, consume());
return false;
}
}
return true;
}
/**
* Parses the "FTThesaurusID" rule.
* @param thes link to thesaurus
* @throws QueryException query exception
*/
private void ftThesaurusID(final ThesQuery thes) throws QueryException {
wsCheck(AT);
String fn = string(stringLiteral());
if(ctx.thes != null) fn = ctx.thes.get(fn);
final IO fl = io(fn);
final byte[] rel = wsConsumeWs(RELATIONSHIP) ? stringLiteral() : EMPTY;
final Expr[] range = ftRange(true);
long min = 0;
long max = Long.MAX_VALUE;
if(range != null) {
wsCheck(LEVELS);
// values will always be integer instances
min = ((Int) range[0]).itr(input());
max = ((Int) range[1]).itr(input());
}
thes.add(new Thesaurus(fl, rel, min, max, ctx.context));
}
/**
* Parses the "InsertExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr insert() throws QueryException {
final int p = qp;
if(!wsConsumeWs(INSERT) || !wsConsumeWs(NODE) && !wsConsumeWs(NODES)) {
qp = p;
return null;
}
final Expr s = check(single(), INCOMPLETE);
boolean first = false;
boolean last = false;
boolean before = false;
boolean after = false;
if(wsConsumeWs(AS)) {
first = wsConsumeWs(FIRST);
if(!first) {
wsCheck(LAST);
last = true;
}
wsCheck(INTO);
} else if(!wsConsumeWs(INTO)) {
after = wsConsumeWs(AFTER);
before = !after && wsConsumeWs(BEFORE);
if(!after && !before) error(INCOMPLETE);
}
final Expr trg = check(single(), INCOMPLETE);
ctx.updating = true;
return new Insert(input(), s, first, last, before, after, trg);
}
/**
* Parses the "DeleteExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr deletee() throws QueryException {
final int p = qp;
if(!wsConsumeWs(DELETE) || !wsConsumeWs(NODES) && !wsConsumeWs(NODE)) {
qp = p;
return null;
}
ctx.updating = true;
return new Delete(input(), check(single(), INCOMPLETE));
}
/**
* Parses the "RenameExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr rename() throws QueryException {
final int p = qp;
if(!wsConsumeWs(RENAME) || !wsConsumeWs(NODE)) {
qp = p;
return null;
}
final Expr trg = check(single(), INCOMPLETE);
wsCheck(AS);
final Expr n = check(single(), INCOMPLETE);
ctx.updating = true;
return new Rename(input(), trg, n);
}
/**
* Parses the "ReplaceExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr replace() throws QueryException {
final int p = qp;
if(!wsConsumeWs(REPLACE)) return null;
final boolean v = wsConsumeWs(VALUEE);
if(v) {
wsCheck(OF);
wsCheck(NODE);
} else if(!wsConsumeWs(NODE)) {
qp = p;
return null;
}
final Expr t = check(single(), INCOMPLETE);
wsCheck(WITH);
final Expr r = check(single(), INCOMPLETE);
ctx.updating = true;
return new Replace(input(), t, r, v);
}
/**
* Parses the "TransformExpr" rule.
* @return query expression
* @throws QueryException query exception
*/
private Expr transform() throws QueryException {
if(!wsConsumeWs(COPY, DOLLAR, INCOMPLETE)) return null;
final boolean u = ctx.updating;
ctx.updating = false;
final int s = ctx.vars.size();
Let[] fl = { };
do {
final Var v = Var.create(ctx, input(), varName());
wsCheck(ASSIGN);
final Expr e = check(single(), INCOMPLETE);
ctx.vars.add(v);
fl = Array.add(fl, new Let(input(), e, v));
} while(wsConsumeWs(COMMA));
wsCheck(MODIFY);
final Expr m = check(single(), INCOMPLETE);
wsCheck(RETURN);
final Expr r = check(single(), INCOMPLETE);
ctx.vars.size(s);
ctx.updating = u;
return new Transform(input(), fl, m, r);
}
/**
* Parses the "NCName" rule.
* @param err optional error message
* @return string
* @throws QueryException query exception
*/
private byte[] ncName(final Err err) throws QueryException {
tok.reset();
if(ncName()) return tok.finish();
if(err != null) error(err, tok);
return EMPTY;
}
/**
* Parses the "EQName" rule.
* @param err optional error message. Will be thrown if no EQName is found,
* or ignored if set to {@code null}
* @param def default namespace, or operation mode
* ({@link #URICHECK}, {@link #SKIPCHECK})
* @return string
* @throws QueryException query exception
*/
private QNm eQName(final Err err, final byte[] def) throws QueryException {
final int p = qp;
if(ctx.xquery3 && quote(curr())) {
final byte[] uri = stringLiteral();
if(consume(':')) {
final byte[] name = ncName(null);
if(name.length != 0) {
if(def == URICHECK && uri.length == 0) error(NOURI, name);
return new QNm(name, uri);
}
}
qp = p;
}
final byte[] nm = qName(err);
if(nm.length == 0) return null;
if(def == SKIPCHECK) return new QNm(nm);
// create new EQName and set namespace
final QNm name = new QNm(nm, ctx);
if(!name.hasURI()) {
if(def == URICHECK) error(NSMISS, name);
if(name.hasPrefix()) error(NOURI, name);
name.uri(def);
}
return name;
}
/**
* Parses the "QName" rule.
* @param err optional error message. Will be thrown if no QName is found, and
* ignored if set to {@code null}
* @return string
* @throws QueryException query exception
*/
private byte[] qName(final Err err) throws QueryException {
tok.reset();
if(!ncName()) {
if(err != null) error(err, consume());
} else if(consume(':')) {
if(!XMLToken.isNCStartChar(curr())) {
--qp;
} else {
tok.add(':');
do {
tok.add(consume());
} while(XMLToken.isNCChar(curr()));
}
}
return tok.finish();
}
/**
* Helper method for parsing NCNames.
* @return true for success
*/
private boolean ncName() {
if(!XMLToken.isNCStartChar(curr())) return false;
do {
tok.add(consume());
} while(XMLToken.isNCChar(curr()));
return true;
}
/**
* Parses and converts entities.
* @param tb token builder
* @return true if an entity was found
* @throws QueryException query exception
*/
private boolean entity(final TokenBuilder tb) throws QueryException {
final int p = qp;
final boolean ent = consume('&');
if(ent) {
if(consume('#')) {
final int b = consume('x') ? 16 : 10;
int n = 0;
do {
final char c = curr();
final boolean m = digit(c);
final boolean h = b == 16
&& (c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F');
if(!m && !h) entityError(p, INVENTITY);
final long nn = n;
n = n * b + (consume() & 15);
if(n < nn) entityError(p, INVCHARREF);
if(!m) n += 9;
} while(!consume(';'));
if(!XMLToken.valid(n)) entityError(p, INVCHARREF);
tb.add(n);
} else {
if(consume("lt")) {
tb.add('<');
} else if(consume("gt")) {
tb.add('>');
} else if(consume("amp")) {
tb.add('&');
} else if(consume("quot")) {
tb.add('"');
} else if(consume("apos")) {
tb.add('\'');
} else {
entityError(p, INVENTITY);
}
if(!consume(';')) entityError(p, INVENTITY);
}
} else {
final char c = consume();
int ch = c;
if(Character.isHighSurrogate(c) && curr() != 0
&& Character.isLowSurrogate(curr())) {
ch = Character.toCodePoint(c, consume());
}
if(ch == '\r') {
ch = '\n';
if(curr(ch)) consume();
}
tb.add(ch);
}
return ent;
}
/**
* Raises an entity error.
* @param p start position
* @param c error code
* @throws QueryException query exception
*/
private void entityError(final int p, final Err c) throws QueryException {
final String sub = query.substring(p, Math.min(p + 20, ql));
final int sc = sub.indexOf(';');
final String ent = sc != -1 ? sub.substring(0, sc + 1) : sub;
error(c, ent);
}
/**
* Returns an IO instance for the specified file, or {@code null}.
* @param fn filename
* @return io instance
*/
private IO io(final String fn) {
final IO io = IO.get(fn);
if(io.exists()) return io;
// append with base uri
final IO base = ctx.sc.baseIO();
if(base != null) {
final IO io2 = base.merge(fn);
if(!io2.eq(io) && io2.exists()) return io2;
}
// append with query directory
if(file != null) {
final IO io2 = file.merge(fn);
if(!io2.eq(io) && io2.exists()) return io2;
}
return io;
}
/**
* Raises an error if the specified expression is empty.
* @param <E> expression type
* @param expr expression
* @param err error message
* @return expression
* @throws QueryException query exception
*/
private <E extends Expr> E check(final E expr, final Err err)
throws QueryException {
if(expr == null) error(err);
return expr;
}
/**
* Raises an error if the specified character cannot be consumed.
* @param ch character to be found
* @throws QueryException query exception
*/
private void check(final int ch) throws QueryException {
if(!consume(ch)) error(WRONGCHAR, (char) ch, found());
}
/**
* Skips whitespaces, raises an error if the specified string cannot be
* consumed.
* @param s string to be found
* @throws QueryException query exception
*/
private void wsCheck(final String s) throws QueryException {
if(!wsConsume(s)) error(WRONGCHAR, s, found());
}
/**
* Checks if a referenced variable is defined and throws the specified error
* if not.
* @param name variable name
* @param err error to throw
* @return referenced variable
* @throws QueryException if the variable isn't defined
*/
private Var checkVar(final QNm name, final Err err) throws QueryException {
Var v = ctx.vars.get(name);
// dynamically assign variables from function modules
if(v == null && !declVars) {
declVars = true;
Variable.init(ctx);
v = ctx.vars.get(name);
}
if(v == null) error(err, '$' + string(name.string()));
return v;
}
/**
* Checks if the specified character is not found. An error is raised if the
* input is exhausted.
* @param ch character to be found
* @return result of check
* @throws QueryException query exception
*/
private boolean not(final char ch) throws QueryException {
final char c = curr();
if(c == 0) error(WRONGCHAR, ch, found());
return c != ch;
}
/**
* Consumes the specified token and surrounding whitespaces.
* @param t token to consume
* @return true if token was found
* @throws QueryException query exception
*/
private boolean wsConsumeWs(final String t) throws QueryException {
final int p = qp;
if(!wsConsume(t)) return false;
if(skipWS() || !XMLToken.isNCStartChar(t.charAt(0))
|| !XMLToken.isNCChar(curr())) return true;
qp = p;
return false;
}
/**
* Consumes the specified two strings or jumps back to the old query position.
* If the strings are found, the cursor is placed after the first token.
* @param s1 string to be consumed
* @param s2 second string
* @param expr alternative error message
* @return result of check
* @throws QueryException query exception
*/
private boolean wsConsumeWs(final String s1, final String s2, final Err expr)
throws QueryException {
final int p = qp;
if(!wsConsumeWs(s1)) return false;
alter = expr;
ap = qp;
final int p2 = qp;
final boolean ok = wsConsume(s2);
qp = ok ? p2 : p;
return ok;
}
/**
* Skips whitespaces, consumes the specified string and ignores trailing
* characters.
* @param str string to consume
* @return true if string was found
* @throws QueryException query exception
*/
private boolean wsConsume(final String str) throws QueryException {
skipWS();
return consume(str);
}
/**
* Consumes all whitespace characters from the remaining query.
* @return true if whitespaces were found
* @throws QueryException query exception
*/
private boolean skipWS() throws QueryException {
final int p = qp;
while(more()) {
final int c = curr();
if(c == '(' && next() == ':') {
comment();
} else {
if(c <= 0 || c > ' ') return p != qp;
++qp;
}
}
return p != qp;
}
/**
* Consumes a comment.
* @throws QueryException query exception
*/
private void comment() throws QueryException {
++qp;
while(++qp < ql) {
if(curr('(') && next() == ':') comment();
if(curr(':') && next() == ')') {
qp += 2;
return;
}
}
error(COMCLOSE);
}
/**
* Consumes all following whitespace characters.
* @return true if whitespaces were found
*/
private boolean consumeWS() {
final int p = qp;
while(more()) {
final int c = curr();
if(c <= 0 || c > ' ') return p != qp;
++qp;
}
return true;
}
/**
* Throws the alternative error message.
* @throws QueryException query exception
*/
private void error() throws QueryException {
qp = ap;
if(alter != FUNCUNKNOWN) throw error(alter);
ctx.funcs.funError(alterFunc, input());
throw error(alter, alterFunc.string());
}
/**
* Adds an expression to the specified array.
* @param ar input array
* @param e new expression
* @return new array
* @throws QueryException query exception
*/
private Expr[] add(final Expr[] ar, final Expr e) throws QueryException {
if(e == null) error(INCOMPLETE);
final int a = ar.length;
final Expr[] tmp = new Expr[a + 1];
System.arraycopy(ar, 0, tmp, 0, a);
tmp[a] = e;
return tmp;
}
/**
* Throws the specified error.
* @param err error to be thrown
* @param arg error arguments
* @return never
* @throws QueryException query exception
*/
public QueryException error(final Err err, final Object... arg)
throws QueryException {
throw err.thrw(input(), arg);
}
/**
* Finalizes the QNames by assigning namespace URIs.
* @param npos first entry to be checked
* @throws QueryException query exception
*/
private void assignURI(final int npos) throws QueryException {
for(int i = npos; i < names.size(); i++) {
if(names.get(i).assign(npos == 0)) names.delete(i--);
}
}
/**
* Cache for checking QNames after their construction.
*/
private class QNmCheck {
/** QName to be checked. */
final QNm name;
/** Flag for assigning default element namespace. */
final boolean nsElem;
/**
* Constructor.
* @param nm qname
*/
QNmCheck(final QNm nm) {
this(nm, true);
}
/**
* Constructor.
* @param nm qname
* @param nse default check
*/
QNmCheck(final QNm nm, final boolean nse) {
name = nm;
nsElem = nse;
}
/**
* Assigns the namespace URI that is currently in scope.
* @param check check if prefix URI was assigned
* @return true if URI has a URI
* @throws QueryException query exception
*/
boolean assign(final boolean check) throws QueryException {
if(name.hasURI()) return true;
if(name.hasPrefix()) {
name.uri(ctx.sc.ns.uri(name.prefix()));
if(check && !name.hasURI()) error(NOURI, name);
} else if(nsElem) {
name.uri(ctx.sc.nsElem);
}
return name.hasURI();
}
}
}