/* * eXist Open Source Native XML Database * Copyright (C) 2001-2010 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.Database; import org.exist.EXistException; import org.exist.Namespaces; import org.exist.dom.NodeListImpl; import org.exist.dom.QName; import org.exist.dom.persistent.NodeProxy; import org.exist.numbering.NodeId; import org.exist.numbering.NodeIdFactory; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.storage.ElementValue; import org.exist.storage.serializers.Serializer; import org.exist.util.hashtable.NamePool; import org.exist.util.serializer.AttrList; import org.exist.util.serializer.Receiver; import org.exist.xmldb.XmldbURI; import org.exist.xquery.NodeTest; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.Type; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.DOMConfiguration; import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.EntityReference; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import org.xml.sax.SAXException; import javax.xml.XMLConstants; import java.util.Arrays; /** * An in-memory implementation of Document. * <p/> * <p>This implementation stores all node data in the document object. Nodes from another document, i.e. a persistent document in the database, can be * stored as reference nodes, i.e. the nodes are not copied into this document object. Instead a reference is inserted which will only be expanded * during serialization.</p> * * @author wolf */ public class DocumentImpl extends NodeImpl<DocumentImpl> implements Document { private static final int NODE_SIZE = 16; private static final int ATTR_SIZE = 8; private static final int CHAR_BUF_SIZE = 256; private static final int REF_SIZE = 8; private static long nextDocId = 0; // holds the node type of a node protected short[] nodeKind = null; // the tree level of a node protected short[] treeLevel; // the node number of the next sibling protected int[] next; // pointer into the namePool protected QName[] nodeName; protected NodeId[] nodeId; //alphanumeric content protected int[] alpha; protected int[] alphaLen; protected char[] characters = null; protected int nextChar = 0; // attributes protected QName[] attrName; protected int[] attrType; protected NodeId[] attrNodeId; protected int[] attrParent; protected String[] attrValue; protected int nextAttr = 0; // namespaces protected int[] namespaceParent = null; protected QName[] namespaceCode = null; protected int nextNamespace = 0; // the current number of nodes in the doc protected int size = 1; protected int documentRootNode = -1; protected String documentURI = null; // reference nodes (link to an external, persistent document fragment) protected NodeProxy[] references = null; protected int nextReferenceIdx = 0; // end reference nodes protected XQueryContext context; protected final boolean explicitlyCreated; protected final long docId; private Database db = null; protected NamePool namePool; boolean replaceAttribute = false; public DocumentImpl(final XQueryContext context, final boolean explicitlyCreated) { super(null, 0); this.context = context; this.explicitlyCreated = explicitlyCreated; this.docId = createDocId(); if(context == null) { namePool = new NamePool(); } else { db = context.getDatabase(); namePool = context.getSharedNamePool(); } } private Database getDatabase() { if(db == null) { try { db = BrokerPool.getInstance(); } catch(final EXistException e) { throw new RuntimeException(e); } } return db; } private static long createDocId() { return nextDocId++; } private void init() { nodeKind = new short[NODE_SIZE]; treeLevel = new short[NODE_SIZE]; next = new int[NODE_SIZE]; Arrays.fill(next, -1); nodeName = new QName[NODE_SIZE]; nodeId = new NodeId[NODE_SIZE]; alpha = new int[NODE_SIZE]; alphaLen = new int[NODE_SIZE]; Arrays.fill(alphaLen, -1); attrName = new QName[ATTR_SIZE]; attrParent = new int[ATTR_SIZE]; attrValue = new String[ATTR_SIZE]; attrType = new int[ATTR_SIZE]; attrNodeId = new NodeId[NODE_SIZE]; treeLevel[0] = 0; nodeKind[0] = Node.DOCUMENT_NODE; document = this; } public void reset() { size = 0; nextChar = 0; nextAttr = 0; nextReferenceIdx = 0; references = null; } public int getSize() { return size; } public int addNode(final short kind, final short level, final QName qname) { if(nodeKind == null) { init(); } if(size == nodeKind.length) { grow(); } nodeKind[size] = kind; treeLevel[size] = level; nodeName[size] = qname != null ? namePool.getSharedName(qname) : null; alpha[size] = -1; // undefined next[size] = -1; return (size++); } public void addChars(final int nodeNum, final char[] ch, final int start, final int len) { if(nodeKind == null) { init(); } if(characters == null) { characters = new char[len > CHAR_BUF_SIZE ? len : CHAR_BUF_SIZE]; } else if((nextChar + len) >= characters.length) { int newLen = (characters.length * 3) / 2; if(newLen < (nextChar + len)) { newLen = nextChar + len; } final char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alpha[nodeNum] = nextChar; alphaLen[nodeNum] = len; System.arraycopy(ch, start, characters, nextChar, len); nextChar += len; } public void addChars(final int nodeNum, final CharSequence s) { if(nodeKind == null) { init(); } int len = (s == null) ? 0 : s.length(); if(characters == null) { characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE]; } else if((nextChar + len) >= characters.length) { int newLen = (characters.length * 3) / 2; if(newLen < (nextChar + len)) { newLen = nextChar + len; } final char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alpha[nodeNum] = nextChar; alphaLen[nodeNum] = len; for(int i = 0; i < len; i++) { characters[nextChar++] = s.charAt(i); } } public void appendChars(final int nodeNum, final char[] ch, final int start, final int len) { if(characters == null) { characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE]; } else if((nextChar + len) >= characters.length) { int newLen = (characters.length * 3) / 2; if(newLen < (nextChar + len)) { newLen = nextChar + len; } final char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alphaLen[nodeNum] = alphaLen[nodeNum] + len; System.arraycopy(ch, start, characters, nextChar, len); nextChar += len; } public void appendChars(final int nodeNum, final CharSequence s) { final int len = s.length(); if(characters == null) { characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE]; } else if((nextChar + len) >= characters.length) { int newLen = (characters.length * 3) / 2; if(newLen < (nextChar + len)) { newLen = nextChar + len; } final char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alphaLen[nodeNum] = alphaLen[nodeNum] + len; for(int i = 0; i < len; i++) { characters[nextChar++] = s.charAt(i); } } public void addReferenceNode(final int nodeNum, final NodeProxy proxy) { if(nodeKind == null) { init(); } if((references == null) || (nextReferenceIdx == references.length)) { growReferences(); } references[nextReferenceIdx] = proxy; alpha[nodeNum] = nextReferenceIdx++; } public boolean hasReferenceNodes() { return references != null && references[0] != null; } public void replaceReferenceNode(final int nodeNum, final CharSequence ch) { nodeKind[nodeNum] = Node.TEXT_NODE; references[alpha[nodeNum]] = null; addChars(nodeNum, ch); } public int addAttribute(final int nodeNum, final QName qname, final String value, final int type) throws DOMException { if(nodeKind == null) { init(); } if((nodeNum > 0) && !(nodeKind[nodeNum] == Node.ELEMENT_NODE || nodeKind[nodeNum] == NodeImpl.NAMESPACE_NODE)) { throw (new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, "err:XQTY0024: An attribute node cannot follow a node that is not an attribute node.")); } int prevAttr = nextAttr - 1; int attrN; //Check if an attribute with the same qname exists in the parent element while((nodeNum > 0) && (prevAttr > -1) && (attrParent[prevAttr] == nodeNum)) { attrN = prevAttr--; final QName prevQn = attrName[attrN]; if(prevQn.equals(qname)) { if(replaceAttribute) { attrValue[attrN] = value; attrType[attrN] = type; return attrN; } else { throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, "err:XQDY0025: element has more than one attribute '" + qname + "'"); } } } if(nextAttr == attrName.length) { growAttributes(); } final QName attrQname = new QName(qname.getLocalPart(), qname.getNamespaceURI(), qname.getPrefix(), ElementValue.ATTRIBUTE); attrParent[nextAttr] = nodeNum; attrName[nextAttr] = namePool.getSharedName(attrQname); attrValue[nextAttr] = value; attrType[nextAttr] = type; if(alpha[nodeNum] < 0) { alpha[nodeNum] = nextAttr; } return (nextAttr++); } public int addNamespace(final int nodeNum, final QName qname) { if(nodeKind == null) { init(); } if((namespaceCode == null) || (nextNamespace == namespaceCode.length)) { growNamespaces(); } namespaceCode[nextNamespace] = namePool.getSharedName(qname); namespaceParent[nextNamespace] = nodeNum; if(alphaLen[nodeNum] < 0) { alphaLen[nodeNum] = nextNamespace; } return nextNamespace++; } public short getTreeLevel(final int nodeNum) { return treeLevel[nodeNum]; } public int getLastNode() { return size - 1; } public short getNodeType(final int nodeNum) { if((nodeKind == null) || (nodeNum < 0)) { return -1; } return nodeKind[nodeNum]; } @Override public String getStringValue() { if(document == null) { return ""; } return super.getStringValue(); } private void grow() { final int newSize = (size * 3) / 2; final short[] newNodeKind = new short[newSize]; System.arraycopy(nodeKind, 0, newNodeKind, 0, size); nodeKind = newNodeKind; final short[] newTreeLevel = new short[newSize]; System.arraycopy(treeLevel, 0, newTreeLevel, 0, size); treeLevel = newTreeLevel; final int[] newNext = new int[newSize]; Arrays.fill(newNext, -1); System.arraycopy(next, 0, newNext, 0, size); next = newNext; final QName[] newNodeName = new QName[newSize]; System.arraycopy(nodeName, 0, newNodeName, 0, size); nodeName = newNodeName; final NodeId[] newNodeId = new NodeId[newSize]; System.arraycopy(nodeId, 0, newNodeId, 0, size); nodeId = newNodeId; final int[] newAlpha = new int[newSize]; System.arraycopy(alpha, 0, newAlpha, 0, size); alpha = newAlpha; final int[] newAlphaLen = new int[newSize]; Arrays.fill(newAlphaLen, -1); System.arraycopy(alphaLen, 0, newAlphaLen, 0, size); alphaLen = newAlphaLen; } private void growAttributes() { final int size = attrName.length; final int newSize = (size * 3) / 2; final QName[] newAttrName = new QName[newSize]; System.arraycopy(attrName, 0, newAttrName, 0, size); attrName = newAttrName; final int[] newAttrParent = new int[newSize]; System.arraycopy(attrParent, 0, newAttrParent, 0, size); attrParent = newAttrParent; final String[] newAttrValue = new String[newSize]; System.arraycopy(attrValue, 0, newAttrValue, 0, size); attrValue = newAttrValue; final int[] newAttrType = new int[newSize]; System.arraycopy(attrType, 0, newAttrType, 0, size); attrType = newAttrType; final NodeId[] newNodeId = new NodeId[newSize]; System.arraycopy(attrNodeId, 0, newNodeId, 0, size); attrNodeId = newNodeId; } private void growReferences() { if(references == null) { references = new NodeProxy[REF_SIZE]; } else { final int size = references.length; final int newSize = (size * 3) / 2; final NodeProxy[] newReferences = new NodeProxy[newSize]; System.arraycopy(references, 0, newReferences, 0, size); references = newReferences; } } private void growNamespaces() { if(namespaceCode == null) { namespaceCode = new QName[5]; namespaceParent = new int[5]; } else { final int size = namespaceCode.length; final int newSize = (size * 3) / 2; final QName[] newCodes = new QName[newSize]; System.arraycopy(namespaceCode, 0, newCodes, 0, size); namespaceCode = newCodes; final int[] newParents = new int[newSize]; System.arraycopy(namespaceParent, 0, newParents, 0, size); namespaceParent = newParents; } } public NodeImpl getAttribute(final int nodeNum) throws DOMException { return new AttrImpl(this, nodeNum); } public NodeImpl getNamespaceNode(final int nodeNum) throws DOMException { return new NamespaceNode(this, nodeNum); } public NodeImpl getNode(final int nodeNum) throws DOMException { if(nodeNum == 0) { return this; } if(nodeNum >= size) { throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "node not found"); } final NodeImpl node; switch(nodeKind[nodeNum]) { case Node.ELEMENT_NODE: node = new ElementImpl(this, nodeNum); break; case Node.TEXT_NODE: node = new TextImpl(this, nodeNum); break; case Node.COMMENT_NODE: node = new CommentImpl(this, nodeNum); break; case Node.PROCESSING_INSTRUCTION_NODE: node = new ProcessingInstructionImpl(this, nodeNum); break; case Node.CDATA_SECTION_NODE: node = new CDATASectionImpl(this, nodeNum); break; case NodeImpl.REFERENCE_NODE: node = new ReferenceNode(this, nodeNum); break; default: throw new DOMException(DOMException.NOT_FOUND_ERR, "node not found"); } return node; } public NodeImpl getLastAttr() { if(nextAttr == 0) { return null; } return new AttrImpl(this, nextAttr - 1); } @Override public Node getParentNode() { return null; } @Override public DocumentType getDoctype() { return null; } @Override public DOMImplementation getImplementation() { return new DOMImplementation() { @Override public Document createDocument(final String namespaceURI, final String qualifiedName, final DocumentType doctype) throws DOMException { return null; } @Override public DocumentType createDocumentType(final String qualifiedName, final String publicId, final String systemId) throws DOMException { return null; } @Override public Object getFeature(final String feature, final String version) { return null; } @Override public boolean hasFeature(final String feature, final String version) { return ("XML".equals(feature) && ("1.0".equals(version) || "2.0".equals(version))); } }; } @Override public Element getDocumentElement() { if(size == 1) { return null; } int nodeNum = 1; while(nodeKind[nodeNum] != Node.ELEMENT_NODE) { if(next[nodeNum] < nodeNum) { return null; } nodeNum = next[nodeNum]; } return (Element)getNode(nodeNum); } @Override public Node getFirstChild() { if(size > 1) { return getNode(1); } return null; } @Override public Node getLastChild() { return getFirstChild(); } public int getAttributesCountFor(final int nodeNumber) { int count = 0; int attr = alpha[nodeNumber]; if(-1 < attr) { while((attr < nextAttr) && (attrParent[attr++] == nodeNumber)) { ++count; } } return count; } public int getNamespacesCountFor(final int nodeNumber) { int count = 0; int ns = alphaLen[nodeNumber]; if(-1 < ns) { while((ns < nextNamespace) && (namespaceParent[ns++] == nodeNumber)) { ++count; } } return count; } public int getChildCountFor(final int nr) { int count = 0; int nextNode = getFirstChildFor(nr); while(nextNode > nr) { ++count; nextNode = next[nextNode]; } return count; } public int getFirstChildFor(final int nodeNumber) { final short level = treeLevel[nodeNumber]; final int nextNode = nodeNumber + 1; if((nextNode < size) && (treeLevel[nextNode] > level)) { return nextNode; } return -1; } public int getNextSiblingFor(final int nodeNumber) { final int nextNr = next[nodeNumber]; return nextNr < nodeNumber ? -1 : nextNr; } public int getParentNodeFor(final int nodeNumber) { int nextNode = next[nodeNumber]; while(nextNode > nodeNumber) { nextNode = next[nextNode]; } return nextNode; } @Override public void selectChildren(final NodeTest test, final Sequence result) throws XPathException { if(size == 1) { return; } NodeImpl next = (NodeImpl) getFirstChild(); while(next != null) { if(test.matches(next)) { result.add(next); } next = (NodeImpl) next.getNextSibling(); } } @Override public void selectDescendants(final boolean includeSelf, final NodeTest test, final Sequence result) throws XPathException { if(includeSelf && test.matches(this)) { result.add(this); } if(size == 1) { return; } NodeImpl next = (NodeImpl) getFirstChild(); while(next != null) { if(test.matches(next)) { result.add(next); } next.selectDescendants(includeSelf, test, result); next = (NodeImpl) next.getNextSibling(); } } @Override public void selectDescendantAttributes(final NodeTest test, final Sequence result) throws XPathException { if(size == 1) { return; } NodeImpl next = (NodeImpl) getFirstChild(); while(next != null) { if(test.matches(next)) { result.add(next); } next.selectDescendantAttributes(test, result); next = (NodeImpl) next.getNextSibling(); } } public NodeImpl selectById(final String id) { if(size == 1) { return null; } final ElementImpl root = (ElementImpl) getDocumentElement(); if(hasIdAttribute(root.getNodeNumber(), id)) { return root; } final int treeLevel = this.treeLevel[root.getNodeNumber()]; int nextNode = root.getNodeNumber(); while((++nextNode < document.size) && (document.treeLevel[nextNode] > treeLevel)) { if((document.nodeKind[nextNode] == Node.ELEMENT_NODE) && hasIdAttribute(nextNode, id)) { return getNode(nextNode); } } return null; } public NodeImpl selectByIdref(final String id) { if(size == 1) { return null; } final ElementImpl root = (ElementImpl) getDocumentElement(); AttrImpl attr = getIdrefAttribute(root.getNodeNumber(), id); if(attr != null) { return attr; } final int treeLevel = this.treeLevel[root.getNodeNumber()]; int nextNode = root.getNodeNumber(); while((++nextNode < document.size) && (document.treeLevel[nextNode] > treeLevel)) { if(document.nodeKind[nextNode] == Node.ELEMENT_NODE) { attr = getIdrefAttribute(nextNode, id); if(attr != null) { return attr; } } } return null; } private boolean hasIdAttribute(final int nodeNumber, final String id) { int attr = document.alpha[nodeNumber]; if(-1 < attr) { while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) { if((document.attrType[attr] == AttrImpl.ATTR_ID_TYPE) && id.equals(document.attrValue[attr])) { return true; } ++attr; } } return false; } private AttrImpl getIdrefAttribute(final int nodeNumber, final String id) { int attr = document.alpha[nodeNumber]; if(-1 < attr) { while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) { if((document.attrType[attr] == AttrImpl.ATTR_IDREF_TYPE) && id.equals(document.attrValue[attr])) { return new AttrImpl(this, attr); } ++attr; } } return null; } @Override public boolean matchChildren(final NodeTest test) throws XPathException { if(size == 1) { return false; } NodeImpl next = (NodeImpl) getFirstChild(); while(next != null) { if(test.matches(next)) { return true; } next = (NodeImpl) next.getNextSibling(); } return false; } @Override public boolean matchDescendants(final boolean includeSelf, final NodeTest test) throws XPathException { if(includeSelf && test.matches(this)) { return true; } if(size == 1) { return true; } NodeImpl next = (NodeImpl) getFirstChild(); while(next != null) { if(test.matches(next)) { return true; } if(next.matchDescendants(includeSelf, test)) { return true; } next = (NodeImpl) next.getNextSibling(); } return false; } @Override public boolean matchDescendantAttributes(final NodeTest test) throws XPathException { if(size == 1) { return false; } NodeImpl next = (NodeImpl) getFirstChild(); while(next != null) { if(test.matches(next)) { return true; } if(next.matchDescendantAttributes(test)) { return true; } next = (NodeImpl) next.getNextSibling(); } return false; } @Override public Element createElement(final String tagName) throws DOMException { try { final QName qn = QName.parse(getContext(), tagName); final int nodeNum = addNode(Node.ELEMENT_NODE, (short) 1, qn); return new ElementImpl(this, nodeNum); } catch(final XPathException e) { throw new DOMException(DOMException.NAMESPACE_ERR, e.getMessage()); } } @Override public DocumentFragment createDocumentFragment() { return null; } @Override public Text createTextNode(final String data) { return null; } @Override public Comment createComment(final String data) { return null; } @Override public CDATASection createCDATASection(final String data) throws DOMException { return null; } @Override public ProcessingInstruction createProcessingInstruction(final String target, final String data) throws DOMException { return null; } @Override public Attr createAttribute(final String name) throws DOMException { return null; } @Override public EntityReference createEntityReference(final String name) throws DOMException { return null; } @Override public NodeList getElementsByTagName(final String tagname) { final NodeListImpl nl = new NodeListImpl(); for(int i = 1; i < size; i++) { if(nodeKind[i] == Node.ELEMENT_NODE) { final QName qn = nodeName[i]; if(qn.getStringValue().equals(tagname)) { nl.add(getNode(i)); } } } return nl; } @Override public Node importNode(final Node importedNode, final boolean deep) throws DOMException { return null; } @Override public Element createElementNS(final String namespaceURI, final String qualifiedName) throws DOMException { return null; } @Override public Attr createAttributeNS(final String namespaceURI, final String qualifiedName) throws DOMException { return null; } @Override public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) { final NodeListImpl nl = new NodeListImpl(); for(int i = 1; i < size; i++) { if(nodeKind[i] == Node.ELEMENT_NODE) { final QName qn = nodeName[i]; if(qn.getNamespaceURI().equals(namespaceURI) && qn.getLocalPart().equals(localName)) { nl.add(getNode(i)); } } } return nl; } @Override public Element getElementById(final String elementId) { return null; } @Override public DocumentImpl getOwnerDocument() { return this; } /** * Copy the document fragment starting at the specified node to the given document builder. * * @param node * @param receiver * @throws SAXException DOCUMENT ME! */ public void copyTo(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException { copyTo(node, receiver, false); } protected void copyTo(NodeImpl node, final DocumentBuilderReceiver receiver, final boolean expandRefs) throws SAXException { final NodeImpl top = node; while(node != null) { copyStartNode(node, receiver, expandRefs); NodeImpl nextNode; if(node instanceof ReferenceNode) { //Nothing more to stream ? nextNode = null; } else { nextNode = (NodeImpl) node.getFirstChild(); } while(nextNode == null) { copyEndNode(node, receiver); if((top != null) && (top.nodeNumber == node.nodeNumber)) { break; } //No nextNode if the top node is a Document node nextNode = (NodeImpl) node.getNextSibling(); if(nextNode == null) { node = (NodeImpl) node.getParentNode(); if((node == null) || ((top != null) && (top.nodeNumber == node.nodeNumber))) { copyEndNode(node, receiver); break; } } } node = nextNode; } } private void copyStartNode(final NodeImpl node, final DocumentBuilderReceiver receiver, final boolean expandRefs) throws SAXException { final int nr = node.nodeNumber; switch(node.getNodeType()) { case Node.ELEMENT_NODE: { final QName nodeName = document.nodeName[nr]; receiver.startElement(nodeName, null); int attr = document.alpha[nr]; if(-1 < attr) { while((attr < document.nextAttr) && (document.attrParent[attr] == nr)) { final QName attrQName = document.attrName[attr]; receiver.attribute(attrQName, attrValue[attr]); ++attr; } } int ns = document.alphaLen[nr]; if(-1 < ns) { while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nr)) { final QName nsQName = document.namespaceCode[ns]; receiver.addNamespaceNode(nsQName); ++ns; } } break; } case Node.TEXT_NODE: receiver.characters(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.CDATA_SECTION_NODE: receiver.cdataSection(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.ATTRIBUTE_NODE: final QName attrQName = document.attrName[nr]; receiver.attribute(attrQName, attrValue[nr]); break; case Node.COMMENT_NODE: receiver.comment(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.PROCESSING_INSTRUCTION_NODE: final QName piQName = document.nodeName[nr]; final String data = new String(document.characters, document.alpha[nr], document.alphaLen[nr]); receiver.processingInstruction(piQName.getLocalPart(), data); break; case NodeImpl.NAMESPACE_NODE: receiver.addNamespaceNode(document.namespaceCode[nr]); break; case NodeImpl.REFERENCE_NODE: if(expandRefs) { try(final DBBroker broker = getDatabase().getBroker()) { final Serializer serializer = broker.getSerializer(); serializer.reset(); serializer.setProperty(Serializer.GENERATE_DOC_EVENTS, "false"); serializer.setReceiver(receiver); serializer.toReceiver(document.references[document.alpha[nr]], false, false); } catch(final EXistException e) { throw new SAXException(e); } } else { receiver.addReferenceNode(document.references[document.alpha[nr]]); } break; } } private void copyEndNode(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException { if(node.getNodeType() == Node.ELEMENT_NODE) { receiver.endElement(node.getQName()); } } /** * Expand all reference nodes in the current document, i.e. replace them by real nodes. Reference nodes are just pointers to nodes from other * documents stored in the database. The XQuery engine uses reference nodes to speed up the creation of temporary doc fragments. * <p/> * <p>This method creates a new copy of the document contents and expands all reference nodes.</p> * * @throws DOMException DOCUMENT ME! */ @Override public void expand() throws DOMException { if(size == 0) { return; } final DocumentImpl newDoc = expandRefs(null); copyDocContents(newDoc); } public DocumentImpl expandRefs(final NodeImpl rootNode) throws DOMException { try { if(nextReferenceIdx == 0) { computeNodeIds(); return this; } final MemTreeBuilder builder = new MemTreeBuilder(context); final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(builder); try { builder.startDocument(); NodeImpl node = (rootNode == null) ? (NodeImpl) getFirstChild() : rootNode; while(node != null) { copyTo(node, receiver, true); node = (NodeImpl) node.getNextSibling(); } receiver.endDocument(); } catch(final SAXException e) { throw new DOMException(DOMException.INVALID_STATE_ERR, e.getMessage()); } final DocumentImpl newDoc = builder.getDocument(); newDoc.computeNodeIds(); return newDoc; } catch(final EXistException e) { throw new DOMException(DOMException.INVALID_STATE_ERR, e.getMessage()); } } public NodeImpl getNodeById(final NodeId id) { expand(); for(int i = 0; i < size; i++) { if(id.equals(nodeId[i])) { return getNode(i); } } return null; } private void computeNodeIds() throws EXistException { if(nodeId[0] != null) { return; } final NodeIdFactory nodeFactory = getDatabase().getNodeFactory(); nodeId[0] = nodeFactory.documentNodeId(); if(size == 1) { return; } NodeId nextId = nodeFactory.createInstance(); NodeImpl next = (NodeImpl) getFirstChild(); while(next != null) { computeNodeIds(nextId, next.nodeNumber); next = (NodeImpl) next.getNextSibling(); nextId = nextId.nextSibling(); } } private void computeNodeIds(final NodeId id, final int nodeNum) { nodeId[nodeNum] = id; if(nodeKind[nodeNum] == Node.ELEMENT_NODE) { NodeId nextId = id.newChild(); int attr = document.alpha[nodeNum]; if(-1 < attr) { while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNum)) { attrNodeId[attr] = nextId; nextId = nextId.nextSibling(); ++attr; } } int nextNode = getFirstChildFor(nodeNum); while(nextNode > nodeNum) { computeNodeIds(nextId, nextNode); nextNode = document.next[nextNode]; if(nextNode > nodeNum) { nextId = nextId.nextSibling(); } } } } /** * DOCUMENT ME! * * @param newDoc */ private void copyDocContents(final DocumentImpl newDoc) { namePool = newDoc.namePool; nodeKind = newDoc.nodeKind; treeLevel = newDoc.treeLevel; next = newDoc.next; nodeName = newDoc.nodeName; nodeId = newDoc.nodeId; alpha = newDoc.alpha; alphaLen = newDoc.alphaLen; characters = newDoc.characters; nextChar = newDoc.nextChar; attrName = newDoc.attrName; attrNodeId = newDoc.attrNodeId; attrParent = newDoc.attrParent; attrValue = newDoc.attrValue; nextAttr = newDoc.nextAttr; namespaceParent = newDoc.namespaceParent; namespaceCode = newDoc.namespaceCode; nextNamespace = newDoc.nextNamespace; size = newDoc.size; documentRootNode = newDoc.documentRootNode; references = newDoc.references; nextReferenceIdx = newDoc.nextReferenceIdx; } /** * Stream the specified document fragment to a receiver. This method * is called by the serializer to output in-memory nodes. * * @param serializer * @param node * @param receiver * @throws SAXException */ public void streamTo(final Serializer serializer, NodeImpl node, final Receiver receiver) throws SAXException { final NodeImpl top = node; while(node != null) { startNode(serializer, node, receiver); NodeImpl nextNode; if(node instanceof ReferenceNode) { //Nothing more to stream ? nextNode = null; } else { nextNode = (NodeImpl) node.getFirstChild(); } while(nextNode == null) { endNode(node, receiver); if((top != null) && (top.nodeNumber == node.nodeNumber)) { break; } nextNode = (NodeImpl) node.getNextSibling(); if(nextNode == null) { node = (NodeImpl) node.getParentNode(); if((node == null) || ((top != null) && (top.nodeNumber == node.nodeNumber))) { endNode(node, receiver); break; } } } node = nextNode; } } private void startNode(final Serializer serializer, final NodeImpl node, final Receiver receiver) throws SAXException { final int nr = node.nodeNumber; switch(node.getNodeType()) { case Node.ELEMENT_NODE: final QName nodeName = document.nodeName[nr]; //Output required namespace declarations int ns = document.alphaLen[nr]; if(ns > -1) { while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nr)) { final QName nsQName = document.namespaceCode[ns]; if(XMLConstants.XMLNS_ATTRIBUTE.equals(nsQName.getLocalPart())) { receiver.startPrefixMapping(XMLConstants.DEFAULT_NS_PREFIX, nsQName.getNamespaceURI()); } else { receiver.startPrefixMapping(nsQName.getLocalPart(), nsQName.getNamespaceURI()); } ++ns; } } //Create the attribute list AttrList attribs = null; int attr = document.alpha[nr]; if(attr > -1) { attribs = new AttrList(); while((attr < document.nextAttr) && (document.attrParent[attr] == nr)) { final QName attrQName = document.attrName[attr]; attribs.addAttribute(attrQName, attrValue[attr]); ++attr; } } receiver.startElement(nodeName, attribs); break; case Node.TEXT_NODE: receiver.characters(new String(document.characters, document.alpha[nr], document.alphaLen[nr])); break; case Node.ATTRIBUTE_NODE: final QName attrQName = document.attrName[nr]; receiver.attribute(attrQName, attrValue[nr]); break; case Node.COMMENT_NODE: receiver.comment(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.PROCESSING_INSTRUCTION_NODE: final QName qn = document.nodeName[nr]; final String data = new String(document.characters, document.alpha[nr], document.alphaLen[nr]); receiver.processingInstruction(qn.getLocalPart(), data); break; case Node.CDATA_SECTION_NODE: receiver.cdataSection(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case NodeImpl.REFERENCE_NODE: serializer.toReceiver(document.references[document.alpha[nr]], true, false); break; } } private void endNode(final NodeImpl node, final Receiver receiver) throws SAXException { if(node.getNodeType() == Node.ELEMENT_NODE) { receiver.endElement(node.getQName()); //End all prefix mappings used for the element final int nr = node.nodeNumber; int ns = document.alphaLen[nr]; if(ns > -1) { while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nr)) { final QName nsQName = document.namespaceCode[ns]; if(XMLConstants.XMLNS_ATTRIBUTE.equals(nsQName.getLocalPart())) { receiver.endPrefixMapping(XMLConstants.DEFAULT_NS_PREFIX); } else { receiver.endPrefixMapping(nsQName.getLocalPart()); } ++ns; } } } } public org.exist.dom.persistent.DocumentImpl makePersistent() throws XPathException { if(size <= 1) { return null; } return context.storeTemporaryDoc(this); } public int getChildCount() { int count = 0; int top = (size > 1) ? 1 : -1; while(top > 0) { ++count; top = getNextSiblingFor(top); } return count; } @Override public boolean hasChildNodes() { return getChildCount() > 0; } @Override public NodeList getChildNodes() { final NodeListImpl nl = new NodeListImpl(1); final Element el = getDocumentElement(); if(el != null) { nl.add(el); } return nl; } @Override public String getInputEncoding() { return null; } @Override public String getXmlEncoding() { return null; } @Override public boolean getXmlStandalone() { return false; } @Override public void setXmlStandalone(final boolean xmlStandalone) throws DOMException { } @Override public String getXmlVersion() { return "1.0"; } @Override public void setXmlVersion(final String xmlVersion) throws DOMException { } @Override public boolean getStrictErrorChecking() { return false; } @Override public void setStrictErrorChecking(final boolean strictErrorChecking) { } @Override public String getDocumentURI() { return documentURI; } @Override public void setDocumentURI(final String documentURI) { this.documentURI = documentURI; } @Override public Node adoptNode(final Node source) throws DOMException { return null; } @Override public DOMConfiguration getDomConfig() { return null; } @Override public void normalizeDocument() { } @Override public Node renameNode(final Node n, final String namespaceURI, final String qualifiedName) throws DOMException { return null; } public void setContext(final XQueryContext context) { this.context = context; } public XQueryContext getContext() { return context; } @Override public String getBaseURI() { final Element el = getDocumentElement(); if(el != null) { final String baseURI = getDocumentElement().getAttributeNS(Namespaces.XML_NS, "base"); if(baseURI != null) { return baseURI; } } final String docURI = getDocumentURI(); if(docURI != null) { return docURI; } else { if(context.isBaseURIDeclared()) { try { return context.getBaseURI().getStringValue(); } catch(final XPathException e) { //TODO : make something ! } } return XmldbURI.EMPTY_URI.toString(); } } @Override public int getItemType() { return Type.DOCUMENT; } @Override public String toString() { final StringBuilder result = new StringBuilder(); result.append("in-memory#"); result.append("document {"); if(size != 1) { int nodeNum = 1; while(true) { result.append(getNode(nodeNum).toString()); if(next[nodeNum] < nodeNum) { break; } nodeNum = next[nodeNum]; } } result.append("} "); return result.toString(); } @Override public void selectAttributes(final NodeTest test, final Sequence result) throws XPathException { } }