/* * eXist Open Source Native XML Database * Copyright (C) 2001-2014 Wolfgang M. Meier * wolfgang@exist-db.org * http://exist.sourceforge.net * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.dom.memtree; import org.exist.Indexer; import org.exist.Namespaces; import org.exist.dom.QName; import org.exist.dom.persistent.NodeProxy; import org.exist.xquery.Constants; import org.exist.xquery.XQueryContext; import org.w3c.dom.DOMException; import org.w3c.dom.Node; import org.xml.sax.Attributes; import javax.xml.XMLConstants; import java.util.Arrays; /** * Use this class to build a new in-memory DOM document. * * @author Wolfgang <wolfgang@exist-db.org> */ public class MemTreeBuilder { private final XQueryContext context; private DocumentImpl doc; private short level = 1; private int[] prevNodeInLevel; private String defaultNamespaceURI = XMLConstants.NULL_NS_URI; public MemTreeBuilder() { this(null); } public MemTreeBuilder(final XQueryContext context) { super(); this.context = context; prevNodeInLevel = new int[15]; Arrays.fill(prevNodeInLevel, -1); prevNodeInLevel[0] = 0; } /** * Returns the created document object. * * @return DOCUMENT ME! */ public DocumentImpl getDocument() { return doc; } public XQueryContext getContext() { return context; } public int getSize() { return doc.getSize(); } /** * Start building the document. */ public void startDocument() { this.doc = new DocumentImpl(context, false); } /** * Start building the document. * * @param explicitCreation DOCUMENT ME! */ public void startDocument(final boolean explicitCreation) { this.doc = new DocumentImpl(context, explicitCreation); } /** * End building the document. */ public void endDocument() { } /** * Create a new element. * * @param namespaceURI DOCUMENT ME! * @param localName DOCUMENT ME! * @param qname DOCUMENT ME! * @param attributes DOCUMENT ME! * @return the node number of the created element */ public int startElement(final String namespaceURI, final String localName, final String qname, final Attributes attributes) { final int prefixIdx = qname.indexOf(':'); String prefix = null; if(context != null && !getDefaultNamespace().equals(namespaceURI == null ? XMLConstants.NULL_NS_URI : namespaceURI)) { prefix = context.getPrefixForURI(namespaceURI); } if (prefix == null) { prefix = (prefixIdx != Constants.STRING_NOT_FOUND) ? qname.substring(0, prefixIdx) : null; } final QName qn = new QName(localName, namespaceURI, prefix); return startElement(qn, attributes); } /** * Create a new element. * * @param qname DOCUMENT ME! * @param attributes DOCUMENT ME! * @return the node number of the created element */ public int startElement(final QName qname, final Attributes attributes) { final int nodeNr = doc.addNode(Node.ELEMENT_NODE, level, qname); if(attributes != null) { // parse attributes for(int i = 0; i < attributes.getLength(); i++) { final String attrNS = attributes.getURI(i); final String attrLocalName = attributes.getLocalName(i); final String attrQName = attributes.getQName(i); // skip xmlns-attributes and attributes in eXist's namespace if(!(attrQName.startsWith(XMLConstants.XMLNS_ATTRIBUTE))) { // || attrNS.equals(Namespaces.EXIST_NS))) { final int p = attrQName.indexOf(':'); final String attrPrefix = (p != Constants.STRING_NOT_FOUND) ? attrQName.substring(0, p) : null; final QName attrQn = new QName(attrLocalName, attrNS, attrPrefix); final int type = getAttribType(attrQn, attributes.getType(i)); doc.addAttribute(nodeNr, attrQn, attributes.getValue(i), type); } } } // update links if((level + 1) >= prevNodeInLevel.length) { final int[] t = new int[level + 2]; System.arraycopy(prevNodeInLevel, 0, t, 0, prevNodeInLevel.length); prevNodeInLevel = t; } final int prevNr = prevNodeInLevel[level]; // TODO: remove potential ArrayIndexOutOfBoundsException if(prevNr > -1) { doc.next[prevNr] = nodeNr; } doc.next[nodeNr] = prevNodeInLevel[level - 1]; prevNodeInLevel[level] = nodeNr; ++level; return nodeNr; } private int getAttribType(final QName qname, final String type) { if(qname.equals(Namespaces.XML_ID_QNAME)) { // an xml:id attribute. return AttrImpl.ATTR_CDATA_TYPE; } else if(type.equals(Indexer.ATTR_ID_TYPE)) { return AttrImpl.ATTR_ID_TYPE; } else if(type.equals(Indexer.ATTR_IDREF_TYPE)) { return AttrImpl.ATTR_IDREF_TYPE; } else if(type.equals(Indexer.ATTR_IDREFS_TYPE)) { return AttrImpl.ATTR_IDREFS_TYPE; } else { return AttrImpl.ATTR_CDATA_TYPE; } } /** * Close the last element created. */ public void endElement() { // System.out.println("end-element: level = " + level); prevNodeInLevel[level] = -1; --level; } public int addReferenceNode(final NodeProxy proxy) { final int lastNode = doc.getLastNode(); if((lastNode > 0) && (level == doc.getTreeLevel(lastNode))) { if((doc.getNodeType(lastNode) == Node.TEXT_NODE) && (proxy.getNodeType() == Node.TEXT_NODE)) { // if the last node is a text node, we have to append the // characters to this node. XML does not allow adjacent text nodes. doc.appendChars(lastNode, proxy.getNodeValue()); return lastNode; } if(doc.getNodeType(lastNode) == NodeImpl.REFERENCE_NODE) { // check if the previous node is a reference node. if yes, check if it is a text node final int p = doc.alpha[lastNode]; if((doc.references[p].getNodeType() == Node.TEXT_NODE) && (proxy.getNodeType() == Node.TEXT_NODE)) { // found a text node reference. create a new char sequence containing // the concatenated text of both nodes final String s = doc.references[p].getStringValue() + proxy.getStringValue(); doc.replaceReferenceNode(lastNode, s); return lastNode; } } } final int nodeNr = doc.addNode(NodeImpl.REFERENCE_NODE, level, null); doc.addReferenceNode(nodeNr, proxy); linkNode(nodeNr); return nodeNr; } public int addAttribute(final QName qname, final String value) { final int lastNode = doc.getLastNode(); //if(0 < lastNode && doc.nodeKind[lastNode] != Node.ELEMENT_NODE) { //Definitely wrong ! //lastNode = characters(value); //} else { //lastNode = doc.addAttribute(lastNode, qname, value); //} final int nodeNr = doc.addAttribute(lastNode, qname, value, AttrImpl.ATTR_CDATA_TYPE); //TODO : //1) call linkNode(nodeNr); ? //2) is there a relationship between lastNode and nodeNr ? return nodeNr; } /** * Create a new text node. * * @param ch DOCUMENT ME! * @param start DOCUMENT ME! * @param len DOCUMENT ME! * @return the node number of the created node */ public int characters(final char[] ch, final int start, final int len) { final int lastNode = doc.getLastNode(); if((lastNode > 0) && (level == doc.getTreeLevel(lastNode))) { if(doc.getNodeType(lastNode) == Node.TEXT_NODE) { // if the last node is a text node, we have to append the // characters to this node. XML does not allow adjacent text nodes. doc.appendChars(lastNode, ch, start, len); return lastNode; } if(doc.getNodeType(lastNode) == NodeImpl.REFERENCE_NODE) { // check if the previous node is a reference node. if yes, check if it is a text node final int p = doc.alpha[lastNode]; if(doc.references[p].getNodeType() == Node.TEXT_NODE) { // found a text node reference. create a new char sequence containing // the concatenated text of both nodes final StringBuilder s = new StringBuilder(doc.references[p].getStringValue()); s.append(ch, start, len); doc.replaceReferenceNode(lastNode, s); return lastNode; } // fall through and add the node below } } final int nodeNr = doc.addNode(Node.TEXT_NODE, level, null); doc.addChars(nodeNr, ch, start, len); linkNode(nodeNr); return nodeNr; } /** * Create a new text node. * * @param s DOCUMENT ME! * @return the node number of the created node, -1 if no node was created */ public int characters(final CharSequence s) { if(s == null) { return -1; } final int lastNode = doc.getLastNode(); if((lastNode > 0) && (level == doc.getTreeLevel(lastNode))) { if((doc.getNodeType(lastNode) == Node.TEXT_NODE) || (doc.getNodeType(lastNode) == Node.CDATA_SECTION_NODE)) { // if the last node is a text node, we have to append the // characters to this node. XML does not allow adjacent text nodes. doc.appendChars(lastNode, s); return lastNode; } if(doc.getNodeType(lastNode) == NodeImpl.REFERENCE_NODE) { // check if the previous node is a reference node. if yes, check if it is a text node final int p = doc.alpha[lastNode]; if((doc.references[p].getNodeType() == Node.TEXT_NODE) || (doc.references[p].getNodeType() == Node.CDATA_SECTION_NODE)) { // found a text node reference. create a new char sequence containing // the concatenated text of both nodes doc.replaceReferenceNode(lastNode, doc.references[p].getStringValue() + s); return lastNode; } // fall through and add the node below } } final int nodeNr = doc.addNode(Node.TEXT_NODE, level, null); doc.addChars(nodeNr, s); linkNode(nodeNr); return nodeNr; } public int comment(final CharSequence data) { final int nodeNr = doc.addNode(Node.COMMENT_NODE, level, null); doc.addChars(nodeNr, data); linkNode(nodeNr); return nodeNr; } public int comment(final char[] ch, final int start, final int len) { final int nodeNr = doc.addNode(Node.COMMENT_NODE, level, null); doc.addChars(nodeNr, ch, start, len); linkNode(nodeNr); return nodeNr; } public int cdataSection(final CharSequence data) { final int lastNode = doc.getLastNode(); if((lastNode > 0) && (level == doc.getTreeLevel(lastNode))) { if((doc.getNodeType(lastNode) == Node.TEXT_NODE) || (doc.getNodeType(lastNode) == Node.CDATA_SECTION_NODE)) { // if the last node is a text node, we have to append the // characters to this node. XML does not allow adjacent text nodes. doc.appendChars(lastNode, data); return lastNode; } if(doc.getNodeType(lastNode) == NodeImpl.REFERENCE_NODE) { // check if the previous node is a reference node. if yes, check if it is a text node final int p = doc.alpha[lastNode]; if((doc.references[p].getNodeType() == Node.TEXT_NODE) || (doc.references[p].getNodeType() == Node.CDATA_SECTION_NODE)) { // found a text node reference. create a new char sequence containing // the concatenated text of both nodes doc.replaceReferenceNode(lastNode, doc.references[p].getStringValue() + data); return lastNode; } // fall through and add the node below } } final int nodeNr = doc.addNode(Node.CDATA_SECTION_NODE, level, null); doc.addChars(nodeNr, data); linkNode(nodeNr); return nodeNr; } public int processingInstruction(final String target, final String data) { final QName qname = new QName(target, null, null); final int nodeNr = doc.addNode(Node.PROCESSING_INSTRUCTION_NODE, level, qname); doc.addChars(nodeNr, (data == null) ? "" : data); linkNode(nodeNr); return nodeNr; } public int namespaceNode(final String prefix, final String uri) { return namespaceNode(new QName(prefix, uri, XMLConstants.XMLNS_ATTRIBUTE)); } public int namespaceNode(final QName qname) { return namespaceNode(qname, false); } public int namespaceNode(final QName qname, boolean checkNS) { final int lastNode = doc.getLastNode(); boolean addNode = true; if(doc.nodeName != null) { final QName elemQN = doc.nodeName[lastNode]; if(elemQN != null) { final String elemPrefix = (elemQN.getPrefix() == null) ? XMLConstants.DEFAULT_NS_PREFIX : elemQN.getPrefix(); if (checkNS && XMLConstants.DEFAULT_NS_PREFIX.equals(elemPrefix) && XMLConstants.XMLNS_ATTRIBUTE.equals(qname.getPrefix()) && "".equals(qname.getLocalPart())) { throw new DOMException( DOMException.NAMESPACE_ERR, "Cannot output a namespace node for the default namespace when the element is in no namespace." ); } if(elemPrefix.equals(qname.getLocalPart()) && (elemQN.getNamespaceURI() != null)) { addNode = false; } } } return (addNode ? doc.addNamespace(lastNode, qname) : -1); } public int documentType(final String publicId, final String systemId) { // int nodeNr = doc.addNode(Node.DOCUMENT_TYPE_NODE, level, null); // doc.addChars(nodeNr, data); // linkNode(nodeNr); // return nodeNr; return -1; } public void documentType(final String name, final String publicId, final String systemId) { } private void linkNode(final int nodeNr) { final int prevNr = prevNodeInLevel[level]; if(prevNr > -1) { doc.next[prevNr] = nodeNr; } doc.next[nodeNr] = prevNodeInLevel[level - 1]; prevNodeInLevel[level] = nodeNr; } public void setReplaceAttributeFlag(final boolean replaceAttribute) { doc.replaceAttribute = replaceAttribute; } public void setDefaultNamespace(final String defaultNamespaceURI) { this.defaultNamespaceURI = defaultNamespaceURI; } private String getDefaultNamespace() { // guard against someone setting null as the defaultNamespaceURI return defaultNamespaceURI == null ? XMLConstants.NULL_NS_URI : defaultNamespaceURI; } }