/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.xml2; import com.caucho.vfs.Depend; import com.caucho.vfs.Path; import org.w3c.dom.*; import javax.xml.namespace.QName; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; /** * Implements the top-level document for the XML tree. */ public class QDocument extends QDocumentFragment implements CauchoDocument { QDOMImplementation _implementation; QDocumentType _dtd; QElement _element; // top HashMap<String,String> _attributes; String _encoding = "UTF-8"; String _version; private String _systemId; private HashMap<String,String> _namespaces; private transient HashMap<NameKey,QName> _nameCache = new HashMap<NameKey,QName>(); private transient NameKey _nameKey = new NameKey(); private transient ArrayList<Path> _depends; private transient ArrayList<Depend> _dependList; int _changeCount; // possibly different from the systemId if the DOCTYPE doesn't match // the actual file location String _rootFilename; private boolean _standalone; public QDocument() { _implementation = new QDOMImplementation(); _owner = this; } public QDocument(DocumentType docType) { _owner = this; setDoctype(docType); } public QDocument(QDOMImplementation impl) { _implementation = impl; _owner = this; } void setAttribute(String name, String value) { if (name.equals("version")) _version = value; else if (name.equals("encoding")) _encoding = value; else { if (_attributes == null) _attributes = new HashMap<String,String>(); _attributes.put(name, value); } } public String getRootFilename() { return _rootFilename; } public void setRootFilename(String filename) { _rootFilename = filename; } public void setSystemId(String systemId) { _systemId = systemId; } public String getSystemId() { return _systemId; } /** * Returns the base URI of the node. */ public String getBaseURI() { return getSystemId(); } public Document getOwnerDocument() { return null; } public DOMConfiguration getDomConfig() { return null; } public boolean isSupported(String feature, String version) { return _owner.getImplementation().hasFeature(feature, version); } /** * The node name for the document is #document. */ public String getNodeName() { return "#document"; } public short getNodeType() { return DOCUMENT_NODE; } protected Node copyNode(QDocument newNode, boolean deep) { newNode._dtd = _dtd; newNode._element = _element; return newNode; } /** * Returns a clone of the document. * * @param deep if true, recursively copy the document. */ public Node cloneNode(boolean deep) { QDocument newDoc = new QDocument(); newDoc._implementation = _implementation; newDoc._dtd = _dtd; if (_attributes != null) newDoc._attributes = (HashMap) _attributes.clone(); newDoc._encoding = _encoding; newDoc._version = _version; if (_namespaces != null) newDoc._namespaces = (HashMap) _namespaces.clone(); if (deep) { for (Node node = getFirstChild(); node != null; node = node.getNextSibling()) { newDoc.appendChild(newDoc.importNode(node, true)); } } return newDoc; } Node importNode(QDocument doc, boolean deep) { return null; } /** * Imports a copy of a node into the current document. * * @param node the node to import/copy * @param deep if true, recursively copy the children. * * @return the new imported node. */ public Node importNode(Node node, boolean deep) { if (node == null) return null; QName name; switch (node.getNodeType()) { case ELEMENT_NODE: return importElement((Element) node, deep); case ATTRIBUTE_NODE: Attr attr = (Attr) node; name = createName(attr.getNamespaceURI(), attr.getNodeName()); QAttr newAttr = new QAttr(name, attr.getNodeValue()); newAttr._owner = this; return newAttr; case TEXT_NODE: QText newText = new QText(node.getNodeValue()); newText._owner = this; return newText; case CDATA_SECTION_NODE: QCdata newCData = new QCdata(node.getNodeValue()); newCData._owner = this; return newCData; case ENTITY_REFERENCE_NODE: QEntityReference newER = new QEntityReference(node.getNodeName()); newER._owner = this; return newER; case ENTITY_NODE: Entity oldEntity = (Entity) node; QEntity newEntity = new QEntity(oldEntity.getNodeName(), oldEntity.getNodeValue(), oldEntity.getPublicId(), oldEntity.getSystemId()); newEntity._owner = this; return newEntity; case PROCESSING_INSTRUCTION_NODE: QProcessingInstruction newPI; newPI = new QProcessingInstruction(node.getNodeName(), node.getNodeValue()); newPI._owner = this; return newPI; case COMMENT_NODE: QComment newComment = new QComment(node.getNodeValue()); newComment._owner = this; return newComment; case DOCUMENT_FRAGMENT_NODE: return importFragment((DocumentFragment) node, deep); default: throw new UnsupportedOperationException(String.valueOf(node)); } } /** * Imports an element. */ private Element importElement(Element elt, boolean deep) { QElement newElt = new QElement(createName(elt.getNamespaceURI(), elt.getNodeName())); QElement oldElt = null; if (elt instanceof QElement) oldElt = (QElement) elt; newElt._owner = this; if (oldElt != null) { newElt._filename = oldElt._filename; newElt._line = oldElt._line; } NamedNodeMap attrs = elt.getAttributes(); int len = attrs.getLength(); for (int i = 0; i < len; i++) { Attr attr = (Attr) attrs.item(i); newElt.setAttributeNode((Attr) importNode(attr, deep)); } if (! deep) return newElt; for (Node node = elt.getFirstChild(); node != null; node = node.getNextSibling()) { newElt.appendChild(importNode(node, true)); } return newElt; } /** * Imports an element. */ private DocumentFragment importFragment(DocumentFragment elt, boolean deep) { QDocumentFragment newFrag = new QDocumentFragment(); newFrag._owner = this; if (! deep) return newFrag; for (Node node = elt.getFirstChild(); node != null; node = node.getNextSibling()) { newFrag.appendChild(importNode(node, true)); } return newFrag; } public DocumentType getDoctype() { return _dtd; } public void setDoctype(DocumentType dtd) { QDocumentType qdtd = (QDocumentType) dtd; _dtd = qdtd; if (qdtd != null) qdtd._owner = this; } public String getEncoding() { if (_encoding == null) return null; else return _encoding; } public DOMImplementation getImplementation() { return _implementation; } public Element getDocumentElement() { return _element; } public void setDocumentElement(Element elt) { _element = (QElement) elt; } /** * Creates a new element */ public Element createElement(String tagName) throws DOMException { if (! isNameValid(tagName)) throw new QDOMException(DOMException.INVALID_CHARACTER_ERR, "illegal tag `" + tagName + "'"); QElement elt = new QElement(createName(null, tagName)); elt._owner = this; return elt; } /** * Creates a new namespace-aware element */ public Element createElementNS(String namespaceURI, String name) throws DOMException { QName qname = createName(namespaceURI, name); validateName(qname); addNamespace(qname); QElement elt = new QElement(qname); elt._owner = this; return elt; } public void validateName(QName qname) throws DOMException { String prefix = qname.getPrefix(); String namespaceURI = qname.getNamespaceURI(); if (qname.getPrefix() == "") { } else if (prefix == "xml" && namespaceURI != "http://www.w3.org/XML/1998/namespace") throw new DOMException(DOMException.NAMESPACE_ERR, L.l("`xml' prefix expects namespace uri 'http://www.w3.org/XML/1998/namespace'")); else if (prefix != "" && prefix != null && namespaceURI == null) throw new DOMException(DOMException.NAMESPACE_ERR, L.l("`{0}' prefix expects a namespace uri", prefix)); } /** * Creates a new namespace-aware element */ public Element createElement(String prefix, String local, String url) throws DOMException { QName name = new QName(prefix, local, url); addNamespace(name); QElement elt = new QElement(name); elt._owner = this; return elt; } public Element createElementByName(QName name) throws DOMException { QElement elt = new QElement(name); elt._owner = this; return elt; } /** * Creates a new document fragment. */ public DocumentFragment createDocumentFragment() { QDocumentFragment frag = new QDocumentFragment(); frag._owner = this; return frag; } /** * Creates a new text node in this document. */ public Text createTextNode(String data) { if (data == null) data = ""; QText text = new QText(data); text._owner = this; return text; } public Text createUnescapedTextNode(String data) { if (data == null) data = ""; QText text = new QUnescapedText(data); text._owner = this; return text; } public Comment createComment(String data) { if (data == null) data = ""; QComment comment = new QComment(data); comment._owner = this; return comment; } public CDATASection createCDATASection(String data) { if (data == null) data = ""; QCdata cdata = new QCdata(data); cdata._owner = this; return cdata; } public ProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException { if (target == null || target.length() == 0) throw new QDOMException(DOMException.INVALID_CHARACTER_ERR, L.l("Empty processing instruction name. The processing instruction syntax is: <?name ... ?>")); if (! isNameValid(target)) throw new QDOMException(DOMException.INVALID_CHARACTER_ERR, L.l("`{0}' is an invalid processing instruction name. The processing instruction syntax is: <?name ... ?>", target)); if (data == null) data = ""; QProcessingInstruction pi = new QProcessingInstruction(target, data); pi._owner = this; return pi; } public Attr createAttribute(String name, String value) throws DOMException { if (! isNameValid(name)) throw new QDOMException(DOMException.INVALID_CHARACTER_ERR, "illegal attribute `" + name + "'"); if (value == null) value = ""; QAttr attr = new QAttr(new QName(null, name, null), value); attr._owner = this; return attr; } public Attr createAttribute(String name) throws DOMException { return createAttribute(name, null); } /** * Creates a new namespace-aware attribute */ public Attr createAttribute(String prefix, String local, String url) throws DOMException { QName name = new QName(prefix, local, url); if (url != null && ! url.equals("")) addNamespace(prefix, url); QAttr attr = new QAttr(name, null); attr._owner = this; return attr; } /** * Creates a new namespace-aware attribute */ public Attr createAttributeNS(String namespaceURI, String qualifiedName) throws DOMException { QName qname = createName(namespaceURI, qualifiedName); validateName(qname); addNamespace(qname); /* xml/0213 else if (name.getNamespace() == "") throw new DOMException(DOMException.NAMESPACE_ERR, L.l("`{0}' prefix expects a namespace uri", name.getPrefix())); */ QAttr attr = new QAttr(qname, null); attr._owner = this; return attr; } public QName createName(String uri, String name) { _nameKey.init(name, uri); QName qName = _nameCache.get(_nameKey); if (qName != null) return qName; if (uri == null) { qName = new QName(null, name, null); } else { int p = name.indexOf(':'); String prefix; String local; if (p < 0) { prefix = null; local = name; } else { prefix = name.substring(0, p); local = name.substring(p + 1); } qName = new QName(prefix, local, uri); } _nameCache.put(new NameKey(name, uri), qName); return qName; } /** * Creates a new namespace-aware attribute */ public Attr createAttribute(QName name, String value) throws DOMException { String url = name.getNamespaceURI(); if (url != null && url != "") { addNamespace(name.getPrefix(), url); } QAttr attr = new QAttr(name, value); attr._owner = this; return attr; } public EntityReference createEntityReference(String name) throws DOMException { if (! isNameValid(name)) throw new QDOMException(DOMException.INVALID_CHARACTER_ERR, "illegal entityReference `" + name + "'"); QEntityReference er = new QEntityReference(name); er._owner = this; return er; } /** * Returns a list of elements, filtered by the tag name. */ public NodeList getElementsByTagName(String name) { if (_element == null) return new QDeepNodeList(null, null, null); else return new QDeepNodeList(_element, _element, new QElement.TagPredicate(name)); } public NodeList getElementsByTagNameNS(String uri, String name) { if (_element == null) return new QDeepNodeList(null, null, null); else return new QDeepNodeList(_element, _element, new QElement.NSTagPredicate(uri, name)); } public Element getElementById(String name) { Node node = _element; for (; node != null; node = XmlUtil.getNext(node)) { if (node instanceof Element) { Element elt = (Element) node; String id = elt.getAttribute("id"); if (name.equals(id)) return elt; } } return null; } static public Document create() { QDocument doc = new QDocument(); doc._masterDoc = doc; return doc; } void setAttributes(HashMap<String,String> attributes) { _attributes = attributes; } public Node appendChild(Node newChild) throws DOMException { if (newChild instanceof Element) { _element = (QElement) newChild; // xml/0201 if (false && _namespaces != null) { Iterator<String> iter = _namespaces.keySet().iterator(); while (iter.hasNext()) { String prefix = iter.next(); String ns = _namespaces.get(prefix); String xmlns; if (prefix.equals("")) xmlns = "xmlns"; else xmlns = "xmlns:" + prefix; if (_element.getAttribute(xmlns).equals("")) { QName qName = new QName(xmlns, XmlParser.XMLNS); _element.setAttributeNode(createAttribute(qName, ns)); } } } } return super.appendChild(newChild); } public Node removeChild(Node oldChild) throws DOMException { Node value = super.removeChild(oldChild); if (oldChild == _element) _element = null; return value; } // non-DOM public void addNamespace(QName qname) { addNamespace(qname.getPrefix(), qname.getNamespaceURI()); } /** * Add a namespace declaration to a document. If the declaration * prefix already has a namespace, the old one wins. */ public void addNamespace(String prefix, String url) { if (url == null || url.length() == 0 || XmlParser.XMLNS.equals(url) || XmlParser.XML.equals(url)) { return; } if (prefix == null) prefix = ""; if (_namespaces == null) _namespaces = new HashMap<String,String>(); String old = _namespaces.get(prefix); if (old == null) _namespaces.put(prefix, url.intern()); } public HashMap<String,String> getNamespaces() { return _namespaces; } /** * Returns the namespace url for a given prefix. */ public String getNamespace(String prefix) { if (_namespaces == null) return null; else return _namespaces.get(prefix); } /** * Returns an iterator of top-level namespace prefixes. */ public Iterator<String> getNamespaceKeys() { if (_namespaces == null) return null; return _namespaces.keySet().iterator(); } public Object getProperty(String name) { if (name.equals(DEPENDS)) return _depends; else return null; } public ArrayList<Path> getDependList() { return _depends; } public ArrayList<Depend> getDependencyList() { return _dependList; } public void setProperty(String name, Object value) { if (name.equals(DEPENDS)) _depends = (ArrayList) value; } // DOM LEVEL 3 public String getActualEncoding() { throw new UnsupportedOperationException(); } public void setActualEncoding(String actualEncoding) { throw new UnsupportedOperationException(); } /* public String getEncoding() { throw new UnsupportedOperationException(); } */ public void setEncoding(String encoding) { throw new UnsupportedOperationException(); } public boolean getStandalone() { return _standalone; } public void setStandalone(boolean standalone) { _standalone = true; } public String getXmlVersion() { return _version; } public void setXmlVersion(String version) throws DOMException { _version = version; } public void setXmlStandalone(boolean value) throws DOMException { } public TypeInfo getSchemaTypeInfo() { return null; } public String getXmlEncoding() { return null; } public String getInputEncoding() { return null; } public boolean getXmlStandalone() throws DOMException { return false; } public boolean getStrictErrorChecking() { throw new UnsupportedOperationException(); } public void setStrictErrorChecking(boolean strictErrorChecking) { throw new UnsupportedOperationException(); } public DOMErrorHandler getErrorHandler() { throw new UnsupportedOperationException(); } public void setErrorHandler(DOMErrorHandler errorHandler) { throw new UnsupportedOperationException(); } public String getDocumentURI() { throw new UnsupportedOperationException(); } public void setDocumentURI(String documentURI) { throw new UnsupportedOperationException(); } public Node adoptNode(Node source) throws DOMException { throw new UnsupportedOperationException(); } public void normalizeDocument() { throw new UnsupportedOperationException(); } public boolean canSetNormalizationFeature(String name, boolean state) { throw new UnsupportedOperationException(); } public void setNormalizationFeature(String name, boolean state) throws DOMException { throw new UnsupportedOperationException(); } public boolean getNormalizationFeature(String name) throws DOMException { throw new UnsupportedOperationException(); } public Node renameNode(Node n, String namespaceURI, String name) throws DOMException { throw new UnsupportedOperationException(); } // CAUCHO public void addDepend(Path path) { if (path == null) return; if (_depends == null) _depends = new ArrayList<Path>(); if (! _depends.contains(path)) { _depends.add(path); if (_dependList == null) _dependList = new ArrayList<Depend>(); _dependList.add(new Depend(path)); } } public boolean isModified() { if (_dependList == null) return false; for (int i = 0; i < _dependList.size(); i++) { Depend depend = _dependList.get(i); if (depend.isModified()) return true; } return false; } void print(XmlPrinter os) throws IOException { os.startDocument(this); if (_namespaces != null) { Iterator<String> iter = _namespaces.keySet().iterator(); while (iter.hasNext()) { String prefix = iter.next(); String url = _namespaces.get(prefix); if (prefix.equals("")) os.attribute(null, prefix, "xmlns", url); else os.attribute(null, prefix, "xmlns:" + prefix, url); } } if (getFirstChild() == null) os.printHeader(null); for (Node node = getFirstChild(); node != null; node = node.getNextSibling()) { ((QAbstractNode) node).print(os); if (os.isPretty()) os.println(); } os.endDocument(); } public String toString() { String topElt = _element == null ? "XXX:top" : _element.getNodeName(); if (_dtd == null) return "Document[" + topElt + "]"; if (_dtd.getPublicId() != null && _dtd.getSystemId() != null) return ("Document[" + topElt + " PUBLIC '" + _dtd.getPublicId() + "' '" + _dtd.getSystemId() + "']"); else if (_dtd._publicId != null) return "Document[" + topElt + " PUBLIC '" + _dtd.getPublicId() + "']"; else if (_dtd.getSystemId() != null) return "Document[" + topElt + " SYSTEM '" + _dtd.getSystemId() + "']"; else return "Document[" + topElt + "]"; } static class NameKey { String _qName; String _url; NameKey() { } NameKey(String qName, String url) { init(qName, url); } void init(String qName, String url) { if (qName == null) throw new NullPointerException(); if (url == null) url = ""; _qName = qName; _url = url; } public int hashCode() { return 65521 * _url.hashCode() + _qName.hashCode(); } public boolean equals(Object b) { if (! (b instanceof NameKey)) return false; NameKey key = (NameKey) b; return _qName.equals(key._qName) && _url.equals(key._url); } } private Object writeReplace() { return new SerializedXml(this); } }