summaryrefslogtreecommitdiffstats
path: root/xml/src/main/java
diff options
context:
space:
mode:
authorJesse Wilson <jessewilson@google.com>2010-02-19 09:22:21 -0800
committerJesse Wilson <jessewilson@google.com>2010-02-19 09:22:21 -0800
commit1ec94feeb09591c30996c7c0834d6f131e204922 (patch)
tree28de8c0740a3032d3a5bd6c3a9414e448f9dd7b7 /xml/src/main/java
parent3fe3b27d0af854f28fc8de612271c0ca9cd6a8f7 (diff)
downloadlibcore-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')
-rw-r--r--xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java27
-rw-r--r--xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java28
-rw-r--r--xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java251
-rw-r--r--xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java4
-rw-r--r--xml/src/main/java/org/kxml2/io/KXmlParser.java47
-rw-r--r--xml/src/main/java/org/w3c/dom/Attr.java2
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