/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 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.memtree; import org.exist.Indexer; import org.exist.Namespaces; import org.exist.dom.NodeProxy; import org.exist.dom.QName; import org.exist.xquery.Constants; import org.exist.xquery.XQueryContext; import org.w3c.dom.Node; import org.xml.sax.Attributes; import java.util.Arrays; /** * Use this class to build a new in-memory DOM document. * * @author Wolfgang <wolfgang@exist-db.org> */ public class MemTreeBuilder { protected DocumentImpl doc; protected short level = 1; protected int[] prevNodeInLevel; protected XQueryContext context = null; public MemTreeBuilder() { this(null); } public MemTreeBuilder(XQueryContext context) { super(); this.context = context; prevNodeInLevel = new int[15]; Arrays.fill(prevNodeInLevel, -1); prevNodeInLevel[0] = 0; } /** * Returns the created document object. * */ 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. */ public void startDocument(boolean explicitCreation) { this.doc = new DocumentImpl(context, explicitCreation); } /** * End building the document. */ public void endDocument() { } /** * Create a new element. * * @return the node number of the created element */ public int startElement(String namespaceURI, String localName, String qname, Attributes attributes) { int p = qname.indexOf(':'); String prefix = null; if(context != null) { prefix = context.getPrefixForURI(namespaceURI); } if(prefix == null) prefix = (p != Constants.STRING_NOT_FOUND) ? qname.substring(0, p) : ""; QName qn = new QName(localName, namespaceURI, prefix); return startElement(qn, attributes); } /** * Create a new element. * * @return the node number of the created element */ public int startElement(QName qn, Attributes attributes) { int nodeNr = doc.addNode(Node.ELEMENT_NODE, level, qn); if(attributes != null) { // parse attributes for (int i = 0; i < attributes.getLength(); i++) { String attrNS = attributes.getURI(i); String attrLocalName = attributes.getLocalName(i); String attrQName = attributes.getQName(i); // skip xmlns-attributes and attributes in eXist's namespace if (!(attrQName.startsWith("xmlns"))) { // || attrNS.equals(Namespaces.EXIST_NS))) { int p = attrQName.indexOf(':'); String attrPrefix = (p != Constants.STRING_NOT_FOUND) ? attrQName.substring(0, p) : null; QName attrQn = new QName(attrLocalName, attrNS, attrPrefix); int type = getAttribType(attrQn, attributes.getType(i)); doc.addAttribute(nodeNr, attrQn, attributes.getValue(i), type); } } } // update links if (level + 1 >= prevNodeInLevel.length) { int[] t = new int[level + 2]; System.arraycopy(prevNodeInLevel, 0, t, 0, prevNodeInLevel.length); prevNodeInLevel = t; } 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(QName qname, String type) { if (qname.equalsSimple(Namespaces.XML_ID_QNAME)) { // an xml:id attribute. return AttributeImpl.ATTR_CDATA_TYPE; } if (type.equals(Indexer.ATTR_ID_TYPE)) return AttributeImpl.ATTR_ID_TYPE; else if (type.equals(Indexer.ATTR_IDREF_TYPE)) return AttributeImpl.ATTR_IDREF_TYPE; else if (type.equals(Indexer.ATTR_IDREFS_TYPE)) return AttributeImpl.ATTR_IDREFS_TYPE; else return AttributeImpl.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(NodeProxy proxy) { int lastNode = doc.getLastNode(); if (0 < lastNode && 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 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 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(QName qname, String value) { 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); //} int nodeNr = doc.addAttribute(lastNode, qname, value, AttributeImpl.ATTR_CDATA_TYPE); //TODO : //1) call linkNode(nodeNr); ? //2) is there a relationship between lastNode and nodeNr ? return nodeNr; } /** * Create a new text node. * * @return the node number of the created node */ public int characters(char[] ch, int start, int len) { int lastNode = doc.getLastNode(); if (0 < lastNode && 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 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 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 } } int nodeNr = doc.addNode(Node.TEXT_NODE, level, null); doc.addChars(nodeNr, ch, start, len); linkNode(nodeNr); return nodeNr; } /** * Create a new text node. * * @return the node number of the created node */ public int characters(CharSequence s) { int lastNode = doc.getLastNode(); if (0 < lastNode && 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 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 } } int nodeNr = doc.addNode(Node.TEXT_NODE, level, null); doc.addChars(nodeNr, s); linkNode(nodeNr); return nodeNr; } public int comment(CharSequence data) { int nodeNr = doc.addNode(Node.COMMENT_NODE, level, null); doc.addChars(nodeNr, data); linkNode(nodeNr); return nodeNr; } public int comment(char ch[], int start, int len) { int nodeNr = doc.addNode(Node.COMMENT_NODE, level, null); doc.addChars(nodeNr, ch, start, len); linkNode(nodeNr); return nodeNr; } public int cdataSection(CharSequence data) { int lastNode = doc.getLastNode(); if (0 < lastNode && 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 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 } } int nodeNr = doc.addNode(Node.CDATA_SECTION_NODE, level, null); doc.addChars(nodeNr, data); linkNode(nodeNr); return nodeNr; } public int processingInstruction(String target, String data) { QName qn = new QName(target, null, null); int nodeNr = doc.addNode(Node.PROCESSING_INSTRUCTION_NODE, level, qn); doc.addChars(nodeNr, data==null ? "" : data); linkNode(nodeNr); return nodeNr; } public int namespaceNode(String prefix, String uri) { return namespaceNode(new QName(prefix, uri, "xmlns")); } public int namespaceNode(QName qn) { int lastNode = doc.getLastNode(); QName elemQN = doc.nodeName[lastNode]; String elemPrefix = elemQN.getPrefix() == null ? "" : elemQN.getPrefix(); if (elemPrefix.equals(qn.getLocalName()) && elemQN.getNamespaceURI() != null && !elemQN.getNamespaceURI().equals(qn.getNamespaceURI())) return -1; return doc.addNamespace(lastNode, qn); } public int documentType(String publicId, 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(String name, String publicId, String systemId) { } private void linkNode(int nodeNr) { int prevNr = prevNodeInLevel[level]; if (prevNr > -1) doc.next[prevNr] = nodeNr; doc.next[nodeNr] = prevNodeInLevel[level - 1]; prevNodeInLevel[level] = nodeNr; } }