package org.basex.query.item; import static org.basex.query.QueryText.*; import static org.basex.util.Token.*; import java.io.IOException; import org.basex.io.serial.Serializer; import org.basex.query.iter.AxisMoreIter; import org.basex.query.iter.NodeCache; import org.basex.util.Atts; import org.basex.util.Token; import org.basex.util.TokenBuilder; import org.basex.util.hash.TokenMap; import org.w3c.dom.Attr; import org.w3c.dom.Comment; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; /** * Element node fragment. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class FElem extends FNode { /** Tag name. */ private final QNm name; /** Child nodes. */ private NodeCache children; /** Attributes. */ private NodeCache atts; /** Namespaces. */ private Atts ns; /** * Constructor. * @param n tag name */ public FElem(final QNm n) { this(n, null); } /** * Constructor. * @param n tag name * @param nsp namespaces */ public FElem(final QNm n, final Atts nsp) { this(n, null, null, nsp); } /** * Constructor. * @param n tag name * @param ch children; can be {@code null} * @param at attributes; can be {@code null} * @param nsp namespaces; can be {@code null} */ public FElem(final QNm n, final NodeCache ch, final NodeCache at, final Atts nsp) { super(NodeType.ELM); name = n; children = ch; atts = at; ns = nsp; } /** * Constructor for DOM nodes. * Originally provided by Erdal Karaca. * @param elem DOM node * @param p parent reference * @param nss namespaces in scope */ public FElem(final Element elem, final ANode p, final TokenMap nss) { super(NodeType.ELM); // general stuff final String nu = elem.getNamespaceURI(); name = new QNm(token(elem.getNodeName()), nu == null ? EMPTY : token(nu)); par = p; ns = new Atts(); // attributes and namespaces final NamedNodeMap at = elem.getAttributes(); final int as = at.getLength(); for(int i = 0; i < as; ++i) { final Attr att = (Attr) at.item(i); final byte[] nm = token(att.getName()), uri = token(att.getValue()); if(Token.eq(nm, XMLNS)) { ns.add(EMPTY, uri); } else if(startsWith(nm, XMLNSC)) { ns.add(local(nm), uri); } else { add(new FAttr(att)); } } // add all new namespaces for(int i = 0; i < ns.size(); ++i) nss.add(ns.name(i), ns.string(i)); // no parent, so we have to add all namespaces in scope if(p == null) { nsScope(elem.getParentNode(), nss); for(final byte[] pref : nss.keys()) { if(!ns.contains(pref)) ns.add(pref, nss.get(pref)); } } final byte[] pref = name.prefix(); final byte[] uri = name.uri(); final byte[] old = nss.get(pref); if(old == null || !Token.eq(uri, old)) { ns.add(pref, uri); nss.add(pref, uri); } // children final NodeList ch = elem.getChildNodes(); for(int i = 0; i < ch.getLength(); ++i) { final Node child = ch.item(i); switch(child.getNodeType()) { case Node.TEXT_NODE: add(new FTxt((Text) child)); break; case Node.COMMENT_NODE: add(new FComm((Comment) child)); break; case Node.PROCESSING_INSTRUCTION_NODE: add(new FPI((ProcessingInstruction) child)); break; case Node.ELEMENT_NODE: add(new FElem((Element) child, this, nss)); break; default: break; } } optimize(); } /** * Gathers all defined namespaces in the scope of the given DOM element. * @param elem DOM element * @param nss map */ private static void nsScope(final Node elem, final TokenMap nss) { Node n = elem; // only elements can declare namespaces while(n != null && n instanceof Element) { final NamedNodeMap atts = n.getAttributes(); final byte[] pref = token(n.getPrefix()); if(nss.get(pref) != null) nss.add(pref, token(n.getNamespaceURI())); for(int i = 0, len = atts.getLength(); i < len; ++i) { final Attr a = (Attr) atts.item(i); final byte[] name = token(a.getName()), val = token(a.getValue()); if(Token.eq(name, XMLNS)) { // default namespace if(nss.get(EMPTY) == null) nss.add(EMPTY, val); } else if(startsWith(name, XMLNS)) { // prefixed namespace final byte[] ln = local(name); if(nss.get(ln) == null) nss.add(ln, val); } } n = n.getParentNode(); } } @Override public FElem optimize() { if(children != null && children.size() == 0) children = null; if(atts != null && atts.size() == 0) atts = null; if(ns != null && ns.size() == 0) ns = null; return this; } /** * Adds a node to this node. * @param node node to be added * @return self reference */ public FElem add(final ANode node) { final NodeCache nc; if(node.type == NodeType.ATT) { if(atts == null) atts = new NodeCache(); nc = atts; } else { if(children == null) children = new NodeCache(); nc = children; } nc.add(node); node.parent(this); return this; } @Override public Atts namespaces() { if(ns == null) ns = new Atts(); return ns; } @Override public byte[] string() { return children == null ? EMPTY : string(children); } @Override public byte[] baseURI() { final byte[] b = attribute(new QNm(BASE, XMLURI)); return b != null ? b : EMPTY; } @Override public QNm qname() { return name; } @Override public byte[] name() { return name.string(); } @Override public AxisMoreIter attributes() { return atts != null ? iter(atts) : super.attributes(); } @Override public AxisMoreIter children() { return children != null ? iter(children) : super.children(); } @Override public boolean hasChildren() { return children != null && children.size() != 0; } @Override public void serialize(final Serializer ser) throws IOException { ser.openElement(name.string()); // serialize namespaces if(ns != null) { for(int p = ns.size() - 1; p >= 0; p--) { ser.namespace(ns.name(p), ns.string(p)); } } // serialize attributes if(atts != null) { for(int n = 0; n < atts.size(); ++n) { final ANode node = atts.get(n); ser.attribute(node.name(), node.string()); } } // serialize children if(children != null) { for(int n = 0; n < children.size(); ++n) children.get(n).serialize(ser); } ser.closeElement(); } @Override public FNode copy() { final FElem node = new FElem(name); if(ns != null) { node.ns = new Atts(); for(int n = 0; n < ns.size(); ++n) node.ns.add(ns.name(n), ns.string(n)); } if(atts != null) { for(int a = 0; a < atts.size(); ++a) node.add(atts.get(a).copy()); } if(children != null) { for(int c = 0; c < children.size(); ++c) node.add(children.get(c).copy()); } return node.parent(par); } @Override public void plan(final Serializer ser) throws IOException { ser.emptyElement(this, NAM, name.string()); } @Override public String toString() { final TokenBuilder tb = new TokenBuilder().add('<').add(name.string()); if(ns != null) { for(int n = 0; n < ns.size(); n++) { tb.add(new FNames(ns.name(n), ns.string(n)).toString()); } } if(atts != null) { for(int a = 0; a < atts.size(); a++) tb.add(atts.get(a).toString()); } if(hasChildren()) tb.add(">...</").add(name.string()); else tb.add("/"); return tb.add(">").toString(); } }