package org.basex.query.func;
import static org.basex.query.QueryText.*;
import static org.basex.query.util.Err.*;
import static org.basex.util.Token.*;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Expr;
import org.basex.query.item.Bln;
import org.basex.query.item.FNode;
import org.basex.query.item.Item;
import org.basex.query.item.ANode;
import org.basex.query.item.NodeType;
import org.basex.query.iter.Iter;
import org.basex.query.iter.NodeCache;
import org.basex.query.iter.AxisIter;
import org.basex.util.InputInfo;
import org.basex.util.list.TokenList;
/**
* ID functions.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class FNId extends StandardFunc {
/**
* Constructor.
* @param ii input info
* @param f function definition
* @param e arguments
*/
public FNId(final InputInfo ii, final Function f, final Expr... e) {
super(ii, f, e);
}
@Override
public Iter iter(final QueryContext ctx) throws QueryException {
// functions have 1 or 2 arguments...
final Item it = checkNoEmpty((expr.length == 2 ? expr[1] :
checkCtx(ctx)).item(ctx, input));
final ANode node = checkNode(it);
switch(sig) {
case ID: return id(ctx.iter(expr[0]), node);
case IDREF: return idref(ctx.iter(expr[0]), node);
case ELEMENT_WITH_ID: return elid(ctx.iter(expr[0]), node);
default: return super.iter(ctx);
}
}
@Override
public Item item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
// functions have 1 or 2 arguments...
final Item it = checkNoEmpty((expr.length == 2 ? expr[1] :
checkCtx(ctx)).item(ctx, input));
switch(sig) {
case LANG: return lang(lc(checkEStr(expr[0], ctx)), checkNode(it));
default: return super.item(ctx, ii);
}
}
/**
* Returns the parent result of the ID function.
* @param it item ids to be found
* @param node attribute
* @return resulting node list
* @throws QueryException query exception
*/
private Iter elid(final Iter it, final ANode node) throws QueryException {
return id(it, node);
/*
final NodeCache nc = id(it, node);
final NodeCache res = new NodeCache().random();
for(ANode n; (n = nc.next()) != null;) res.add(n.parent());
return res;
*/
}
/**
* Returns the result of the ID function.
* @param it item ids to be found
* @param node attribute
* @return resulting node list
* @throws QueryException query exception
*/
private NodeCache id(final Iter it, final ANode node) throws QueryException {
final NodeCache nc = new NodeCache().random();
add(ids(it), nc, checkRoot(node));
return nc;
}
/**
* Returns the result of the IDREF function.
* @param it item ids to be found
* @param node attribute
* @return resulting node list
* @throws QueryException query exception
*/
private Iter idref(final Iter it, final ANode node) throws QueryException {
final NodeCache nb = new NodeCache().random();
addRef(ids(it), nb, checkRoot(node));
return nb;
}
/**
* Returns the result of the language function.
* @param lang language to be found
* @param node attribute
* @return resulting node list
*/
private static Bln lang(final byte[] lang, final ANode node) {
for(ANode n = node; n != null; n = n.parent()) {
final AxisIter atts = n.attributes();
for(ANode at; (at = atts.next()) != null;) {
if(eq(at.qname().string(), LANG)) {
final byte[] ln = lc(norm(at.string()));
return Bln.get(startsWith(ln, lang) &&
(lang.length == ln.length || ln[lang.length] == '-'));
}
}
}
return Bln.FALSE;
}
/**
* Extracts the ids from the specified iterator.
* @param iter iterator
* @return ids
* @throws QueryException query exception
*/
private byte[][] ids(final Iter iter) throws QueryException {
final TokenList tl = new TokenList();
for(Item id; (id = iter.next()) != null;) {
for(final byte[] i : split(norm(checkEStr(id)), ' ')) tl.add(i);
}
return tl.toArray();
}
/**
* Adds nodes with the specified id.
* @param ids ids to be found
* @param nc node cache
* @param node node
* @throws QueryException query exception
*/
private static void add(final byte[][] ids, final NodeCache nc,
final ANode node) throws QueryException {
AxisIter ai = node.attributes();
for(ANode at; (at = ai.next()) != null;) {
final byte[] val = at.string();
// [CG] XQuery: ID-IDREF Parsing
for(final byte[] id : ids) {
if(!eq(val, id)) continue;
final byte[] nm = lc(at.qname().string());
if(contains(nm, ID) && !contains(nm, IDREF)) nc.add(node);
}
}
ai = node.children();
for(ANode att; (att = ai.next()) != null;) add(ids, nc, att.finish());
}
/**
* Adds nodes with the specified id.
* @param ids ids to be found
* @param nc node cache
* @param node node
* @throws QueryException query exception
*/
private static void addRef(final byte[][] ids, final NodeCache nc,
final ANode node) throws QueryException {
AxisIter ai = node.attributes();
for(ANode at; (at = ai.next()) != null;) {
final byte[] val = at.string();
// [CG] XQuery: ID-IDREF Parsing
for(final byte[] id : ids) {
if(!eq(val, id)) continue;
final byte[] nm = lc(at.qname().string());
if(contains(nm, IDREF)) nc.add(at.finish());
}
}
ai = node.children();
for(ANode att; (att = ai.next()) != null;) addRef(ids, nc, att.finish());
}
/**
* Checks if the specified node has a document node as root.
* @param node input node
* @return specified node
* @throws QueryException query exception
*/
private ANode checkRoot(final ANode node) throws QueryException {
if(node instanceof FNode) {
ANode n = node;
while(n.type != NodeType.DOC) {
n = n.parent();
if(n == null) throw IDDOC.thrw(input);
}
}
return node;
}
@Override
public boolean uses(final Use u) {
return u == Use.X30 && sig == Function.ELEMENT_WITH_ID ||
u == Use.CTX && expr.length == 1 || super.uses(u);
}
}