package org.basex.query.expr;
import static org.basex.query.util.Err.*;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.item.ANode;
import org.basex.query.item.FAttr;
import org.basex.query.item.FTxt;
import org.basex.query.item.Item;
import org.basex.query.item.NodeType;
import org.basex.query.item.QNm;
import org.basex.query.item.Type;
import org.basex.query.iter.AxisIter;
import org.basex.query.iter.Iter;
import org.basex.query.iter.NodeCache;
import org.basex.util.Atts;
import org.basex.util.InputInfo;
import org.basex.util.Token;
import org.basex.util.TokenBuilder;
/**
* Element constructor.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class Constr {
/** Node array. */
public final NodeCache children = new NodeCache();
/** Attribute array. */
public final NodeCache atts = new NodeCache();
/** Namespace array. */
public final Atts nspaces = new Atts();
/** Error: attribute position. */
public boolean errAtt;
/** Error: namespace position. */
public boolean errNS;
/** Error: duplicate attribute. */
public byte[] duplAtt;
/** Error: duplicate namespace. */
public byte[] duplNS;
/** Query context. */
private final QueryContext ctx;
/** Input information. */
private final InputInfo input;
/** Text cache. */
private final TokenBuilder text = new TokenBuilder();
/** Space separator flag. */
private boolean more;
/**
* Creates the children of the constructor.
* @param ii input info
* @param qc query context
*/
public Constr(final InputInfo ii, final QueryContext qc) {
input = ii;
ctx = qc;
}
/**
* Constructs child and attribute nodes.
* @param expr input expressions
* @return self reference
* @throws QueryException query exception
*/
public Constr add(final Expr... expr) throws QueryException {
final int s = ctx.sc.ns.size();
try {
for(final Expr e : expr) {
more = false;
final Iter iter = ctx.iter(e);
for(Item ch; (ch = iter.next()) != null && add(ch););
}
if(text.size() != 0) children.add(new FTxt(text.finish()));
return this;
} finally {
ctx.sc.ns.size(s);
}
}
/**
* Recursively adds nodes to the element arrays. Recursion is necessary
* as documents are resolved to their child nodes.
* @param it current item
* @return true if item was added
* @throws QueryException query exception
*/
private boolean add(final Item it) throws QueryException {
final Type ip = it.type;
if(ip.isFunction()) CONSFUNC.thrw(input, it);
if(!ip.isNode()) {
// type: atomic value
if(more) text.add(' ');
text.add(it.string(input));
more = true;
} else {
// type: nodes
ANode node = (ANode) it;
if(ip == NodeType.TXT) {
// type: text node
text.add(node.string());
} else if(ip == NodeType.ATT) {
// type: attribute node
// no attribute allowed after texts or child nodes
if(text.size() != 0 || children.size() != 0) {
errAtt = true;
return false;
}
// check for duplicate attribute names
final QNm name = node.qname();
for(int a = 0; a < atts.size(); ++a) {
if(name.eq(atts.get(a).qname())) {
duplAtt = name.string();
return false;
}
}
// add attribute
atts.add(new FAttr(name, node.string()));
if(name.hasURI()) {
ctx.sc.ns.add(name.prefix(), name.uri());
}
} else if(ip == NodeType.NSP) {
// type: namespace node
// no attribute allowed after texts or child nodes
if(text.size() != 0 || children.size() != 0) {
errNS = true;
return false;
}
// add namespace
final byte[] name = node.name();
final byte[] uri = node.string();
final byte[] u = nspaces.string(name);
if(u == null) {
nspaces.add(name, uri);
} else if(!Token.eq(uri, u)) {
// duplicate namespace (ignore duplicates with same uri)
duplNS = name;
return false;
}
} else if(ip == NodeType.DOC) {
// type: document node
final AxisIter ai = node.children();
for(ANode ch; (ch = ai.next()) != null && add(ch););
} else {
// type: element/comment/processing instruction node
// add text node
if(text.size() != 0) {
children.add(new FTxt(text.finish()));
text.reset();
}
// [CG] Element construction: avoid full copy of sub tree if not needed
node = node.deepCopy();
children.add(node);
}
more = false;
}
return true;
}
}