/* * eXist Open Source Native XML Database * Copyright (C) 2001-2007 The eXist team * 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; import org.exist.EXistException; import org.exist.Namespaces; import org.exist.indexing.StreamListener; import org.exist.numbering.NodeId; import org.exist.stax.EmbeddedXMLStreamReader; import org.exist.storage.DBBroker; import org.exist.storage.ElementValue; import org.exist.storage.NodePath; import org.exist.storage.RangeIndexSpec; import org.exist.storage.Signatures; import org.exist.storage.btree.Value; import org.exist.storage.txn.TransactionException; import org.exist.storage.txn.TransactionManager; import org.exist.storage.txn.Txn; import org.exist.util.ByteArrayPool; import org.exist.util.ByteConversion; import org.exist.util.UTF8; import org.exist.util.pool.NodePool; import org.exist.xquery.Constants; import org.exist.xquery.value.StringValue; import org.exist.external.org.apache.commons.io.output.ByteArrayOutputStream; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; 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.ProcessingInstruction; import org.w3c.dom.Text; import org.w3c.dom.TypeInfo; import org.w3c.dom.UserDataHandler; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeSet; /** * ElementImpl.java * * @author Wolfgang Meier */ public class ElementImpl extends NamedNode implements Element { public static final int LENGTH_ELEMENT_CHILD_COUNT = 4; //sizeof int public static final int LENGTH_ATTRIBUTES_COUNT = 2; //sizeof short public static final int LENGTH_NS_ID = 2; //sizeof short public static final int LENGTH_PREFIX_LENGTH = 2; //sizeof short private short attributes = 0; private int children = 0; private int position = 0; private Map namespaceMappings = null; private int indexType = RangeIndexSpec.NO_INDEX; private boolean preserveWS = false; private boolean isDirty = false; public ElementImpl() { super(Node.ELEMENT_NODE); } /** * Constructor for the ElementImpl object * * @param nodeName Description of the Parameter */ public ElementImpl(QName nodeName) { super(Node.ELEMENT_NODE, nodeName); this.nodeName = nodeName; } public ElementImpl(ElementImpl other) { super(other); this.children = other.children; this.attributes = other.attributes; this.namespaceMappings = other.namespaceMappings; this.indexType = other.indexType; this.position = other.position; } /** * Reset this element to its initial state. * */ public void clear() { super.clear(); attributes = 0; children = 0; position = 0; namespaceMappings = null; //TODO : reset below as well ? -pb //indexType //preserveWS } public void setIndexType(int idxType) { this.indexType = idxType; } public int getIndexType() { return indexType; } public boolean isDirty() { return isDirty; } public void setDirty(boolean dirty) { this.isDirty = dirty; } public void setPosition(int position) { this.position = position; } public int getPosition() { return position; } public boolean declaresNamespacePrefixes() { if (namespaceMappings == null) return false; return (namespaceMappings.size() > 0); } public byte[] serialize() { if (nodeId == null) throw new RuntimeException("nodeId = null for element: " + getQName().getStringValue()); try { final SymbolTable symbols = ownerDocument.getBrokerPool().getSymbols(); byte[] prefixData = null; // serialize namespace prefixes declared in this element if (declaresNamespacePrefixes()) { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout); out.writeShort(namespaceMappings.size()); for (Iterator i = namespaceMappings.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); out.writeUTF((String) entry.getKey()); short nsId = symbols.getNSSymbol((String) entry.getValue()); out.writeShort(nsId); } prefixData = bout.toByteArray(); } final short id = symbols.getSymbol(this); final boolean hasNamespace = nodeName.needsNamespaceDecl(); short nsId = 0; if (hasNamespace) nsId = symbols.getNSSymbol(nodeName.getNamespaceURI()); final byte idSizeType = Signatures.getSizeType(id); byte signature = (byte) ((Signatures.Elem << 0x5) | idSizeType); int prefixLen = 0; if (hasNamespace) { if (nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0) prefixLen = UTF8.encoded(nodeName.getPrefix()); signature |= 0x10; } if (isDirty) signature |= 0x8; final int nodeIdLen = nodeId.size(); byte[] data = ByteArrayPool.getByteArray( StoredNode.LENGTH_SIGNATURE_LENGTH + LENGTH_ELEMENT_CHILD_COUNT + NodeId.LENGTH_NODE_ID_UNITS + + nodeIdLen + LENGTH_ATTRIBUTES_COUNT + Signatures.getLength(idSizeType) + (hasNamespace ? prefixLen + 4 : 0) + (prefixData != null ? prefixData.length : 0) ); int next = 0; data[next] = signature; next += StoredNode.LENGTH_SIGNATURE_LENGTH; ByteConversion.intToByte(children, data, next); next += LENGTH_ELEMENT_CHILD_COUNT; ByteConversion.shortToByte((short) nodeId.units(), data, next); next += NodeId.LENGTH_NODE_ID_UNITS; nodeId.serialize(data, next); next += nodeIdLen; ByteConversion.shortToByte(attributes, data, next); next += LENGTH_ATTRIBUTES_COUNT; Signatures.write(idSizeType, id, data, next); next += Signatures.getLength(idSizeType); if (hasNamespace) { ByteConversion.shortToByte(nsId, data, next); next += LENGTH_NS_ID; ByteConversion.shortToByte((short) prefixLen, data, next); next += LENGTH_PREFIX_LENGTH; if (nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0) UTF8.encode(nodeName.getPrefix(), data, next); next += prefixLen; } if (prefixData != null) System.arraycopy(prefixData, 0, data, next, prefixData.length); return data; } catch (IOException e) { return null; } } public static StoredNode deserialize(byte[] data, int start, int len, DocumentImpl doc, boolean pooled) { int end = start + len; int pos = start; byte idSizeType = (byte) (data[pos] & 0x03); boolean isDirty = (data[pos] & 0x8) == 0x8; boolean hasNamespace = (data[pos] & 0x10) == 0x10; pos += StoredNode.LENGTH_SIGNATURE_LENGTH; int children = ByteConversion.byteToInt(data, pos); pos += LENGTH_ELEMENT_CHILD_COUNT; int dlnLen = ByteConversion.byteToShort(data, pos); pos += NodeId.LENGTH_NODE_ID_UNITS; NodeId dln = doc.getBrokerPool().getNodeFactory().createFromData(dlnLen, data, pos); pos += dln.size(); short attributes = ByteConversion.byteToShort(data, pos); pos += LENGTH_ATTRIBUTES_COUNT; short id = (short) Signatures.read(idSizeType, data, pos); pos += Signatures.getLength(idSizeType); short nsId = 0; String prefix = null; if (hasNamespace) { nsId = ByteConversion.byteToShort(data, pos); pos += LENGTH_NS_ID; int prefixLen = ByteConversion.byteToShort(data, pos); pos += LENGTH_PREFIX_LENGTH; if (prefixLen > 0) prefix = UTF8.decode(data, pos, prefixLen).toString(); pos += prefixLen; } String name = doc.getBrokerPool().getSymbols().getName(id); String namespace = ""; if (nsId != 0) namespace = doc.getBrokerPool().getSymbols().getNamespace(nsId); ElementImpl node; if (pooled) node = (ElementImpl) NodePool.getInstance().borrowNode(Node.ELEMENT_NODE); // node = (ElementImpl) NodeObjectPool.getInstance().borrowNode(ElementImpl.class); else node = new ElementImpl(); node.setNodeId(dln); node.nodeName = doc.getBrokerPool().getSymbols().getQName(Node.ELEMENT_NODE, namespace, name, prefix); node.children = children; node.attributes = attributes; node.isDirty = isDirty; node.setOwnerDocument(doc); //TO UNDERSTAND : why is this code here ? if (end > pos) { byte[] pfxData = new byte[end - pos]; System.arraycopy(data, pos, pfxData, 0, end - pos); ByteArrayInputStream bin = new ByteArrayInputStream(pfxData); DataInputStream in = new DataInputStream(bin); try { short prefixCount = in.readShort(); for (int i = 0; i < prefixCount; i++) { prefix = in.readUTF(); nsId = in.readShort(); node.addNamespaceMapping(prefix, doc.getBrokerPool().getSymbols().getNamespace(nsId)); } } catch (IOException e) { e.printStackTrace(); } } return node; } public static QName readQName(Value value, DocumentImpl document, NodeId nodeId) { final byte[] data = value.data(); int offset = value.start(); byte idSizeType = (byte) (data[offset] & 0x03); boolean hasNamespace = (data[offset] & 0x10) == 0x10; offset += StoredNode.LENGTH_SIGNATURE_LENGTH; offset += LENGTH_ELEMENT_CHILD_COUNT; offset += NodeId.LENGTH_NODE_ID_UNITS; offset += nodeId.size(); offset += LENGTH_ATTRIBUTES_COUNT; short id = (short) Signatures.read(idSizeType, data, offset); offset += Signatures.getLength(idSizeType); short nsId = 0; String prefix = null; if (hasNamespace) { nsId = ByteConversion.byteToShort(data, offset); offset += LENGTH_NS_ID; int prefixLen = ByteConversion.byteToShort(data, offset); offset += LENGTH_PREFIX_LENGTH; if (prefixLen > 0) prefix = UTF8.decode(data, offset, prefixLen).toString(); offset += prefixLen; } String name = document.getBrokerPool().getSymbols().getName(id); String namespace = ""; if (nsId != 0) namespace = document.getBrokerPool().getSymbols().getNamespace(nsId); return new QName(name, namespace, prefix == null ? "" : prefix); } public static void readNamespaceDecls(List namespaces, Value value, DocumentImpl document, NodeId nodeId) { final byte[] data = value.data(); int offset = value.start(); int end = offset + value.getLength(); byte idSizeType = (byte) (data[offset] & 0x03); boolean hasNamespace = (data[offset] & 0x10) == 0x10; offset += StoredNode.LENGTH_SIGNATURE_LENGTH; offset += LENGTH_ELEMENT_CHILD_COUNT; offset += NodeId.LENGTH_NODE_ID_UNITS; offset += nodeId.size(); offset += LENGTH_ATTRIBUTES_COUNT; offset += Signatures.getLength(idSizeType); if (hasNamespace) { offset += LENGTH_NS_ID; int prefixLen = ByteConversion.byteToShort(data, offset); offset += LENGTH_PREFIX_LENGTH; offset += prefixLen; } if (end > offset) { byte[] pfxData = new byte[end - offset]; System.arraycopy(data, offset, pfxData, 0, end - offset); ByteArrayInputStream bin = new ByteArrayInputStream(pfxData); DataInputStream in = new DataInputStream(bin); try { short prefixCount = in.readShort(); String prefix; short nsId; for (int i = 0; i < prefixCount; i++) { prefix = in.readUTF(); nsId = in.readShort(); namespaces.add(new String[] { prefix, document.getBrokerPool().getSymbols().getNamespace(nsId) }); } } catch (IOException e) { e.printStackTrace(); } } } public void addNamespaceMapping(String prefix, String ns) { if (prefix == null) return; if (namespaceMappings == null) namespaceMappings = new HashMap(1); else if (namespaceMappings.containsKey(prefix)) return; namespaceMappings.put(prefix, ns); } /** * Append a child to this node. This method does not rearrange the * node tree and is only used internally by the parser. * * @param child * @throws DOMException */ public void appendChildInternal(StoredNode prevNode, StoredNode child) throws DOMException { NodeId childId; if (prevNode == null) { childId = getNodeId().newChild(); } else { if (prevNode.getNodeId() == null) { LOG.warn(getQName() + " : " + prevNode.getNodeName()); } childId = prevNode.getNodeId().nextSibling(); } child.setNodeId(childId); ++children; } /** * @see org.w3c.dom.Node#appendChild(org.w3c.dom.Node) */ public Node appendChild(Node child) throws DOMException { TransactionManager transact = ownerDocument.getBrokerPool().getTransactionManager(); Txn transaction = transact.beginTransaction(); NodeListImpl nl = new NodeListImpl(); nl.add(child); DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); appendChildren(transaction, nl, 0); broker.storeXMLResource(transaction, (DocumentImpl) getOwnerDocument()); transact.commit(transaction); // bugID 3419602 return getLastChild(); } catch (Exception e) { transact.abort(transaction); throw new DOMException(DOMException.INVALID_STATE_ERR, e.getMessage()); } finally { ownerDocument.getBrokerPool().release(broker); } } public void appendAttributes(Txn transaction, NodeList attribs) throws DOMException { NodeList duplicateAttrs = findDupAttributes(attribs); removeAppendAttributes(transaction, duplicateAttrs, attribs); } private NodeList checkForAttributes(Txn transaction, NodeList nodes) throws DOMException { NodeListImpl attribs = null; NodeListImpl rest = null; for (int i = 0; i < nodes.getLength(); i++) { Node next = nodes.item(i); if (next.getNodeType() == Node.ATTRIBUTE_NODE) { if (!next.getNodeName().startsWith("xmlns")) { if (attribs == null) attribs = new NodeListImpl(); attribs.add(next); } } else if (attribs != null) { if (rest == null) rest = new NodeListImpl(); rest.add(next); } } if (attribs == null) return nodes; appendAttributes(transaction, attribs); return rest; } public void appendChildren(Txn transaction, NodeList nodes, int child) throws DOMException { // attributes are handled differently. Call checkForAttributes to extract them. nodes = checkForAttributes(transaction, nodes); if (nodes == null || nodes.getLength() == 0) return; DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); NodePath path = getPath(); StreamListener listener = null; //May help getReindexRoot() to make some useful things broker.getIndexController().setDocument(ownerDocument); StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path); broker.getIndexController().setMode(StreamListener.STORE); if (reindexRoot == null) { listener = broker.getIndexController().getStreamListener(); } else { broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); } if (children == 0) { // no children: append a new child appendChildren(transaction, nodeId.newChild(), null, new NodeImplRef(this), path, nodes, listener); } else { if (child == 1) { Node firstChild = getFirstChild(); insertBefore(transaction, nodes, firstChild); } else { if (child > 1 && child <= children) { NodeList cl = getChildNodes(); StoredNode last = (StoredNode) cl.item(child - 2); insertAfter(transaction, nodes, last); } else { StoredNode last = (StoredNode) getLastChild(); appendChildren(transaction, last.getNodeId().nextSibling(), null, new NodeImplRef(getLastNode(last)), path, nodes, listener); } } } broker.updateNode(transaction, this, false); broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); broker.flush(); } catch (EXistException e) { LOG.warn("Exception while appending child node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } } /** * Internal append. * * @throws DOMException */ protected void appendChildren(Txn transaction, NodeId newNodeId, NodeId followingId, NodeImplRef last, NodePath lastPath, NodeList nodes, StreamListener listener) throws DOMException { if (last == null || last.getNode() == null || last.getNode().getOwnerDocument() == null) throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "invalid node"); children += nodes.getLength(); for (int i = 0; i < nodes.getLength(); i++) { Node child = nodes.item(i); appendChild(transaction, newNodeId, last, lastPath, child, listener); NodeId next = newNodeId.nextSibling(); if (followingId != null && next.equals(followingId)) { next = newNodeId.insertNode(followingId); if (LOG.isDebugEnabled()) LOG.debug("Node ID collision on " + followingId + ". Using " + next + " instead."); } newNodeId = next; } } private Node appendChild(Txn transaction, NodeId newNodeId, NodeImplRef last, NodePath lastPath, Node child, StreamListener listener) throws DOMException { if (last == null || last.getNode() == null) //TODO : same test as above ? -pb throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "invalid node"); final DocumentImpl owner = (DocumentImpl)getOwnerDocument(); DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); switch (child.getNodeType()) { case Node.DOCUMENT_FRAGMENT_NODE : appendChildren(transaction, newNodeId, null, last, lastPath, child.getChildNodes(), listener); return null; // TODO: implement document fragments so we can return all newly appended children case Node.ELEMENT_NODE : // create new element final ElementImpl elem = new ElementImpl( new QName(child.getLocalName() == null ? child.getNodeName() : child.getLocalName(), child.getNamespaceURI(), child.getPrefix()) ); elem.setNodeId(newNodeId); elem.setOwnerDocument(owner); final NodeListImpl ch = new NodeListImpl(); final NamedNodeMap attribs = child.getAttributes(); int numActualAttribs = 0; for (int i = 0; i < attribs.getLength(); i++) { Attr attr = (Attr) attribs.item(i); if (!attr.getNodeName().startsWith("xmlns")) { ch.add(attr); numActualAttribs++; } else { String xmlnsDecl = attr.getNodeName(); String prefix = xmlnsDecl.length()==5 ? "" : xmlnsDecl.substring(6); elem.addNamespaceMapping(prefix,attr.getNodeValue()); } } NodeList cl = child.getChildNodes(); for (int i = 0; i < cl.getLength(); i++) { Node n = cl.item(i); if (n.getNodeType() != Node.ATTRIBUTE_NODE) ch.add(n); } elem.setChildCount(ch.getLength()); if (numActualAttribs != (short) numActualAttribs) throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "too many attributes"); elem.setAttributes((short) numActualAttribs); lastPath.addComponent(elem.getQName()); // insert the node broker.insertNodeAfter(transaction, last.getNode(), elem); broker.indexNode(transaction, elem, lastPath); broker.getIndexController().indexNode(transaction, elem, lastPath, listener); //getBroker().getIndexController().startElement(transaction, elem, lastPath, listener); elem.setChildCount(0); last.setNode(elem); //process child nodes elem.appendChildren(transaction, newNodeId.newChild(), null, last, lastPath, ch, listener); broker.endElement(elem, lastPath, null); broker.getIndexController().endElement(transaction, elem, lastPath, listener); lastPath.removeLastComponent(); return elem; case Node.TEXT_NODE : final TextImpl text = new TextImpl(newNodeId, ((Text) child).getData()); text.setOwnerDocument(owner); // insert the node broker.insertNodeAfter(transaction, last.getNode(), text); broker.indexNode(transaction, text, lastPath); broker.getIndexController().indexNode(transaction, text, lastPath, listener); //getBroker().getIndexController().characters(transaction, text, lastPath, listener); last.setNode(text); return text; case Node.CDATA_SECTION_NODE : final CDATASectionImpl cdata = new CDATASectionImpl(newNodeId, ((CDATASection) child).getData()); cdata.setOwnerDocument(owner); // insert the node broker.insertNodeAfter(transaction, last.getNode(), cdata); broker.indexNode(transaction, cdata, lastPath); last.setNode(cdata); return cdata; case Node.ATTRIBUTE_NODE: Attr attr = (Attr) child; String ns = attr.getNamespaceURI(); String prefix = (Namespaces.XML_NS.equals(ns) ? "xml" : attr.getPrefix()); String name = attr.getLocalName(); if (name == null) name = attr.getName(); QName attrName = new QName(name, ns, prefix); final AttrImpl attrib = new AttrImpl(attrName, attr.getValue()); attrib.setNodeId(newNodeId); attrib.setOwnerDocument(owner); if (ns != null && attrName.compareTo(Namespaces.XML_ID_QNAME) == Constants.EQUAL) { // an xml:id attribute. Normalize the attribute and set its type to ID attrib.setValue(StringValue.trimWhitespace(StringValue.collapseWhitespace(attrib.getValue()))); attrib.setType(AttrImpl.ID); } else attrName.setNameType(ElementValue.ATTRIBUTE); broker.insertNodeAfter(transaction, last.getNode(), attrib); broker.indexNode(transaction, attrib, lastPath); broker.getIndexController().indexNode(transaction, attrib, lastPath, listener); //getBroker().getIndexController().attribute(transaction, attrib, lastPath, listener); last.setNode(attrib); return attrib; case Node.COMMENT_NODE: final CommentImpl comment = new CommentImpl(((Comment) child).getData()); comment.setNodeId(newNodeId); comment.setOwnerDocument(owner); // insert the node broker.insertNodeAfter(transaction, last.getNode(), comment); broker.indexNode(transaction, comment, lastPath); last.setNode(comment); return comment; case Node.PROCESSING_INSTRUCTION_NODE: final ProcessingInstructionImpl pi = new ProcessingInstructionImpl(newNodeId, ((ProcessingInstruction) child).getTarget(), ((ProcessingInstruction) child).getData()); pi.setOwnerDocument(owner); // insert the node broker.insertNodeAfter(transaction, last.getNode(), pi); broker.indexNode(transaction, pi, lastPath); last.setNode(pi); return pi; default : throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "unknown node type: " + child.getNodeType() + " " + child.getNodeName()); } } catch (EXistException e) { LOG.warn("Exception while appending node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } return null; } public short getAttributesCount() { return attributes; } /** * Set the attributes that belong to this node. * * @param attribNum The new attributes value */ public void setAttributes(short attribNum) { attributes = attribNum; } /** * @see org.w3c.dom.Element#getAttribute(java.lang.String) */ public String getAttribute(String name) { Attr attr = findAttribute(name); return attr != null ? attr.getValue() : ""; } /** * @see org.w3c.dom.Element#getAttributeNS(java.lang.String, java.lang.String) */ public String getAttributeNS(String namespaceURI, String localName) { Attr attr = findAttribute(new QName(localName, namespaceURI)); return attr != null ? attr.getValue() : ""; } /** * @see org.w3c.dom.Element#getAttributeNode(java.lang.String) */ public Attr getAttributeNode(String name) { return findAttribute(name); } /** * @see org.w3c.dom.Element#getAttributeNodeNS(java.lang.String, java.lang.String) */ public Attr getAttributeNodeNS(String namespaceURI, String localName) { return findAttribute(new QName(localName, namespaceURI)); } /** * @see org.w3c.dom.Node#getAttributes() */ public NamedNodeMap getAttributes() { NamedNodeMapImpl map = new NamedNodeMapImpl(); if (getAttributesCount() > 0) { DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); final Iterator iterator = broker.getNodeIterator(this); iterator.next(); final int ccount = getChildCount(); for (int i = 0; i < ccount; i++) { StoredNode next = (StoredNode) iterator.next(); if (next.getNodeType() != Node.ATTRIBUTE_NODE) break; map.setNamedItem(next); } } catch (EXistException e) { LOG.warn("Exception while retrieving attributes: " + e.getMessage()); } finally { ownerDocument.getBrokerPool().release(broker); } } if(declaresNamespacePrefixes()) { for(Iterator i = namespaceMappings.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry) i.next(); String prefix = entry.getKey().toString(); String ns = entry.getValue().toString(); QName attrName = new QName(prefix, Namespaces.XMLNS_NS, "xmlns"); AttrImpl attr = new AttrImpl(attrName, ns); map.setNamedItem(attr); } } return map; } private AttrImpl findAttribute(String qname) { DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); final Iterator iterator = broker.getNodeIterator(this); iterator.next(); return findAttribute(qname, iterator, this); } catch (EXistException e) { LOG.warn("Exception while retrieving attributes: " + e.getMessage()); } finally { ownerDocument.getBrokerPool().release(broker); } return null; } private AttrImpl findAttribute(String qname, Iterator iterator, StoredNode current) { final int ccount = current.getChildCount(); StoredNode next; for (int i = 0; i < ccount; i++) { next = (StoredNode) iterator.next(); if (next.getNodeType() != Node.ATTRIBUTE_NODE) break; if (next.getNodeName().equals(qname)) return (AttrImpl) next; } return null; } private AttrImpl findAttribute(QName qname) { DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); final Iterator iterator = broker.getNodeIterator(this); iterator.next(); return findAttribute(qname, iterator, this); } catch (EXistException e) { LOG.warn("Exception while retrieving attributes: " + e.getMessage()); } finally { ownerDocument.getBrokerPool().release(broker); } return null; } private AttrImpl findAttribute(QName qname, Iterator iterator, StoredNode current) { final int ccount = current.getChildCount(); for (int i = 0; i < ccount; i++) { StoredNode next = (StoredNode) iterator.next(); if (next.getNodeType() != Node.ATTRIBUTE_NODE) break; if (next.getQName().equalsSimple(qname)) return (AttrImpl) next; } return null; } /** * Returns a list of all attribute nodes in attrs that are already present * in the current element. * * @param attrs * @return The attributes list * @throws DOMException */ private NodeList findDupAttributes(NodeList attrs) throws DOMException { NodeListImpl dupList = null; NamedNodeMap map = getAttributes(); for (int i = 0; i < attrs.getLength(); i++) { Node attr = attrs.item(i); // Workaround: xerces sometimes returns null for getLocalName() !!!! String localName = attr.getLocalName(); if (localName == null) localName = attr.getNodeName(); Node duplicate = map.getNamedItemNS(attr.getNamespaceURI(), localName); if (duplicate != null) { if (dupList == null) dupList = new NodeListImpl(); dupList.add(duplicate); } } return dupList; } /** * @see org.exist.dom.NodeImpl#getChildCount() */ public int getChildCount() { return children; } public NodeList getChildNodes() { final NodeListImpl childList = new NodeListImpl(1); DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); for (EmbeddedXMLStreamReader reader = broker.getXMLStreamReader(this, true); reader.hasNext(); ) { int status = reader.next(); if (status != XMLStreamReader.END_ELEMENT) { if (((NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID)).isChildOf(nodeId)) childList.add(reader.getNode()); } } } catch (IOException e) { LOG.warn("Internal error while reading child nodes: " + e.getMessage(), e); } catch (XMLStreamException e) { LOG.warn("Internal error while reading child nodes: " + e.getMessage(), e); } catch (EXistException e) { LOG.warn("Internal error while reading child nodes: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } // accept(new NodeVisitor() { // public boolean visit(StoredNode node) { // if(node.nodeId.isChildOf(nodeId)) // childList.add(node); // return true; // } // }); return childList; } /** * @see org.w3c.dom.Element#getElementsByTagName(java.lang.String) */ public NodeList getElementsByTagName(String tagName) { QName qname = new QName(tagName, "", null); return ((DocumentImpl)getOwnerDocument()).findElementsByTagName(this, qname); } /** * @see org.w3c.dom.Element#getElementsByTagNameNS(java.lang.String, java.lang.String) */ public NodeList getElementsByTagNameNS(String namespaceURI, String localName) { QName qname = new QName(localName, namespaceURI, null); return ((DocumentImpl)getOwnerDocument()).findElementsByTagName(this, qname); } /** * @see org.w3c.dom.Node#getFirstChild() */ public Node getFirstChild() { if (!hasChildNodes() || getChildCount() == getAttributesCount()) return null; DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); final Iterator iterator = broker.getNodeIterator(this); iterator.next(); StoredNode next; for (int i = 0; i < getChildCount(); i++) { next = (StoredNode) iterator.next(); if (next.getNodeType() != Node.ATTRIBUTE_NODE) return next; } } catch (EXistException e) { LOG.warn("Exception while retrieving child node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } return null; } public Node getLastChild() { if (!hasChildNodes()) return null; Node node = null; if (!isDirty) { NodeId child = nodeId.getChild(children); node = ownerDocument.getNode(new NodeProxy(ownerDocument, child)); } if (node == null) { NodeList cl = getChildNodes(); return cl.item(cl.getLength() - 1); } return node; } /** * @see org.w3c.dom.Element#getTagName() */ public String getTagName() { return nodeName.getStringValue(); } /** * @see org.w3c.dom.Element#hasAttribute(java.lang.String) */ public boolean hasAttribute(String name) { return findAttribute(name) != null; } /** * @see org.w3c.dom.Element#hasAttributeNS(java.lang.String, java.lang.String) */ public boolean hasAttributeNS(String namespaceURI, String localName) { return findAttribute(new QName(localName, namespaceURI)) != null; } /** * @see org.w3c.dom.Node#hasAttributes() */ public boolean hasAttributes() { return (getAttributesCount() > 0); } /** * @see org.w3c.dom.Node#hasChildNodes() */ public boolean hasChildNodes() { return children > 0; } /** * @see org.w3c.dom.Node#getNodeValue() */ public String getNodeValue() /*throws DOMException*/ { //TODO : parametrize the boolea value ? DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); return broker.getNodeValue(this, false); } catch (EXistException e) { LOG.warn("Exception while reading node value: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } return ""; } /** * @see org.w3c.dom.Element#removeAttribute(java.lang.String) */ public void removeAttribute(String name) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "removeAttribute(String name) not implemented on class " + getClass().getName()); } /** * @see org.w3c.dom.Element#removeAttributeNS(java.lang.String, java.lang.String) */ public void removeAttributeNS(String namespaceURI, String name) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "removeAttributeNS(String namespaceURI, String name) not implemented on class " + getClass().getName()); } public Attr removeAttributeNode(Attr oldAttr) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "removeAttributeNode(Attr oldAttr) not implemented on class " + getClass().getName()); } public void setAttribute(String name, String value) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setAttribute(String name, String value) not implemented on class " + getClass().getName()); } public void setAttributeNS(String namespaceURI, String qualifiedName, String value) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setAttributeNS(String namespaceURI, String qualifiedName, String value) not implemented on class " + getClass().getName()); } public Attr setAttributeNode(Attr newAttr) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setAttributeNode(Attr newAttr) not implemented on class " + getClass().getName()); } public Attr setAttributeNodeNS(Attr newAttr) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setAttributeNodeNS(Attr newAttr) not implemented on class " + getClass().getName()); } public void setChildCount(int count) { children = count; } public void setNamespaceMappings(Map map) { namespaceMappings = new HashMap(map); String ns; for (Iterator i = namespaceMappings.values().iterator(); i.hasNext();) { ns = (String) i.next(); ownerDocument.getBrokerPool().getSymbols().getNSSymbol(ns); } } public Iterator getPrefixes() { return namespaceMappings.keySet().iterator(); } public String getNamespaceForPrefix(String prefix) { return (String) namespaceMappings.get(prefix); } public int getPrefixCount() { return namespaceMappings.size(); } /** * @see java.lang.Object#toString() */ public String toString() { return toString(true); } /** */ public String toString(boolean top) { return toString(top, new TreeSet()); } /** * Method toString. * */ public String toString(boolean top, TreeSet namespaces) { StringBuilder buf = new StringBuilder(); StringBuilder attributes = new StringBuilder(); StringBuilder children = new StringBuilder(); buf.append('<'); buf.append(nodeName); //Remove false to have a verbose output if (top && false) { buf.append(" xmlns:exist=\""+ Namespaces.EXIST_NS + "\""); buf.append(" exist:id=\""); buf.append(getNodeId()); buf.append("\" exist:document=\""); buf.append(((DocumentImpl)getOwnerDocument()).getFileURI()); buf.append("\""); } if (declaresNamespacePrefixes()) { // declare namespaces used by this element Map.Entry entry; String namespace, prefix; for (Iterator i = namespaceMappings.entrySet().iterator(); i.hasNext();) { entry = (Map.Entry) i.next(); prefix = (String) entry.getKey(); namespace = (String) entry.getValue(); if (prefix.length() == 0) { buf.append(" xmlns=\""); //buf.append(namespace); buf.append("..."); } else { buf.append(" xmlns:"); buf.append(prefix); buf.append("=\""); //buf.append(namespace); buf.append("..."); } buf.append("\" "); namespaces.add(namespace); } } if (nodeName.getNamespaceURI().length() > 0 && (!namespaces.contains(nodeName.getNamespaceURI()))) { buf.append(" xmlns:").append(nodeName.getPrefix()).append("=\""); buf.append(nodeName.getNamespaceURI()); buf.append("\" "); } NodeList childNodes = getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); switch (child.getNodeType()) { case Node.ATTRIBUTE_NODE: attributes.append(' '); attributes.append(((Attr) child).getName()); attributes.append("=\""); attributes.append(escapeXml(child)); attributes.append("\""); break; case Node.ELEMENT_NODE: children.append(((ElementImpl) child).toString(false, namespaces)); break; default : children.append(child.toString()); } } if (attributes.length() > 0) buf.append(attributes.toString()); if (childNodes.getLength() > 0) { buf.append(">"); buf.append(children.toString()); buf.append("</"); buf.append(nodeName); buf.append(">"); } else buf.append("/>"); return buf.toString(); } /** * @see org.w3c.dom.Node#insertBefore(org.w3c.dom.Node, org.w3c.dom.Node) */ public Node insertBefore(Node newChild, Node refChild) throws DOMException { if (refChild == null) return appendChild(newChild); if (!(refChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type"); NodeListImpl nl = new NodeListImpl(); nl.add(newChild); TransactionManager transact = ownerDocument.getBrokerPool().getTransactionManager(); Txn transaction = transact.beginTransaction(); DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); insertBefore(transaction, nl, refChild); broker.storeXMLResource(transaction, (DocumentImpl) getOwnerDocument()); transact.commit(transaction); return refChild.getPreviousSibling(); } catch(TransactionException e) { transact.abort(transaction); throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, e.getMessage()); } catch (EXistException e) { transact.abort(transaction); LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } return null; } /** * Insert a list of nodes at the position before the reference * child. */ public void insertBefore(Txn transaction, NodeList nodes, Node refChild) throws DOMException { if (refChild == null) { //TODO : use NodeImpl.UNKNOWN_NODE_IMPL_GID ? -pb appendChildren(transaction, nodes, -1); return; } if (!(refChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type"); DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); NodePath path = getPath(); StreamListener listener = null; //May help getReindexRoot() to make some useful things broker.getIndexController().setDocument(ownerDocument); StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path, true); broker.getIndexController().setMode(StreamListener.STORE); if (reindexRoot == null) { listener = broker.getIndexController().getStreamListener(); } else { broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); } StoredNode following = (StoredNode) refChild; StoredNode previous = (StoredNode) following.getPreviousSibling(); if (previous == null) { // there's no sibling node before the new node NodeId newId = following.getNodeId().insertBefore(); appendChildren(transaction, newId, following.getNodeId(), new NodeImplRef(this), path, nodes, listener); } else { // insert the new node between the preceding and following sibling NodeId newId = previous.getNodeId().insertNode(following.getNodeId()); appendChildren(transaction, newId, following.getNodeId(), new NodeImplRef(getLastNode(previous)), path, nodes, listener); } setDirty(true); broker.updateNode(transaction, this, true); broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); broker.flush(); } catch (EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } } /** * Insert a list of nodes at the position following the reference * child. */ public void insertAfter(Txn transaction, NodeList nodes, Node refChild) throws DOMException { if (refChild == null) { //TODO : use NodeImpl.UNKNOWN_NODE_IMPL_GID ? -pb appendChildren(null, nodes, -1); return; } if (!(refChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type: "); DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); NodePath path = getPath(); StreamListener listener = null; //May help getReindexRoot() to make some useful things broker.getIndexController().setDocument(ownerDocument); StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path, true); broker.getIndexController().setMode(StreamListener.STORE); if (reindexRoot == null) { listener = broker.getIndexController().getStreamListener(); } else { broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); } StoredNode previous = (StoredNode) refChild; StoredNode following = (StoredNode) previous.getNextSibling(); NodeId followingId = following == null ? null : following.getNodeId(); NodeId newNodeId = previous.getNodeId().insertNode(followingId); appendChildren(transaction, newNodeId, followingId, new NodeImplRef(getLastNode(previous)), path, nodes, listener); setDirty(true); broker.updateNode(transaction, this, true); broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); broker.flush(); } catch (EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } } /** * Update the contents of this element. The passed list of nodes * becomes the new content. * * @param newContent * @throws DOMException */ public void update(Txn transaction, NodeList newContent) throws DOMException { final NodePath path = getPath(); // remove old child nodes NodeList nodes = getChildNodes(); StreamListener listener = null; DBBroker broker = null; //May help getReindexRoot() to make some useful things try { broker = ownerDocument.getBrokerPool().get(null); broker.getIndexController().setDocument(ownerDocument); StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path, true); broker.getIndexController().setMode(StreamListener.REMOVE_SOME_NODES); if (reindexRoot == null) { listener = broker.getIndexController().getStreamListener(); } else { broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.REMOVE_SOME_NODES); } // TODO: fix once range index has been moved to new architecture StoredNode valueReindexRoot = broker.getValueIndex().getReindexRoot(this, path); broker.getValueIndex().reindex(valueReindexRoot); StoredNode last = this; int i = nodes.getLength(); for (; i > 0; i--) { StoredNode child = (StoredNode) nodes.item(i - 1); if (child.getNodeType() == Node.ATTRIBUTE_NODE) { last = child; break; } if (child.getNodeType() == Node.ELEMENT_NODE) path.addComponent(child.getQName()); broker.removeAllNodes(transaction, child, path, listener); if (child.getNodeType() == Node.ELEMENT_NODE) path.removeLastComponent(); } broker.getIndexController().flush(); broker.getIndexController().setMode(StreamListener.STORE); broker.getIndexController().getStreamListener(); broker.endRemove(transaction); children = i; NodeId newNodeId = last == this ? nodeId.newChild() : last.nodeId.nextSibling(); // append new content appendChildren(transaction, newNodeId, null, new NodeImplRef(last), path, newContent, listener); broker.updateNode(transaction, this, false); broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); broker.getValueIndex().reindex(valueReindexRoot); broker.flush(); } catch (EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } } /** * Update a child node. This method will only update the child node * but not its potential descendant nodes. * * @param oldChild * @param newChild * @throws DOMException */ public StoredNode updateChild(Txn transaction, Node oldChild, Node newChild) throws DOMException { if (!(oldChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type"); if (!(newChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type"); StoredNode oldNode = (StoredNode) oldChild; StoredNode newNode = (StoredNode) newChild; if (!oldNode.nodeId.getParentId().equals(nodeId)) throw new DOMException(DOMException.NOT_FOUND_ERR, "node is not a child of this element"); if (newNode.getNodeType() == Node.ATTRIBUTE_NODE) { if (newNode.getQName().equalsSimple(Namespaces.XML_ID_QNAME)) { // an xml:id attribute. Normalize the attribute and set its type to ID AttrImpl attr = (AttrImpl) newNode; attr.setValue(StringValue.trimWhitespace(StringValue.collapseWhitespace(attr.getValue()))); attr.setType(AttrImpl.ID); } } StoredNode previousNode = (StoredNode) oldNode.getPreviousSibling(); if (previousNode == null) previousNode = this; else previousNode = getLastNode(previousNode); final NodePath currentPath = getPath(); final NodePath oldPath = oldNode.getPath(currentPath); DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); //May help getReindexRoot() to make some useful things broker.getIndexController().setDocument(ownerDocument); // check if the change affects any ancestor nodes, which then need to be reindexed later StoredNode reindexRoot = broker.getIndexController().getReindexRoot(oldNode, oldPath); // Remove indexes if (reindexRoot == null) reindexRoot = oldNode; broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.REMOVE_SOME_NODES); // TODO: fix once range index has been moved to new architecture StoredNode valueReindexRoot = broker.getValueIndex().getReindexRoot(this, oldPath); broker.getValueIndex().reindex(valueReindexRoot); // Remove the actual node data broker.removeNode(transaction, oldNode, oldPath, null); broker.endRemove(transaction); newNode.nodeId = oldNode.nodeId; // Reinsert the new node data broker.insertNodeAfter(transaction, previousNode, newNode); final NodePath path = newNode.getPath(currentPath); broker.indexNode(transaction, newNode, path); if (newNode.getNodeType() == Node.ELEMENT_NODE) broker.endElement(newNode, path, null); broker.updateNode(transaction, this, true); // Recreate indexes on ancestor nodes broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); broker.getValueIndex().reindex(valueReindexRoot); broker.flush(); } catch (EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } return newNode; } /** * @see org.w3c.dom.Node#removeChild(org.w3c.dom.Node) */ public Node removeChild(Txn transaction, Node oldChild) throws DOMException { if (!(oldChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type"); final StoredNode oldNode = (StoredNode) oldChild; if (!oldNode.nodeId.getParentId().equals(nodeId)) throw new DOMException(DOMException.NOT_FOUND_ERR, "node is not a child of this element"); NodePath oldPath = oldNode.getPath(); StreamListener listener = null; DBBroker broker = null; try { //May help getReindexRoot() to make some useful things broker = ownerDocument.getBrokerPool().get(null); broker.getIndexController().setDocument(ownerDocument); StoredNode reindexRoot = broker.getIndexController().getReindexRoot(oldNode, oldPath); broker.getIndexController().setMode(StreamListener.REMOVE_SOME_NODES); if (reindexRoot == null) { listener = broker.getIndexController().getStreamListener(); } else { broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.REMOVE_SOME_NODES); } broker.removeAllNodes(transaction, oldNode, oldPath, listener); --children; if (oldChild.getNodeType() == Node.ATTRIBUTE_NODE) --attributes; broker.endRemove(transaction); setDirty(true); broker.updateNode(transaction, this, false); broker.flush(); broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); } catch (EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } return oldNode; } public void removeAppendAttributes(Txn transaction, NodeList removeList, NodeList appendList) { DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); if (removeList != null) { try { for (int i=0; i<removeList.getLength(); i++) { Node oldChild = removeList.item(i); if (!(oldChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type"); StoredNode old = (StoredNode) oldChild; if (!old.nodeId.isChildOf(nodeId)) throw new DOMException(DOMException.NOT_FOUND_ERR, "node " + old.nodeId.getParentId() + " is not a child of element " + nodeId); final NodePath oldPath = old.getPath(); // remove old custom indexes broker.getIndexController().reindex(transaction, old, StreamListener.REMOVE_SOME_NODES); broker.removeNode(transaction, old, oldPath, null); children--; attributes--; } } finally { broker.endRemove(transaction); } } NodePath path = getPath(); broker.getIndexController().setDocument(ownerDocument, StreamListener.STORE); StreamListener listener = broker.getIndexController().getStreamListener(); if (children == 0) { appendChildren(transaction, nodeId.newChild(), null, new NodeImplRef(this), path, appendList, listener); } else { if (attributes == 0) { StoredNode firstChild = (StoredNode) getFirstChild(); NodeId newNodeId = firstChild.nodeId.insertBefore(); appendChildren(transaction, newNodeId, firstChild.getNodeId(), new NodeImplRef(this), path, appendList, listener); } else { AttribVisitor visitor = new AttribVisitor(); accept(visitor); NodeId firstChildId = visitor.firstChild == null ? null : visitor.firstChild.nodeId; NodeId newNodeId = visitor.lastAttrib.nodeId.insertNode(firstChildId); appendChildren(transaction, newNodeId, firstChildId, new NodeImplRef(visitor.lastAttrib), path, appendList, listener); } setDirty(true); } attributes += appendList.getLength(); } catch (EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { broker.updateNode(transaction, this, true); broker.flush(); ownerDocument.getBrokerPool().release(broker); } } @Deprecated private class AttribVisitor implements NodeVisitor { private StoredNode lastAttrib = null; private StoredNode firstChild = null; public boolean visit(StoredNode node) { if (node.getNodeType() == Node.ATTRIBUTE_NODE) { lastAttrib = node; } else if (node.nodeId.isChildOf(ElementImpl.this.nodeId)) { firstChild = node; return false; } return true; } } /** * Replaces the oldNode with the newChild * * @param transaction * @param newChild * @param oldChild * * @return The new node (this differs from the {@link org.w3c.dom.Node#replaceChild(Node, Node)} specification) * * @see org.w3c.dom.Node#replaceChild(org.w3c.dom.Node, org.w3c.dom.Node) */ public Node replaceChild(Txn transaction, Node newChild, Node oldChild) throws DOMException { if (!(oldChild instanceof StoredNode)) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type"); StoredNode oldNode = (StoredNode) oldChild; if (!oldNode.nodeId.getParentId().equals(nodeId)) throw new DOMException(DOMException.NOT_FOUND_ERR, "node is not a child of this element"); StoredNode previous = (StoredNode) oldNode.getPreviousSibling(); if (previous == null) previous = this; else previous = getLastNode(previous); NodePath oldPath = oldNode.getPath(); StreamListener listener = null; //May help getReindexRoot() to make some useful things Node newNode = null; DBBroker broker = null; try { broker = ownerDocument.getBrokerPool().get(null); broker.getIndexController().setDocument(ownerDocument); StoredNode reindexRoot = broker.getIndexController().getReindexRoot(oldNode, oldPath); broker.getIndexController().setMode(StreamListener.REMOVE_SOME_NODES); if (reindexRoot == null) { listener = broker.getIndexController().getStreamListener(); } else { broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.REMOVE_SOME_NODES); } broker.removeAllNodes(transaction, oldNode, oldPath, listener); broker.endRemove(transaction); broker.flush(); broker.getIndexController().setMode(StreamListener.STORE); listener = broker.getIndexController().getStreamListener(); newNode = appendChild(transaction, oldNode.nodeId, new NodeImplRef(previous), getPath(), newChild, listener); // reindex if required final DocumentImpl owner = (DocumentImpl)getOwnerDocument(); broker.storeXMLResource(transaction, owner); broker.updateNode(transaction, this, false); broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE); broker.flush(); } catch (EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); } finally { ownerDocument.getBrokerPool().release(broker); } //return oldChild; // method is spec'd to return the old child, even though that's probably useless in this case return newNode; //returning the newNode is more sensible than returning the oldNode } private String escapeXml(Node child) { final String str = ((Attr) child).getValue(); StringBuilder buffer = null; String entity = null; char ch; for (int i = 0; i < str.length(); i++) { ch = str.charAt(i); switch (ch) { case '"': entity = """; break; case '<': entity = "<"; break; case '>': entity = ">"; break; case '\'': entity = "'"; break; default : entity = null; break; } if (buffer == null) { if (entity != null) { buffer = new StringBuilder(str.length() + 20); buffer.append(str.substring(0, i)); buffer.append(entity); } } else { if (entity == null) buffer.append(ch); else buffer.append(entity); } } return (buffer == null) ? str : buffer.toString(); } public void setPreserveSpace(boolean preserveWS) { this.preserveWS = preserveWS; } public boolean preserveSpace() { return preserveWS; } /** ? @see org.w3c.dom.Element#getSchemaTypeInfo() */ public TypeInfo getSchemaTypeInfo() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getSchemaTypeInfo() not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Element#setIdAttribute(java.lang.String, boolean) */ public void setIdAttribute(String name, boolean isId) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setIdAttribute(String name, boolean isId) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Element#setIdAttributeNS(java.lang.String, java.lang.String, boolean) */ public void setIdAttributeNS(String namespaceURI, String localName, boolean isId) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setIdAttributeNS(String namespaceURI, String localName, boolean isId) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Element#setIdAttributeNode(org.w3c.dom.Attr, boolean) */ public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setIdAttributeNode(Attr idAttr, boolean isId) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#getBaseURI() */ public String getBaseURI() { String baseURI = getAttributeNS(Namespaces.XML_NS, "base"); if ( baseURI == null) { baseURI = ""; } StoredNode parent = getParentStoredNode(); while (parent != null && parent.getBaseURI() != null) { if ("".equals(baseURI)) { baseURI = parent.getBaseURI(); } else { baseURI = parent.getBaseURI() + "/" + baseURI; } parent = parent.getParentStoredNode(); if (parent == null) return baseURI; } if ("".equals(baseURI)) { baseURI = getDocument().getBaseURI(); } return baseURI; } /** ? @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node) */ public short compareDocumentPosition(Node other) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "compareDocumentPosition(Node other) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#getTextContent() */ public String getTextContent() throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getTextContent() not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#setTextContent(java.lang.String) */ public void setTextContent(String textContent) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setTextContent(String textContent) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#isSameNode(org.w3c.dom.Node) */ public boolean isSameNode(Node other) { // This function is used by Saxon in some circumstances, and this partial implementation is required for proper Saxon operation. if( other instanceof StoredNode ) { return( this.nodeId == ((StoredNode)other).nodeId && this.ownerDocument.getDocId() == ((StoredNode)other).ownerDocument.getDocId() ); } throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "isSameNode(Node other) not implemented on other class " + other.getClass().getName()); } /** ? @see org.w3c.dom.Node#lookupPrefix(java.lang.String) */ public String lookupPrefix(String namespaceURI) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "lookupPrefix(String namespaceURI) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#isDefaultNamespace(java.lang.String) */ public boolean isDefaultNamespace(String namespaceURI) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "isDefaultNamespace(String namespaceURI) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#lookupNamespaceURI(java.lang.String) */ public String lookupNamespaceURI(String prefix) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "lookupNamespaceURI(String prefix) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#isEqualNode(org.w3c.dom.Node) */ public boolean isEqualNode(Node arg) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "isEqualNode(Node arg) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#getFeature(java.lang.String, java.lang.String) */ public Object getFeature(String feature, String version) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getFeature(String feature, String version) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#setUserData(java.lang.String, java.lang.Object, org.w3c.dom.UserDataHandler) */ public Object setUserData(String key, Object data, UserDataHandler handler) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setUserData(String key, Object data, UserDataHandler handler) not implemented on class " + getClass().getName()); } /** ? @see org.w3c.dom.Node#getUserData(java.lang.String) */ public Object getUserData(String key) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getUserData(String key) not implemented on class " + getClass().getName()); } public boolean accept(Iterator iterator, NodeVisitor visitor) { if (!visitor.visit(this)) return false; if (hasChildNodes()) { final int ccount = getChildCount(); StoredNode next; for (int i = 0; i < ccount; i++) { next = (StoredNode) iterator.next(); if (!next.accept(iterator, visitor)) return false; } } return true; } }