/* * eXist Open Source Native XML Database * Copyright (C) 2001-2014 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id$ */ package org.exist.dom.memtree; import org.exist.Namespaces; import org.exist.dom.NamedNodeMapImpl; import org.exist.dom.NodeListImpl; import org.exist.dom.QName; import org.exist.xmldb.XmldbURI; import org.exist.xquery.NodeTest; import org.exist.xquery.XPathException; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.Type; import org.exist.xquery.value.ValueSequence; import org.w3c.dom.Attr; import org.w3c.dom.DOMException; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.w3c.dom.TypeInfo; import javax.xml.XMLConstants; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class ElementImpl extends NodeImpl implements Element { public ElementImpl(final DocumentImpl doc, final int nodeNumber) { super(doc, nodeNumber); } @Override public String getTagName() { return getNodeName(); } @Override public boolean hasChildNodes() { return (nodeNumber + 1) < document.size && document.treeLevel[nodeNumber + 1] > document.treeLevel[nodeNumber]; } @Override public Node getFirstChild() { final short level = document.treeLevel[nodeNumber]; final int nextNode = nodeNumber + 1; if(nextNode < document.size && document.treeLevel[nextNode] > level) { return document.getNode(nextNode); } return null; } @Override public NodeList getChildNodes() { final NodeListImpl nl = new NodeListImpl(); int nextNode = document.getFirstChildFor(nodeNumber); while(nextNode > nodeNumber) { final Node n = document.getNode(nextNode); nl.add(n); nextNode = document.next[nextNode]; } return nl; } private int getChildCount() { return document.getChildCountFor(nodeNumber); } @Override public boolean hasAttributes() { return document.alpha[nodeNumber] > -1 || document.alphaLen[nodeNumber] > -1; } @Override public String getAttribute(final String name) { int attr = document.alpha[nodeNumber]; if(-1 < attr) { while(attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { final QName attrQName = document.attrName[attr]; if(attrQName.getStringValue().equals(name)) { return document.attrValue[attr]; } ++attr; } } if(name.startsWith(XMLConstants.XMLNS_ATTRIBUTE + ":")) { int ns = document.alphaLen[nodeNumber]; if(-1 < ns) { while(ns < document.nextNamespace && document.namespaceParent[ns] == nodeNumber) { final QName nsQName = document.namespaceCode[ns]; if(nsQName.getStringValue().equals(name)) { return nsQName.getNamespaceURI(); } ++ns; } } } return null; } @Override public void setAttribute(final String name, final String value) throws DOMException { try { final int lastNode = document.getLastNode(); final QName qname = QName.parse(document.context, name); document.addAttribute(lastNode, qname, value, AttrImpl.ATTR_CDATA_TYPE); } catch(final XPathException e) { throw new DOMException(DOMException.SYNTAX_ERR, e.getMessage()); } } @Override public void removeAttribute(final String name) throws DOMException { } @Override public NamedNodeMap getAttributes() { final NamedNodeMapImpl map = new NamedNodeMapImpl(); int attr = document.alpha[nodeNumber]; if(-1 < attr) { while(attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { map.setNamedItem(new AttrImpl(document, attr)); ++attr; } } // add namespace declarations attached to this element int ns = document.alphaLen[nodeNumber]; if(ns < 0) { return (map); } while(ns < document.nextNamespace && document.namespaceParent[ns] == nodeNumber) { final NamespaceNode node = new NamespaceNode(document, ns); map.setNamedItem(node); ++ns; } return map; } @Override public Attr getAttributeNode(final String name) { int attr = document.alpha[nodeNumber]; if(-1 < attr) { while(attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { final QName attrQName = document.attrName[attr]; if(attrQName.getStringValue().equals(name)) { return new AttrImpl(document, attr); } ++attr; } } if(name.startsWith(XMLConstants.XMLNS_ATTRIBUTE + ":")) { int ns = document.alphaLen[nodeNumber]; if(-1 < ns) { while(ns < document.nextNamespace && document.namespaceParent[ns] == nodeNumber) { final QName nsQName = document.namespaceCode[ns]; if(nsQName.getStringValue().equals(name)) { return new NamespaceNode(document, ns); } ++ns; } } } return null; } @Override public Attr setAttributeNode(final Attr newAttr) throws DOMException { return null; } @Override public Attr removeAttributeNode(final Attr oldAttr) throws DOMException { return null; } @Override public void selectAttributes(final NodeTest test, final Sequence result) throws XPathException { int attr = document.alpha[nodeNumber]; if(-1 < attr) { while(attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { final AttrImpl attrib = new AttrImpl(document, attr); if(test.matches(attrib)) { result.add(attrib); } ++attr; } } } @Override public void selectDescendantAttributes(final NodeTest test, final Sequence result) throws XPathException { final int treeLevel = document.treeLevel[nodeNumber]; int nextNode = nodeNumber; NodeImpl n = document.getNode(nextNode); n.selectAttributes(test, result); while(++nextNode < document.size && document.treeLevel[nextNode] > treeLevel) { n = document.getNode(nextNode); if(n.getNodeType() == Node.ELEMENT_NODE) { n.selectAttributes(test, result); } } } @Override public void selectChildren(final NodeTest test, final Sequence result) throws XPathException { int nextNode = document.getFirstChildFor(nodeNumber); while(nextNode > nodeNumber) { final NodeImpl n = document.getNode(nextNode); if(test.matches(n)) { result.add(n); } nextNode = document.next[nextNode]; } } public NodeImpl getFirstChild(final NodeTest test) throws XPathException { final ValueSequence seq = new ValueSequence(); selectChildren(test, seq); return seq.isEmpty() ? null : seq.get(0); } @Override public void selectDescendants(final boolean includeSelf, final NodeTest test, final Sequence result) throws XPathException { final int treeLevel = document.treeLevel[nodeNumber]; int nextNode = nodeNumber; if(includeSelf) { final NodeImpl n = document.getNode(nextNode); if(test.matches(n)) { result.add(n); } } while(++nextNode < document.size && document.treeLevel[nextNode] > treeLevel) { final NodeImpl n = document.getNode(nextNode); if(test.matches(n)) { result.add(n); } } } @Override public NodeList getElementsByTagName(final String name) { final NodeListImpl nl = new NodeListImpl(); int nextNode = nodeNumber; final int treeLevel = document.treeLevel[nodeNumber]; while(++nextNode < document.size && document.treeLevel[nextNode] > treeLevel) { if(document.nodeKind[nextNode] == Node.ELEMENT_NODE) { final QName qn = document.nodeName[nextNode]; if(qn.getStringValue().equals(name)) { nl.add(document.getNode(nextNode)); } } } return nl; } @Override public String getAttributeNS(final String namespaceURI, final String localName) { int attr = document.alpha[nodeNumber]; if(-1 < attr) { while(attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { final QName name = document.attrName[attr]; if(name.getLocalPart().equals(localName) && name.getNamespaceURI().equals(namespaceURI)) { return document.attrValue[attr]; } ++attr; } } if(Namespaces.XMLNS_NS.equals(namespaceURI)) { int ns = document.alphaLen[nodeNumber]; if(-1 < ns) { while(ns < document.nextNamespace && document.namespaceParent[ns] == nodeNumber) { final QName nsQName = document.namespaceCode[ns]; if(nsQName.getLocalPart().equals(localName)) { return nsQName.getNamespaceURI(); } ++ns; } } } return null; } @Override public void setAttributeNS(final String namespaceURI, final String qualifiedName, final String value) throws DOMException { } @Override public void removeAttributeNS(final String namespaceURI, final String localName) throws DOMException { } @Override public Attr getAttributeNodeNS(final String namespaceURI, final String localName) { int attr = document.alpha[nodeNumber]; if(-1 < attr) { while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) { final QName name = document.attrName[attr]; if(name.getLocalPart().equals(localName) && name.getNamespaceURI().equals(namespaceURI)) { return (new AttrImpl(document, attr)); } ++attr; } } if(Namespaces.XMLNS_NS.equals(namespaceURI)) { int ns = document.alphaLen[nodeNumber]; if(-1 < ns) { while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nodeNumber)) { final QName nsQName = document.namespaceCode[ns]; if(nsQName.getLocalPart().equals(localName)) { return (new NamespaceNode(document, ns)); } ++ns; } } } return null; } @Override public Attr setAttributeNodeNS(final Attr newAttr) throws DOMException { return null; } @Override public NodeList getElementsByTagNameNS(final String namespaceURI, final String name) { final QName qname = new QName(name, namespaceURI); final NodeListImpl nl = new NodeListImpl(); int nextNode = nodeNumber; while(++nextNode < document.size) { if(document.nodeKind[nextNode] == Node.ELEMENT_NODE) { final QName qn = document.nodeName[nextNode]; if(qname.compareTo(qn) == 0) { nl.add(document.getNode(nextNode)); } } if(document.next[nextNode] <= nodeNumber) { break; } } return nl; } @Override public boolean hasAttribute(final String name) { return getAttribute(name) != null; } @Override public boolean hasAttributeNS(final String namespaceURI, final String localName) { return getAttributeNS(namespaceURI, localName) != null; } /** * The method <code>getPrefixes.</code> * * @return a <code>Set</code> value */ public Set<String> getPrefixes() { final Set<String> set = new HashSet<>(); int ns = document.alphaLen[nodeNumber]; if(-1 < ns) { while(ns < document.nextNamespace && document.namespaceParent[ns] == nodeNumber) { final QName nsQName = document.namespaceCode[ns]; set.add(nsQName.getStringValue()); ++ns; } } return set; } /** * The method <code>declaresNamespacePrefixes.</code> * * @return a <code>boolean</code> value */ public boolean declaresNamespacePrefixes() { return document.getNamespacesCountFor(nodeNumber) > 0; } /** * The method <code>getNamespaceMap.</code> * * @return a <code>Map</code> value */ public Map<String, String> getNamespaceMap() { return getNamespaceMap(new HashMap<String, String>()); } public Map<String, String> getNamespaceMap(final Map<String, String> map) { int ns = document.alphaLen[nodeNumber]; if(-1 < ns) { while(ns < document.nextNamespace && document.namespaceParent[ns] == nodeNumber) { final QName nsQName = document.namespaceCode[ns]; map.put(nsQName.getLocalPart(), nsQName.getNamespaceURI()); ++ns; } } int attr = document.alpha[nodeNumber]; if(-1 < attr) { while(attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { final QName qname = document.attrName[attr]; if(qname.getPrefix() != null && !qname.getPrefix().isEmpty()) { map.put(qname.getPrefix(), qname.getNamespaceURI()); } ++attr; } } return map; } @Override public int getItemType() { return Type.ELEMENT; } @Override public String getBaseURI() { final XmldbURI baseURI = calculateBaseURI(); if(baseURI != null) { return baseURI.toString(); } return "";//UNDERSTAND: is it ok? } //TODO please, keep in sync with org.exist.dom.persistent.ElementImpl private XmldbURI calculateBaseURI() { XmldbURI baseURI = null; final String nodeBaseURI = getAttributeNS(Namespaces.XML_NS, "base"); if(nodeBaseURI != null) { baseURI = XmldbURI.create(nodeBaseURI, false); if(baseURI.isAbsolute()) { return baseURI; } } int parent = -1; final int test = document.getParentNodeFor(nodeNumber); if(document.nodeKind[test] != Node.DOCUMENT_NODE) { parent = test; } if(parent != -1) { if(nodeBaseURI == null) { baseURI = ((ElementImpl) document.getNode(parent)) .calculateBaseURI(); } else { XmldbURI parentsBaseURI = ((ElementImpl) document.getNode(parent)) .calculateBaseURI(); if(nodeBaseURI.isEmpty()) { baseURI = parentsBaseURI; } else { baseURI = parentsBaseURI.append(baseURI); } } } else { if(nodeBaseURI == null) { return XmldbURI.create(getOwnerDocument().getBaseURI(), false); } else if(nodeNumber == 1) { //nothing to do } else { final String docBaseURI = getOwnerDocument().getBaseURI(); if(docBaseURI.endsWith("/")) { baseURI = XmldbURI.create(getOwnerDocument().getBaseURI(), false); baseURI.append(baseURI); } else { baseURI = XmldbURI.create(getOwnerDocument().getBaseURI(), false); baseURI = baseURI.removeLastSegment(); baseURI.append(baseURI); } } } return baseURI; } @Override public TypeInfo getSchemaTypeInfo() { return null; } @Override public void setIdAttribute(final String name, final boolean isId) throws DOMException { } @Override public void setIdAttributeNS(final String namespaceURI, final String localName, final boolean isId) throws DOMException { } @Override public void setIdAttributeNode(final Attr idAttr, final boolean isId) throws DOMException { } @Override public void setTextContent(final String textContent) throws DOMException { final int nodeNr = document.addNode(Node.TEXT_NODE, (short) (document.getTreeLevel(nodeNumber) + 1), null); document.addChars(nodeNr, textContent.toCharArray(), 0, textContent.length()); } @Override public String toString() { final StringBuilder result = new StringBuilder(); result.append("in-memory#"); result.append("element {"); result.append(getQName().getStringValue()); result.append("} {"); final NamedNodeMap theAttrs = getAttributes(); if(theAttrs != null) { for(int i = 0; i < theAttrs.getLength(); i++) { if(i > 0) { result.append(" "); } final Node natt = theAttrs.item(i); result.append(natt.toString()); } } for(int i = 0; i < this.getChildCount(); i++) { if(i > 0) { result.append(" "); } final Node child = getChildNodes().item(i); result.append(child.toString()); } result.append("} "); return result.toString(); } @Override public String getNodeValue() throws DOMException { final StringBuilder result = new StringBuilder(); for(int i = 0; i < this.getChildCount(); i++) { final Node child = getChildNodes().item(i); if(child instanceof Text) { if(i > 0) { result.append(" "); } result.append(((Text) child).getData()); } } return result.toString(); } }