package org.basex.query.expr; 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.item.ANode; import org.basex.query.item.FAttr; import org.basex.query.item.FElem; import org.basex.query.item.NodeType; import org.basex.query.item.QNm; import org.basex.query.iter.AxisIter; import org.basex.util.Atts; import org.basex.util.InputInfo; /** * Element constructor. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class CElem extends CName { /** Namespaces. */ private final Atts nspaces; /** Computed constructor flag. */ private final boolean comp; /** * Constructor. * @param ii input info * @param t tag * @param ns namespaces, or {@code null} if this is a computed constructor. * @param cont element contents */ public CElem(final InputInfo ii, final Expr t, final Atts ns, final Expr... cont) { super(ELEMENT, ii, t, cont); nspaces = ns == null ? new Atts() : ns; comp = ns == null; } @Override public CElem comp(final QueryContext ctx) throws QueryException { final int s = prepare(ctx); super.comp(ctx); ctx.sc.ns.size(s); return this; } @Override public FElem item(final QueryContext ctx, final InputInfo ii) throws QueryException { final int s = prepare(ctx); try { // adds in-scope namespaces final Atts ns = new Atts(); for(int i = 0; i < nspaces.size(); ++i) { ns.add(nspaces.name(i), nspaces.string(i)); } // create and check QName final QNm nm = qname(ctx, ii); final byte[] cp = nm.prefix(), cu = nm.uri(); if(eq(cp, XML) ^ eq(cu, XMLURI)) CEXML.thrw(input, cu, cp); if(eq(cu, XMLNSURI)) CEINV.thrw(input, cu); if(eq(cp, XMLNS)) CEINV.thrw(input, cp); // analyze element namespace unless it is "xml" if(!eq(cp, XML)) { // request namespace for the specified uri final byte[] uri = ctx.sc.ns.uri(cp); // check if element has a namespace if(nm.hasURI()) { // add to statically known namespaces if(!comp && (uri == null || !eq(uri, cu))) ctx.sc.ns.add(cp, cu); // add to in-scope namespaces if(!ns.contains(cp)) ns.add(cp, cu); } else { // element has no namespace: assign default uri nm.uri(uri); } } // create child and attribute nodes final Constr constr = new Constr(ii, ctx).add(expr); if(constr.errAtt) NOATTALL.thrw(input); if(constr.errNS) NONSALL.thrw(input); if(constr.duplAtt != null) (comp ? CATTDUPL : ATTDUPL).thrw(input, constr.duplAtt); if(constr.duplNS != null) DUPLNSCONS.thrw(input, constr.duplNS); // create node final FElem node = new FElem(nm, constr.children, constr.atts, ns); // add namespaces from constructor final Atts cns = constr.nspaces; for(int a = 0; a < cns.size(); ++a) { addNS(cns.name(a), cns.string(a), ns); } // update parent references of attributes and add namespaces for(int a = 0; a < constr.atts.size(); ++a) { constr.atts.get(a).parent(node); final ANode att = constr.atts.get(a); final QNm qnm = att.qname(); // skip attributes without prefixes or URIs if(!qnm.hasPrefix() || !qnm.hasURI()) continue; // skip XML namespace final byte[] apref = qnm.prefix(); if(eq(apref, XML)) continue; final byte[] auri = qnm.uri(); final byte[] npref = addNS(apref, auri, ns); if(npref != null) { constr.atts.item[a] = new FAttr( new QNm(concat(npref, COLON, qnm.local()), auri), att.string()); } } // add inherited namespaces final Atts stack = ctx.sc.ns.stack(); for(int a = stack.size() - 1; a >= 0; a--) { final byte[] pref = stack.name(a); if(!ns.contains(pref)) ns.add(pref, stack.string(a)); } // update parent references of children for(int c = 0; c < constr.children.size(); ++c) { final ANode child = constr.children.get(c).parent(node); // add inherited and remove unused namespaces if(child.type == NodeType.ELM) { if(ctx.sc.nsInherit) inherit(child, ns); if(!ctx.sc.nsPreserve) noPreserve(child); child.optimize(); } } // return generated and optimized node return node.optimize(); } finally { ctx.sc.ns.size(s); } } /** * Removes unused namespaces. * @param node to be modified */ private static void noPreserve(final ANode node) { final Atts ns = node.namespaces(); final byte[] pref = node.qname().prefix(); for(int i = ns.size() - 1; i >= 0; i--) { boolean f = eq(ns.name(i), pref); final AxisIter atts = node.attributes(); for(ANode it; f && (it = atts.next()) != null;) { f |= eq(it.qname().prefix(), pref); } if(!f) ns.delete(i); } } /** * Inherits namespaces. * @param node to be modified * @param nsp in-scope namespaces */ private static void inherit(final ANode node, final Atts nsp) { final Atts ns = node.namespaces(); for(int a = nsp.size() - 1; a >= 0; a--) { final byte[] pref = nsp.name(a); if(!ns.contains(pref)) ns.add(pref, nsp.string(a)); } } /** * Adds the specified namespace to the namespace array. * If the prefix is already used for another URI, a new * name is generated. * @param pref prefix * @param uri uri * @param ns namespaces * @return resulting prefix */ private static byte[] addNS(final byte[] pref, final byte[] uri, final Atts ns) { final byte[] u = ns.string(pref); if(u == null) { // add undeclared namespace ns.add(pref, uri); } else if(!eq(u, uri)) { // prefixes with different URIs exist; new one must be replaced byte[] apref = null; // check if one of the existing prefixes can be adopted for(int c = 0; c < ns.size(); c++) { if(eq(ns.string(c), uri)) apref = ns.name(c); } // if negative, generate a new one that is not used yet if(apref == null) { int i = 1; do { apref = concat(pref, new byte[] { '_' }, token(i++)); } while(ns.contains(apref)); ns.add(apref, uri); } return apref; } return null; } /** * Adds namespaces to the namespace stack. * @param ctx query context * @return old stack position */ private int prepare(final QueryContext ctx) { final int s = ctx.sc.ns.size(); for(int n = 0; n < nspaces.size(); n++) { ctx.sc.ns.add(nspaces.name(n), nspaces.string(n)); } return s; } }