diff options
author | Jesse Wilson <jessewilson@google.com> | 2010-02-23 15:23:26 -0800 |
---|---|---|
committer | Jesse Wilson <jessewilson@google.com> | 2010-02-23 17:56:48 -0800 |
commit | bda224da00c0372a7752b1304aeda98e2930c4af (patch) | |
tree | a28550992209058749a605e25ee35a48cd459964 | |
parent | ea6435b142df4aaaf8854b3200b9f442b331f143 (diff) | |
download | libcore-bda224da00c0372a7752b1304aeda98e2930c4af.zip libcore-bda224da00c0372a7752b1304aeda98e2930c4af.tar.gz libcore-bda224da00c0372a7752b1304aeda98e2930c4af.tar.bz2 |
Implementing still more DOM API for text nodes.
- Text.isElementContentWhitespace()
- Text.getWholeText()
- Text.replaceWholeText()
11 files changed, 248 insertions, 78 deletions
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java index 33e216a..b28c9da 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java @@ -31,7 +31,7 @@ import org.w3c.dom.Node; */ public class CDATASectionImpl extends TextImpl implements CDATASection { - CDATASectionImpl(DocumentImpl document, String data) { + public CDATASectionImpl(DocumentImpl document, String data) { super(document, data); } diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java index c39423c..6354747 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java @@ -51,6 +51,13 @@ public abstract class CharacterDataImpl extends LeafNodeImpl implements return buffer.toString(); } + /** + * Appends this node's text content to the given builder. + */ + public void appendDataTo(StringBuilder stringBuilder) { + stringBuilder.append(buffer); + } + public int getLength() { return buffer.length(); } diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java index 834cc47..1283eeb 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java @@ -31,7 +31,7 @@ import org.w3c.dom.DocumentType; * the DOM implementation can easily access them while maintaining the DOM tree * structure. */ -public class DOMImplementationImpl implements DOMImplementation { +public final class DOMImplementationImpl implements DOMImplementation { // Singleton instance. private static DOMImplementationImpl instance; diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java index c8819cb..b2f16d1 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java @@ -48,7 +48,7 @@ public class DocumentImpl extends InnerNodeImpl implements Document { private DOMImplementation domImplementation; - DocumentImpl(DOMImplementationImpl impl, String namespaceURI, + public DocumentImpl(DOMImplementationImpl impl, String namespaceURI, String qualifiedName, DocumentType doctype) { super(null); 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 ebfdd52..24ed102 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 @@ -224,7 +224,6 @@ public abstract class NodeImpl implements Node { * account null arguments and the "*" special case. * * @param name The required name. - * @param wildcard TODO * @return True if and only if the actual name matches the required one. */ public boolean matchesName(String name, boolean wildcard) { @@ -238,7 +237,6 @@ public abstract class NodeImpl implements Node { * * @param namespaceURI The required namespace. * @param localName The required local name. - * @param wildcard TODO * @return True if and only if the actual namespace and local name match * the required pair of namespace and local name. */ @@ -309,7 +307,7 @@ public abstract class NodeImpl implements Node { removeChild(child); } // create a text node to hold the given content - if (textContent != null && textContent.length() != 0){ + if (textContent != null && textContent.length() != 0) { appendChild(getOwnerDocument().createTextNode(textContent)); } return; diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java index 5c9d123..3840ef4 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java @@ -32,7 +32,7 @@ import org.w3c.dom.Text; */ public class TextImpl extends CharacterDataImpl implements Text { - TextImpl(DocumentImpl document, String data) { + public TextImpl(DocumentImpl document, String data) { super(document, data); } @@ -46,7 +46,7 @@ public class TextImpl extends CharacterDataImpl implements Text { return Node.TEXT_NODE; } - public Text splitText(int offset) throws DOMException { + public final Text splitText(int offset) throws DOMException { Text newText = getOwnerDocument().createTextNode( substringData(offset, getLength() - offset)); deleteData(0, offset); @@ -61,15 +61,83 @@ public class TextImpl extends CharacterDataImpl implements Text { return this; } - public boolean isElementContentWhitespace() { - throw new UnsupportedOperationException(); // TODO + public final boolean isElementContentWhitespace() { + // Undefined because we don't validate. Whether whitespace characters + // constitute "element content whitespace" is defined by the containing + // element's declaration (DTD) and we don't parse that. + // TODO: wire this up when we support document validation + return false; } - public String getWholeText() { - throw new UnsupportedOperationException(); // TODO + public final String getWholeText() { + // TODO: support entity references. This code should expand through + // the child elements of entity references. + // http://code.google.com/p/android/issues/detail?id=6807 + + StringBuilder result = new StringBuilder(); + for (TextImpl n = firstTextNodeInCurrentRun(); n != null; n = n.nextTextNode()) { + n.appendDataTo(result); + } + return result.toString(); } - public Text replaceWholeText(String content) throws DOMException { - throw new UnsupportedOperationException(); // TODO + public final Text replaceWholeText(String content) throws DOMException { + // TODO: support entity references. This code should expand and replace + // the child elements of entity references. + // http://code.google.com/p/android/issues/detail?id=6807 + + Node parent = getParentNode(); + Text result = null; + + // delete all nodes in the current run of text... + for (TextImpl n = firstTextNodeInCurrentRun(); n != null; ) { + + // ...except the current node if we have content for it + if (n == this && content != null && content.length() > 0) { + setData(content); + result = this; + n = n.nextTextNode(); + + } else { + Node toRemove = n; // because removeChild() detaches siblings + n = n.nextTextNode(); + parent.removeChild(toRemove); + } + } + + return result; + } + + /** + * Returns the first text or CDATA node in the current sequence of text and + * CDATA nodes. + */ + private TextImpl firstTextNodeInCurrentRun() { + TextImpl firstTextInCurrentRun = this; + for (Node p = getPreviousSibling(); p != null; p = p.getPreviousSibling()) { + short nodeType = p.getNodeType(); + if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) { + firstTextInCurrentRun = (TextImpl) p; + } else { + break; + } + } + return firstTextInCurrentRun; + } + + /** + * Returns the next sibling node if it exists and it is text or CDATA. + * Otherwise returns null. + */ + private TextImpl nextTextNode() { + Node nextSibling = getNextSibling(); + if (nextSibling == null) { + return null; + } + + short nodeType = nextSibling.getNodeType(); + return nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE + ? (TextImpl) nextSibling + : null; } } 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 52240aa..ca2ff98 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 @@ -23,10 +23,14 @@ import java.util.StringTokenizer; import javax.xml.parsers.DocumentBuilder; +import org.apache.harmony.xml.dom.CDATASectionImpl; +import org.apache.harmony.xml.dom.DocumentImpl; +import org.apache.harmony.xml.dom.TextImpl; import org.kxml2.io.KXmlParser; import org.w3c.dom.Attr; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; @@ -49,7 +53,7 @@ import org.apache.harmony.xml.dom.DOMImplementationImpl; */ class DocumentBuilderImpl extends DocumentBuilder { - private static DOMImplementation dom = DOMImplementationImpl.getInstance(); + private static DOMImplementationImpl dom = DOMImplementationImpl.getInstance(); private boolean coalescing; @@ -72,25 +76,6 @@ class DocumentBuilderImpl extends DocumentBuilder { return dom; } - /** - * Reflects whether this DocumentBuilder is configured to ignore comments. - * - * @return True if and only if comments are ignored. - */ - public boolean isIgnoringComments() { - return ignoreComments; - } - - /** - * Reflects whether this DocumentBuilder is configured to ignore element - * content whitespace. - * - * @return True if and only if whitespace element content is ignored. - */ - public boolean isIgnoringElementContentWhitespace() { - return ignoreElementContentWhitespace; - } - @Override public boolean isNamespaceAware() { return namespaceAware; @@ -112,7 +97,10 @@ class DocumentBuilderImpl extends DocumentBuilder { throw new IllegalArgumentException(); } - Document document = newDocument(); + String namespaceURI = null; + String qualifiedName = null; + DocumentType doctype = null; + DocumentImpl document = new DocumentImpl(dom, namespaceURI, qualifiedName, doctype); try { KXmlParser parser = new KXmlParser(); @@ -189,7 +177,7 @@ class DocumentBuilderImpl extends DocumentBuilder { * @throws XmlPullParserException If a parsing error occurs. * @throws IOException If a general IO error occurs. */ - private void parse(XmlPullParser parser, Document document, Node node, + private void parse(XmlPullParser parser, DocumentImpl document, Node node, int endToken) throws XmlPullParserException, IOException { int token = parser.getEventType(); @@ -271,7 +259,7 @@ class DocumentBuilderImpl extends DocumentBuilder { * whitespace at all. */ if (!ignoreElementContentWhitespace) { - appendText(document, node, true, parser.getText()); + appendText(document, node, token, parser.getText()); } } else if (token == XmlPullParser.TEXT || token == XmlPullParser.CDSECT) { /* @@ -279,7 +267,7 @@ class DocumentBuilderImpl extends DocumentBuilder { * That's the easiest case. We simply take it and create a new text node, * or merge with an adjacent text node. */ - appendText(document, node, token == XmlPullParser.TEXT, parser.getText()); + appendText(document, node, token, parser.getText()); } else if (token == XmlPullParser.ENTITY_REF) { /* * Found an entity reference. If an entity resolver is @@ -294,7 +282,7 @@ class DocumentBuilderImpl extends DocumentBuilder { String replacement = resolveStandardEntity(entity); if (replacement != null) { - appendText(document, node, true, replacement); + appendText(document, node, token, replacement); } else { node.appendChild(document.createEntityReference(entity)); } @@ -380,17 +368,17 @@ class DocumentBuilderImpl extends DocumentBuilder { } /** - * @param isText true for a normal TextNode, false for a CDATA section. - * (If we're not coalescing, it matters which kind of node we put into the DOM.) + * @param token the XML pull parser token type, such as XmlPullParser.CDSECT + * or XmlPullParser.ENTITY_REF. */ - private void appendText(Document document, Node node, boolean isText, String text) { + private void appendText(DocumentImpl document, Node parent, int token, String text) { // Ignore empty runs. if (text.length() == 0) { return; } // Merge with any previous text node if possible. if (coalescing) { - Node lastChild = node.getLastChild(); + Node lastChild = parent.getLastChild(); if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) { Text textNode = (Text) lastChild; textNode.setData(textNode.getNodeValue() + text); @@ -398,11 +386,9 @@ class DocumentBuilderImpl extends DocumentBuilder { } } // Okay, we really do need a new text node - if (isText) { - node.appendChild(document.createTextNode(text)); - } else { - node.appendChild(document.createCDATASection(text)); - } + parent.appendChild(token == XmlPullParser.CDSECT + ? new CDATASectionImpl(document, text) + : new TextImpl(document, text)); } @Override diff --git a/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java b/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java index 6b10a0d..ea7abed 100644 --- a/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java +++ b/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java @@ -562,24 +562,18 @@ public class DocumentBuilderTest extends TestCase { method = "parse", args = {java.lang.String.class} ) - public void test_parseLjava_lang_String() { + public void test_parseLjava_lang_String() throws Exception { // case 1: Trivial use. File f = new File(getClass().getResource("/simple.xml").getFile()); - try { - Document d = db.parse(f.getAbsolutePath()); - assertNotNull(d); + Document d = db.parse(f.getAbsolutePath()); + assertNotNull(d); // TBD getXmlEncoding() is not supported // assertEquals("ISO-8859-1", d.getXmlEncoding()); - assertEquals(2, d.getChildNodes().getLength()); - assertEquals("#comment", - d.getChildNodes().item(0).getNodeName()); - assertEquals("breakfast_menu", - d.getChildNodes().item(1).getNodeName()); - } catch (IOException ioe) { - fail("Unexpected IOException " + ioe.toString()); - } catch (SAXException sax) { - fail("Unexpected SAXException " + sax.toString()); - } + assertEquals(2, d.getChildNodes().getLength()); + assertEquals("#comment", + d.getChildNodes().item(0).getNodeName()); + assertEquals("breakfast_menu", + d.getChildNodes().item(1).getNodeName()); // case 2: Try to call parse with null argument try { @@ -587,10 +581,6 @@ public class DocumentBuilderTest extends TestCase { fail("Expected IllegalArgumentException was not thrown"); } catch (IllegalArgumentException iae) { // expected - } catch (IOException ioe) { - fail("Unexpected IOException " + ioe.toString()); - } catch (SAXException sax) { - fail("Unexpected SAXException " + sax.toString()); } // case 3: Try to parse a non-existent uri @@ -599,8 +589,6 @@ public class DocumentBuilderTest extends TestCase { fail("Expected IOException was not thrown"); } catch (IOException ioe) { // expected - } catch (SAXException sax) { - fail("Unexpected SAXException " + sax.toString()); } // case 4: Try to parse incorrect xml file @@ -608,8 +596,6 @@ public class DocumentBuilderTest extends TestCase { f = new File(getClass().getResource("/wrong.xml").getFile()); db.parse(f.getAbsolutePath()); fail("Expected SAXException was not thrown"); - } catch (IOException ioe) { - fail("Unexpected IOException " + ioe.toString()); } catch (SAXException sax) { // expected } diff --git a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java index ca7cf71..e6d6481 100644 --- a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java +++ b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java @@ -677,8 +677,7 @@ public class SAXParserTest extends TestCase { MyDefaultHandler dh = new MyDefaultHandler(); InputStream is = new FileInputStream(list_wf[i]); parser.parse(is, dh, SAXParserTestSupport.XML_SYSTEM_ID); - assertTrue(SAXParserTestSupport.equalsMaps(hm, - dh.createData())); + assertEquals(hm, dh.createData()); } catch (IOException ioe) { fail("Unexpected IOException " + ioe.toString()); } catch (SAXException sax) { diff --git a/xml/src/test/java/tests/xml/AllTests.java b/xml/src/test/java/tests/xml/AllTests.java index 89da364..597e35e 100644 --- a/xml/src/test/java/tests/xml/AllTests.java +++ b/xml/src/test/java/tests/xml/AllTests.java @@ -24,6 +24,7 @@ public class AllTests { public static Test suite() { TestSuite suite = tests.TestSuiteFactory.createTestSuite(); + suite.addTestSuite(DomTest.class); suite.addTestSuite(SimpleParserTest.class); suite.addTestSuite(SimpleBuilderTest.class); suite.addTestSuite(NodeTest.class); diff --git a/xml/src/test/java/tests/xml/DomTest.java b/xml/src/test/java/tests/xml/DomTest.java index 5f0a19a..69e8b37 100644 --- a/xml/src/test/java/tests/xml/DomTest.java +++ b/xml/src/test/java/tests/xml/DomTest.java @@ -109,7 +109,6 @@ public class DomTest extends TestCase { factory.setNamespaceAware(true); builder = factory.newDocumentBuilder(); domImplementation = builder.getDOMImplementation(); - document = builder.parse(new InputSource(new StringReader(xml))); // doctype nodes @@ -448,7 +447,6 @@ public class DomTest extends TestCase { document.appendChild(root); EntityReference entityReference = document.createEntityReference("sp"); - entityReference.setNodeValue("Maple Syrup"); root.appendChild(entityReference); try { @@ -461,8 +459,7 @@ public class DomTest extends TestCase { public void testAttributeSetTextContent() throws TransformerException { String original = domToString(document); standard.setTextContent("foobar"); - String expected = original.replaceFirst( - "standard=\"strawberry\"", "standard=\"foobar\""); + String expected = original.replace("standard=\"strawberry\"", "standard=\"foobar\""); assertEquals(expected, domToString(document)); } @@ -614,8 +611,136 @@ public class DomTest extends TestCase { } } - private String domToString(Document document) - throws TransformerException { + public void testIsElementContentWhitespaceWithoutDeclaration() throws Exception { + String xml = "<menu> <item/> </menu>"; + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Text text = (Text) factory.newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))) + .getDocumentElement().getChildNodes().item(0); + assertFalse(text.isElementContentWhitespace()); + } + + public void testIsElementContentWhitespaceWithDeclaration() throws Exception { + String xml = "<!DOCTYPE menu [\n" + + " <!ELEMENT menu (item)*>\n" + + " <!ELEMENT item (#PCDATA)>\n" + + "]><menu> <item/> </menu>"; + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Text text = (Text) factory.newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))) + .getDocumentElement().getChildNodes().item(0); + assertTrue("This implementation does not recognize element content whitespace", + text.isElementContentWhitespace()); + } + + public void testGetWholeTextFirst() { + assertEquals("Belgian waffles & strawberries (< 5g of fat)", + descriptionText1.getWholeText()); + } + + public void testGetWholeTextMiddle() { + assertEquals("This implementation doesn't include preceding nodes in getWholeText()", + "Belgian waffles & strawberries (< 5g of fat)", descriptionText2.getWholeText()); + } + + public void testGetWholeTextLast() { + assertEquals("This implementation doesn't include preceding nodes in getWholeText()", + "Belgian waffles & strawberries (< 5g of fat)", descriptionText3.getWholeText()); + } + + public void testGetWholeTextOnly() { + assertEquals("60%", vitamincText.getWholeText()); + } + + public void testGetWholeTextWithEntityReference() { + EntityReference spReference = document.createEntityReference("sp"); + description.insertBefore(spReference, descriptionText2); + + assertEquals("This implementation doesn't resolve entity references in getWholeText()", + "BelgianMaple Syrup waffles & strawberries (< 5g of fat)", + descriptionText1.getWholeText()); + } + + public void testReplaceWholeTextFirst() throws TransformerException { + String original = domToString(document); + Text replacement = descriptionText1.replaceWholeText("Eggos"); + assertSame(descriptionText1, replacement); + String expected = original.replace( + "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos"); + assertEquals(expected, domToString(document)); + } + + public void testReplaceWholeTextMiddle() throws TransformerException { + String original = domToString(document); + Text replacement = descriptionText2.replaceWholeText("Eggos"); + assertSame(descriptionText2, replacement); + String expected = original.replace( + "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "<![CDATA[Eggos]]>"); + assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()", + expected, domToString(document)); + } + + public void testReplaceWholeTextLast() throws TransformerException { + String original = domToString(document); + Text replacement = descriptionText3.replaceWholeText("Eggos"); + assertSame(descriptionText3, replacement); + String expected = original.replace( + "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos"); + assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()", + expected, domToString(document)); + } + + public void testReplaceWholeTextOnly() throws TransformerException { + String original = domToString(document); + Text replacement = vitamincText.replaceWholeText("70%"); + assertEquals(Node.TEXT_NODE, replacement.getNodeType()); + assertSame(vitamincText, replacement); + String expected = original.replace("60%", "70%"); + assertEquals(expected, domToString(document)); + } + + public void testReplaceWholeTextFirstWithNull() throws TransformerException { + String original = domToString(document); + assertNull(descriptionText1.replaceWholeText(null)); + String expected = original.replaceFirst(">.*</description>", "/>"); + assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", + expected, domToString(document)); + } + + public void testReplaceWholeTextMiddleWithNull() throws TransformerException { + String original = domToString(document); + assertNull(descriptionText2.replaceWholeText(null)); + String expected = original.replaceFirst(">.*</description>", "/>"); + assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", + expected, domToString(document)); + } + + public void testReplaceWholeTextLastWithNull() throws TransformerException { + String original = domToString(document); + assertNull(descriptionText3.replaceWholeText(null)); + String expected = original.replaceFirst(">.*</description>", "/>"); + assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", + expected, domToString(document)); + } + + public void testReplaceWholeTextFirstWithEmptyString() throws TransformerException { + String original = domToString(document); + assertNull(descriptionText1.replaceWholeText("")); + String expected = original.replaceFirst(">.*</description>", "/>"); + assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", + expected, domToString(document)); + } + + public void testReplaceWholeTextOnlyWithEmptyString() throws TransformerException { + String original = domToString(document); + assertNull(vitamincText.replaceWholeText("")); + String expected = original.replaceFirst(">.*</a:vitaminc>", "/>"); + assertEquals(expected, domToString(document)); + } + + private String domToString(Document document) throws TransformerException { StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(document), new StreamResult(writer)); return writer.toString(); |