diff options
author | Jesse Wilson <jessewilson@google.com> | 2010-02-19 09:22:21 -0800 |
---|---|---|
committer | Jesse Wilson <jessewilson@google.com> | 2010-02-19 09:22:21 -0800 |
commit | 1ec94feeb09591c30996c7c0834d6f131e204922 (patch) | |
tree | 28de8c0740a3032d3a5bd6c3a9414e448f9dd7b7 /xml/src/main/java | |
parent | 3fe3b27d0af854f28fc8de612271c0ca9cd6a8f7 (diff) | |
download | libcore-1ec94feeb09591c30996c7c0834d6f131e204922.zip libcore-1ec94feeb09591c30996c7c0834d6f131e204922.tar.gz libcore-1ec94feeb09591c30996c7c0834d6f131e204922.tar.bz2 |
Filling in some gaps in our XML DOM v3 API.
Specifically, these methods on Node:
- setTextContent()
- isSameNode()
- lookupPrefix()
- lookupNamespaceURI()
In order to implement the last 2 I needed to fix our KXml parser
to include namespace attributes (ie. xmlns) in the pulled document.
Previously these were being elided.
Added a new testcase to verify our behaviour. It passes the RI. On
Dalvik we have a small issue with entity declarations.
Added a new testcase to verify Node.getBaseURI(). This test fails
because the method isn't implemented. Part of this test required
moving a method out to Support_Resources.java; in order to verify
the BaseURI the XML must be read from a file and not a stream (so
that path information exists).
Also...
- Style cleanup: changing static calls to look like static calls.
- Efficiency: avoiding concatenating with "" when unnecessary
- Duplication: sharing prefix validation between attributes and elements
- Renaming NodeTests to NodeTest for vogar-friendliness
Outstanding:
- I need to write a test for setTextContent().
Diffstat (limited to 'xml/src/main/java')
6 files changed, 291 insertions, 68 deletions
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java index 59a8b78..c071899 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java @@ -73,7 +73,7 @@ public class AttrImpl extends NodeImpl implements Attr { throw new DOMException(DOMException.NAMESPACE_ERR, localName); } - if (!document.isXMLIdentifier(localName)) { + if (!DocumentImpl.isXMLIdentifier(localName)) { throw new DOMException(DOMException.INVALID_CHARACTER_ERR, localName); } @@ -90,11 +90,11 @@ public class AttrImpl extends NodeImpl implements Attr { String prefix = name.substring(0, prefixSeparator); String localName = name.substring(prefixSeparator + 1); - if (!document.isXMLIdentifier(prefix) || !document.isXMLIdentifier(localName)) { + if (!DocumentImpl.isXMLIdentifier(prefix) || !DocumentImpl.isXMLIdentifier(localName)) { throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name); } } else { - if (!document.isXMLIdentifier(name)) { + if (!DocumentImpl.isXMLIdentifier(name)) { throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name); } } @@ -108,7 +108,9 @@ public class AttrImpl extends NodeImpl implements Attr { } public String getName() { - return (prefix != null ? prefix + ":" : "") + localName; + return prefix != null + ? prefix + ":" + localName + : localName; } @Override @@ -154,22 +156,7 @@ public class AttrImpl extends NodeImpl implements Attr { @Override public void setPrefix(String prefix) { - if (!namespaceAware) { - throw new DOMException(DOMException.NAMESPACE_ERR, prefix); - } - - if (prefix != null) { - if (namespaceURI == null - || !DocumentImpl.isXMLIdentifier(prefix) - || ("xmlns".equals(prefix) - && !"http://www.w3.org/2000/xmlns/".equals(namespaceURI)) - || ("xml".equals(prefix) - && !"http://www.w3.org/XML/1998/namespace".equals(namespaceURI))) { - throw new DOMException(DOMException.NAMESPACE_ERR, prefix); - } - } - - this.prefix = prefix; + this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI); } public void setValue(String value) throws DOMException { diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java index 230e444..df1383d 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java @@ -65,7 +65,7 @@ public class ElementImpl extends InnerNodeImpl implements Element { qualifiedName = qualifiedName.substring(p + 1); } - if (!document.isXMLIdentifier(qualifiedName)) { + if (!DocumentImpl.isXMLIdentifier(qualifiedName)) { throw new DOMException(DOMException.INVALID_CHARACTER_ERR, qualifiedName); } @@ -82,11 +82,11 @@ public class ElementImpl extends InnerNodeImpl implements Element { String prefix = name.substring(0, p); String localName = name.substring(p + 1); - if (!document.isXMLIdentifier(prefix) || !document.isXMLIdentifier(localName)) { + if (!DocumentImpl.isXMLIdentifier(prefix) || !DocumentImpl.isXMLIdentifier(localName)) { throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name); } } else { - if (!document.isXMLIdentifier(name)) { + if (!DocumentImpl.isXMLIdentifier(name)) { throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name); } } @@ -241,7 +241,9 @@ public class ElementImpl extends InnerNodeImpl implements Element { } public String getTagName() { - return (prefix != null ? prefix + ":" : "") + localName; + return prefix != null + ? prefix + ":" + localName + : localName; } public boolean hasAttribute(String name) { @@ -281,7 +283,7 @@ public class ElementImpl extends InnerNodeImpl implements Element { throw new DOMException(DOMException.NOT_FOUND_ERR, null); } - attributes.remove(oldAttr); + attributes.remove(oldAttrImpl); oldAttrImpl.ownerElement = null; return oldAttrImpl; @@ -362,21 +364,7 @@ public class ElementImpl extends InnerNodeImpl implements Element { @Override public void setPrefix(String prefix) { - if (!namespaceAware) { - throw new DOMException(DOMException.NAMESPACE_ERR, prefix); - } - - if (prefix != null) { - if (namespaceURI == null || !document.isXMLIdentifier(prefix)) { - throw new DOMException(DOMException.NAMESPACE_ERR, prefix); - } - - if ("xml".equals(prefix) && !"http://www.w3.org/XML/1998/namespace".equals(namespaceURI)) { - throw new DOMException(DOMException.NAMESPACE_ERR, prefix); - } - } - - this.prefix = prefix; + this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI); } public class ElementAttrNamedNodeMapImpl implements NamedNodeMap { diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java index b752506..57ff7dc 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java @@ -16,6 +16,7 @@ package org.apache.harmony.xml.dom; +import org.w3c.dom.Attr; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; @@ -24,16 +25,15 @@ import org.w3c.dom.NodeList; import org.w3c.dom.UserDataHandler; /** - * Provides a straightforward implementation of the corresponding W3C DOM - * interface. The class is used internally only, thus only notable members that - * are not in the original interface are documented (the W3C docs are quite - * extensive). Hope that's ok. - * <p> - * Some of the fields may have package visibility, so other classes belonging to - * the DOM implementation can easily access them while maintaining the DOM tree - * structure. - * <p> - * This class represents a Node that has neither a parent nor children. + * A straightforward implementation of the corresponding W3C DOM node. + * + * <p>Some fields have package visibility so other classes can access them while + * maintaining the DOM structure. + * + * <p>This class represents a Node that has neither a parent nor children. + * Subclasses may have either. + * + * <p>Some code was adapted from Apache Xerces. */ public abstract class NodeImpl implements Node { @@ -142,6 +142,29 @@ public abstract class NodeImpl implements Node { } /** + * Validates the element or attribute namespace prefix on this node. + * + * @param namespaceAware whether this node is namespace aware + * @param namespaceURI this node's namespace URI + */ + protected String validatePrefix(String prefix, boolean namespaceAware, String namespaceURI) { + if (!namespaceAware) { + throw new DOMException(DOMException.NAMESPACE_ERR, prefix); + } + + if (prefix != null) { + if (namespaceURI == null + || !DocumentImpl.isXMLIdentifier(prefix) + || "xml".equals(prefix) && !"http://www.w3.org/XML/1998/namespace".equals(namespaceURI) + || "xmlns".equals(prefix) && !"http://www.w3.org/2000/xmlns/".equals(namespaceURI)) { + throw new DOMException(DOMException.NAMESPACE_ERR, prefix); + } + } + + return prefix; + } + + /** * Checks whether a required string matches an actual string. This utility * method is used for comparing namespaces and such. It takes into account * null arguments and the "*" special case. @@ -190,7 +213,34 @@ public abstract class NodeImpl implements Node { } public String getBaseURI() { - return null; // TODO + /* + * TODO: implement. For reference, here's Xerces' behaviour: + * + * In all cases, the returned URI should be sanitized before it is + * returned. If the URI is malformed, null should be returned instead. + * + * For document nodes, this should return a member field that's + * initialized by the parser. + * + * For element nodes, this should first look for the xml:base attribute. + * if that exists and is absolute, it should be returned. + * if that exists and is relative, it should be resolved to the parent's base URI + * if it doesn't exist, the parent's baseURI should be returned + * + * For entity nodes, if a base URI exists that should be returned. + * Otherwise the document's base URI should be returned + * + * For entity references, if a base URI exists that should be returned + * otherwise it dereferences the entity (via the document) and uses the + * entity's base URI. + * + * For notations, it returns the base URI field. + * + * For processing instructions, it returns the parent's base URI. + * + * For all other node types, it returns null. + */ + return null; } public short compareDocumentPosition(Node other) @@ -210,23 +260,190 @@ public abstract class NodeImpl implements Node { } public void setTextContent(String textContent) throws DOMException { - throw new UnsupportedOperationException(); // TODO + switch (getNodeType()) { + case DOCUMENT_TYPE_NODE: + case DOCUMENT_NODE: + return; // do nothing! + + case ELEMENT_NODE: + case ENTITY_NODE: + case ENTITY_REFERENCE_NODE: + case DOCUMENT_FRAGMENT_NODE: + // remove all existing children + Node child; + while ((child = getFirstChild()) != null) { + removeChild(child); + } + // create a text node to hold the given content + if (textContent != null && textContent.length() != 0){ + appendChild(getOwnerDocument().createTextNode(textContent)); + } + return; + + case ATTRIBUTE_NODE: + case TEXT_NODE: + case CDATA_SECTION_NODE: + case PROCESSING_INSTRUCTION_NODE: + case COMMENT_NODE: + case NOTATION_NODE: + setNodeValue(textContent); + return; + + default: + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Unsupported node type " + getNodeType()); + } } public boolean isSameNode(Node other) { - throw new UnsupportedOperationException(); // TODO + return this == other; } - public String lookupPrefix(String namespaceURI) { - throw new UnsupportedOperationException(); // TODO + /** + * Returns the element whose namespace definitions apply to this node. Use + * this element when mapping prefixes to URIs and vice versa. + */ + private NodeImpl getNamespacingElement() { + switch (this.getNodeType()) { + case ELEMENT_NODE: + return this; + + case DOCUMENT_NODE: + return (NodeImpl) ((Document) this).getDocumentElement(); + + case ENTITY_NODE: + case NOTATION_NODE: + case DOCUMENT_FRAGMENT_NODE: + case DOCUMENT_TYPE_NODE: + return null; + + case ATTRIBUTE_NODE: + return (NodeImpl) ((Attr) this).getOwnerElement(); + + case TEXT_NODE: + case CDATA_SECTION_NODE: + case ENTITY_REFERENCE_NODE: + case PROCESSING_INSTRUCTION_NODE: + case COMMENT_NODE: + return getContainingElement(); + + default: + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Unsupported node type " + getNodeType()); + } + } + + /** + * Returns the nearest ancestor element that contains this node. + */ + private NodeImpl getContainingElement() { + for (Node p = getParentNode(); p != null; p = p.getParentNode()) { + if (p.getNodeType() == ELEMENT_NODE) { + return (NodeImpl) p; + } + } + return null; + } + + public final String lookupPrefix(String namespaceURI) { + if (namespaceURI == null) { + return null; + } + + // the XML specs define some prefixes (like "xml" and "xmlns") but this + // API is explicitly defined to ignore those. + + NodeImpl target = getNamespacingElement(); + for (NodeImpl node = target; node != null; node = node.getContainingElement()) { + // check this element's namespace first + if (namespaceURI.equals(node.getNamespaceURI()) + && target.isPrefixMappedToUri(node.getPrefix(), namespaceURI)) { + return node.getPrefix(); + } + + // search this element for an attribute of this form: + // xmlns:foo="http://namespaceURI" + if (!node.hasAttributes()) { + continue; + } + NamedNodeMap attributes = node.getAttributes(); + for (int i = 0, length = attributes.getLength(); i < length; i++) { + Node attr = attributes.item(i); + if (!"http://www.w3.org/2000/xmlns/".equals(attr.getNamespaceURI()) + || !"xmlns".equals(attr.getPrefix()) + || !namespaceURI.equals(attr.getNodeValue())) { + continue; + } + if (target.isPrefixMappedToUri(attr.getLocalName(), namespaceURI)) { + return attr.getLocalName(); + } + } + } + + return null; + } + + /** + * Returns true if the given prefix is mapped to the given URI on this + * element. Since child elements can redefine prefixes, this check is + * necessary: {@code + * <foo xmlns:a="http://good"> + * <bar xmlns:a="http://evil"> + * <a:baz /> + * </bar> + * </foo>} + * + * @param prefix the prefix to find. Nullable. + * @param uri the URI to match. Non-null. + */ + boolean isPrefixMappedToUri(String prefix, String uri) { + if (prefix == null) { + return false; + } + + String actual = lookupNamespaceURI(prefix); + return uri.equals(actual); } public boolean isDefaultNamespace(String namespaceURI) { throw new UnsupportedOperationException(); // TODO } - public String lookupNamespaceURI(String prefix) { - throw new UnsupportedOperationException(); // TODO + public final String lookupNamespaceURI(String prefix) { + NodeImpl target = getNamespacingElement(); + for (NodeImpl node = target; node != null; node = node.getContainingElement()) { + // check this element's namespace first + String nodePrefix = node.getPrefix(); + if (node.getNamespaceURI() != null) { + if (prefix == null // null => default prefix + ? nodePrefix == null + : prefix.equals(nodePrefix)) { + return node.getNamespaceURI(); + } + } + + // search this element for an attribute of the appropriate form. + // default namespace: xmlns="http://resultUri" + // non default: xmlns:specifiedPrefix="http://resultUri" + if (!node.hasAttributes()) { + continue; + } + NamedNodeMap attributes = node.getAttributes(); + for (int i = 0, length = attributes.getLength(); i < length; i++) { + Node attr = attributes.item(i); + if (!"http://www.w3.org/2000/xmlns/".equals(attr.getNamespaceURI())) { + continue; + } + if (prefix == null // null => default prefix + ? "xmlns".equals(attr.getNodeName()) + : "xmlns".equals(attr.getPrefix()) && prefix.equals(attr.getLocalName())) { + String value = attr.getNodeValue(); + return value.length() > 0 ? value : null; + } + } + } + + return null; } public boolean isEqualNode(Node arg) { diff --git a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java index 5a3c48c..52240aa 100644 --- a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java @@ -115,8 +115,8 @@ class DocumentBuilderImpl extends DocumentBuilder { Document document = newDocument(); try { - XmlPullParser parser = new KXmlParser(); - + KXmlParser parser = new KXmlParser(); + parser.keepNamespaceAttributes(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, namespaceAware); diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java index c4d8f3d..99eb03b 100644 --- a/xml/src/main/java/org/kxml2/io/KXmlParser.java +++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java @@ -45,6 +45,7 @@ public class KXmlParser implements XmlPullParser { private boolean processNsp; private boolean relaxed; + private boolean keepNamespaceAttributes; // android-added private Hashtable entityMap; private int depth; private String[] elementStack = new String[16]; @@ -80,6 +81,14 @@ public class KXmlParser implements XmlPullParser { private boolean degenerated; private int attributeCount; + + /** + * The current element's attributes arranged in groups of 4: + * i + 0 = attribute namespace URI + * i + 1 = attribute namespace prefix + * i + 2 = attribute qualified name (may contain ":", as in "html:h1") + * i + 3 = attribute value + */ private String[] attributes = new String[16]; // private int stackMismatch = 0; private String error; @@ -100,6 +109,19 @@ public class KXmlParser implements XmlPullParser { new char[Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 128]; } + // BEGIN android-added + /** + * Retains namespace attributes like {@code xmlns="http://foo"} or {@code + * xmlns:foo="http:foo"} in pulled elements. Most applications will only be + * interested in the effective namespaces of their elements, so these + * attributes aren't useful. But for structure preserving wrappers like DOM, + * it is necessary to keep the namespace data around. + */ + public void keepNamespaceAttributes() { + this.keepNamespaceAttributes = true; + } + // END android-added + private final boolean isProp(String n1, boolean prop, String n2) { if (!n1.startsWith("http://xmlpull.org/v1/doc/")) return false; @@ -148,14 +170,23 @@ public class KXmlParser implements XmlPullParser { //System.out.println (prefixMap); - System.arraycopy( - attributes, - i + 4, - attributes, - i, - ((--attributeCount) << 2) - i); - - i -= 4; + // BEGIN android-changed + if (keepNamespaceAttributes) { + // explicitly set the namespace for unprefixed attributes + // such as xmlns="http://foo" + attributes[i] = "http://www.w3.org/2000/xmlns/"; + any = true; + } else { + System.arraycopy( + attributes, + i + 4, + attributes, + i, + ((--attributeCount) << 2) - i); + + i -= 4; + } + // END android-changed } } diff --git a/xml/src/main/java/org/w3c/dom/Attr.java b/xml/src/main/java/org/w3c/dom/Attr.java index d9ed6ff..bd7267b 100644 --- a/xml/src/main/java/org/w3c/dom/Attr.java +++ b/xml/src/main/java/org/w3c/dom/Attr.java @@ -176,7 +176,7 @@ public interface Attr extends Node { /** * On retrieval, the value of the attribute is returned as a string. * Character and general entity references are replaced with their - * values. See also the method <code>getAttribute</code> on the + * values. See also the method <code>getAttribute</code> on the * <code>Element</code> interface. * <br>On setting, this creates a <code>Text</code> node with the unparsed * contents of the string, i.e. any characters that an XML processor |