| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package libcore.xml; |
| |
| import com.google.mockwebserver.MockResponse; |
| import com.google.mockwebserver.MockWebServer; |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import junit.framework.Assert; |
| import junit.framework.TestCase; |
| import org.apache.harmony.xml.ExpatReader; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.ext.DefaultHandler2; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| public class ExpatSaxParserTest extends TestCase { |
| |
| private static final String SNIPPET = "<dagny dad=\"bob\">hello</dagny>"; |
| |
| public void testGlobalReferenceTableOverflow() throws Exception { |
| // We used to use a JNI global reference per interned string. |
| // Framework apps have a limit of 2000 JNI global references per VM. |
| StringBuilder xml = new StringBuilder(); |
| xml.append("<root>"); |
| for (int i = 0; i < 4000; ++i) { |
| xml.append("<tag" + i + ">"); |
| xml.append("</tag" + i + ">"); |
| } |
| xml.append("</root>"); |
| parse(xml.toString(), new DefaultHandler()); |
| } |
| |
| public void testExceptions() { |
| // From startElement(). |
| ContentHandler contentHandler = new DefaultHandler() { |
| @Override |
| public void startElement(String uri, String localName, |
| String qName, Attributes attributes) |
| throws SAXException { |
| throw new SAXException(); |
| } |
| }; |
| try { |
| parse(SNIPPET, contentHandler); |
| fail(); |
| } catch (SAXException checked) { /* expected */ } |
| |
| // From endElement(). |
| contentHandler = new DefaultHandler() { |
| @Override |
| public void endElement(String uri, String localName, |
| String qName) |
| throws SAXException { |
| throw new SAXException(); |
| } |
| }; |
| try { |
| parse(SNIPPET, contentHandler); |
| fail(); |
| } catch (SAXException checked) { /* expected */ } |
| |
| // From characters(). |
| contentHandler = new DefaultHandler() { |
| @Override |
| public void characters(char ch[], int start, int length) |
| throws SAXException { |
| throw new SAXException(); |
| } |
| }; |
| try { |
| parse(SNIPPET, contentHandler); |
| fail(); |
| } catch (SAXException checked) { /* expected */ } |
| } |
| |
| public void testSax() { |
| try { |
| // Parse String. |
| TestHandler handler = new TestHandler(); |
| parse(SNIPPET, handler); |
| validate(handler); |
| |
| // Parse Reader. |
| handler = new TestHandler(); |
| parse(new StringReader(SNIPPET), handler); |
| validate(handler); |
| |
| // Parse InputStream. |
| handler = new TestHandler(); |
| parse(new ByteArrayInputStream(SNIPPET.getBytes()), |
| Encoding.UTF_8, handler); |
| validate(handler); |
| } catch (SAXException e) { |
| throw new RuntimeException(e); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| static void validate(TestHandler handler) { |
| assertEquals("dagny", handler.startElementName); |
| assertEquals("dagny", handler.endElementName); |
| assertEquals("hello", handler.text.toString()); |
| } |
| |
| static class TestHandler extends DefaultHandler { |
| |
| String startElementName; |
| String endElementName; |
| StringBuilder text = new StringBuilder(); |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, |
| Attributes attributes) throws SAXException { |
| |
| assertNull(this.startElementName); |
| this.startElementName = localName; |
| |
| // Validate attributes. |
| assertEquals(1, attributes.getLength()); |
| assertEquals("", attributes.getURI(0)); |
| assertEquals("dad", attributes.getLocalName(0)); |
| assertEquals("bob", attributes.getValue(0)); |
| assertEquals(0, attributes.getIndex("", "dad")); |
| assertEquals("bob", attributes.getValue("", "dad")); |
| } |
| |
| @Override |
| public void endElement(String uri, String localName, String qName) |
| throws SAXException { |
| assertNull(this.endElementName); |
| this.endElementName = localName; |
| } |
| |
| @Override |
| public void characters(char ch[], int start, int length) |
| throws SAXException { |
| this.text.append(ch, start, length); |
| } |
| } |
| |
| static final String XML = |
| "<one xmlns='ns:default' xmlns:n1='ns:1' a='b'>\n" |
| + " <n1:two c='d' n1:e='f' xmlns:n2='ns:2'>text</n1:two>\n" |
| + "</one>"; |
| |
| public void testNamespaces() { |
| try { |
| NamespaceHandler handler = new NamespaceHandler(); |
| parse(XML, handler); |
| handler.validate(); |
| } catch (SAXException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| static class NamespaceHandler implements ContentHandler { |
| |
| Locator locator; |
| boolean documentStarted; |
| boolean documentEnded; |
| Map<String, String> prefixMappings = new HashMap<String, String>(); |
| |
| boolean oneStarted; |
| boolean twoStarted; |
| boolean oneEnded; |
| boolean twoEnded; |
| |
| public void validate() { |
| assertTrue(documentEnded); |
| } |
| |
| public void setDocumentLocator(Locator locator) { |
| this.locator = locator; |
| } |
| |
| public void startDocument() throws SAXException { |
| documentStarted = true; |
| assertNotNull(locator); |
| assertEquals(0, prefixMappings.size()); |
| assertFalse(documentEnded); |
| } |
| |
| public void endDocument() throws SAXException { |
| assertTrue(documentStarted); |
| assertTrue(oneEnded); |
| assertTrue(twoEnded); |
| assertEquals(0, prefixMappings.size()); |
| documentEnded = true; |
| } |
| |
| public void startPrefixMapping(String prefix, String uri) |
| throws SAXException { |
| prefixMappings.put(prefix, uri); |
| } |
| |
| public void endPrefixMapping(String prefix) throws SAXException { |
| assertNotNull(prefixMappings.remove(prefix)); |
| } |
| |
| public void startElement(String uri, String localName, String qName, |
| Attributes atts) throws SAXException { |
| |
| if (localName == "one") { |
| assertEquals(2, prefixMappings.size()); |
| |
| assertEquals(1, locator.getLineNumber()); |
| |
| assertFalse(oneStarted); |
| assertFalse(twoStarted); |
| assertFalse(oneEnded); |
| assertFalse(twoEnded); |
| |
| oneStarted = true; |
| |
| assertSame("ns:default", uri); |
| assertEquals("one", qName); |
| |
| // Check atts. |
| assertEquals(1, atts.getLength()); |
| |
| assertSame("", atts.getURI(0)); |
| assertSame("a", atts.getLocalName(0)); |
| assertEquals("b", atts.getValue(0)); |
| assertEquals(0, atts.getIndex("", "a")); |
| assertEquals("b", atts.getValue("", "a")); |
| |
| return; |
| } |
| |
| if (localName == "two") { |
| assertEquals(3, prefixMappings.size()); |
| |
| assertTrue(oneStarted); |
| assertFalse(twoStarted); |
| assertFalse(oneEnded); |
| assertFalse(twoEnded); |
| |
| twoStarted = true; |
| |
| assertSame("ns:1", uri); |
| Assert.assertEquals("n1:two", qName); |
| |
| // Check atts. |
| assertEquals(2, atts.getLength()); |
| |
| assertSame("", atts.getURI(0)); |
| assertSame("c", atts.getLocalName(0)); |
| assertEquals("d", atts.getValue(0)); |
| assertEquals(0, atts.getIndex("", "c")); |
| assertEquals("d", atts.getValue("", "c")); |
| |
| assertSame("ns:1", atts.getURI(1)); |
| assertSame("e", atts.getLocalName(1)); |
| assertEquals("f", atts.getValue(1)); |
| assertEquals(1, atts.getIndex("ns:1", "e")); |
| assertEquals("f", atts.getValue("ns:1", "e")); |
| |
| // We shouldn't find these. |
| assertEquals(-1, atts.getIndex("ns:default", "e")); |
| assertEquals(null, atts.getValue("ns:default", "e")); |
| |
| return; |
| } |
| |
| fail(); |
| } |
| |
| public void endElement(String uri, String localName, String qName) |
| throws SAXException { |
| if (localName == "one") { |
| assertEquals(3, locator.getLineNumber()); |
| |
| assertTrue(oneStarted); |
| assertTrue(twoStarted); |
| assertTrue(twoEnded); |
| assertFalse(oneEnded); |
| |
| oneEnded = true; |
| |
| assertSame("ns:default", uri); |
| assertEquals("one", qName); |
| |
| return; |
| } |
| |
| if (localName == "two") { |
| assertTrue(oneStarted); |
| assertTrue(twoStarted); |
| assertFalse(twoEnded); |
| assertFalse(oneEnded); |
| |
| twoEnded = true; |
| |
| assertSame("ns:1", uri); |
| assertEquals("n1:two", qName); |
| |
| return; |
| } |
| |
| fail(); |
| } |
| |
| public void characters(char ch[], int start, int length) |
| throws SAXException { |
| String s = new String(ch, start, length).trim(); |
| |
| if (!s.equals("")) { |
| assertTrue(oneStarted); |
| assertTrue(twoStarted); |
| assertFalse(oneEnded); |
| assertFalse(twoEnded); |
| assertEquals("text", s); |
| } |
| } |
| |
| public void ignorableWhitespace(char ch[], int start, int length) |
| throws SAXException { |
| fail(); |
| } |
| |
| public void processingInstruction(String target, String data) |
| throws SAXException { |
| fail(); |
| } |
| |
| public void skippedEntity(String name) throws SAXException { |
| fail(); |
| } |
| } |
| |
| private TestDtdHandler runDtdTest(String s) throws Exception { |
| Reader in = new StringReader(s); |
| ExpatReader reader = new ExpatReader(); |
| TestDtdHandler handler = new TestDtdHandler(); |
| reader.setContentHandler(handler); |
| reader.setDTDHandler(handler); |
| reader.setLexicalHandler(handler); |
| reader.parse(new InputSource(in)); |
| return handler; |
| } |
| |
| public void testDtdDoctype() throws Exception { |
| TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee'><a></a>"); |
| assertEquals("foo", handler.name); |
| assertEquals("bar", handler.publicId); |
| assertEquals("tee", handler.systemId); |
| assertTrue(handler.ended); |
| } |
| |
| public void testDtdUnparsedEntity_system() throws Exception { |
| TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!ENTITY ent SYSTEM 'blah' NDATA poop> ]><a></a>"); |
| assertEquals("ent", handler.ueName); |
| assertEquals(null, handler.uePublicId); |
| assertEquals("blah", handler.ueSystemId); |
| assertEquals("poop", handler.ueNotationName); |
| } |
| |
| public void testDtdUnparsedEntity_public() throws Exception { |
| TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!ENTITY ent PUBLIC 'a' 'b' NDATA poop> ]><a></a>"); |
| assertEquals("ent", handler.ueName); |
| assertEquals("a", handler.uePublicId); |
| assertEquals("b", handler.ueSystemId); |
| assertEquals("poop", handler.ueNotationName); |
| } |
| |
| public void testDtdNotation_system() throws Exception { |
| TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!NOTATION sn SYSTEM 'nf2'> ]><a></a>"); |
| assertEquals("sn", handler.ndName); |
| assertEquals(null, handler.ndPublicId); |
| assertEquals("nf2", handler.ndSystemId); |
| } |
| |
| public void testDtdNotation_public() throws Exception { |
| TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!NOTATION pn PUBLIC 'nf1'> ]><a></a>"); |
| assertEquals("pn", handler.ndName); |
| assertEquals("nf1", handler.ndPublicId); |
| assertEquals(null, handler.ndSystemId); |
| } |
| |
| static class TestDtdHandler extends DefaultHandler2 { |
| |
| String name; |
| String publicId; |
| String systemId; |
| String ndName; |
| String ndPublicId; |
| String ndSystemId; |
| String ueName; |
| String uePublicId; |
| String ueSystemId; |
| String ueNotationName; |
| |
| boolean ended; |
| |
| Locator locator; |
| |
| @Override |
| public void startDTD(String name, String publicId, String systemId) { |
| this.name = name; |
| this.publicId = publicId; |
| this.systemId = systemId; |
| } |
| |
| @Override |
| public void endDTD() { |
| ended = true; |
| } |
| |
| @Override |
| public void setDocumentLocator(Locator locator) { |
| this.locator = locator; |
| } |
| |
| @Override |
| public void notationDecl(String name, String publicId, String systemId) { |
| this.ndName = name; |
| this.ndPublicId = publicId; |
| this.ndSystemId = systemId; |
| } |
| |
| @Override |
| public void unparsedEntityDecl(String entityName, String publicId, String systemId, String notationName) { |
| this.ueName = entityName; |
| this.uePublicId = publicId; |
| this.ueSystemId = systemId; |
| this.ueNotationName = notationName; |
| } |
| } |
| |
| public void testCdata() throws Exception { |
| Reader in = new StringReader( |
| "<a><![CDATA[<b></b>]]> <![CDATA[<c></c>]]></a>"); |
| |
| ExpatReader reader = new ExpatReader(); |
| TestCdataHandler handler = new TestCdataHandler(); |
| reader.setContentHandler(handler); |
| reader.setLexicalHandler(handler); |
| |
| reader.parse(new InputSource(in)); |
| |
| assertEquals(2, handler.startCdata); |
| assertEquals(2, handler.endCdata); |
| assertEquals("<b></b> <c></c>", handler.buffer.toString()); |
| } |
| |
| static class TestCdataHandler extends DefaultHandler2 { |
| |
| int startCdata, endCdata; |
| StringBuffer buffer = new StringBuffer(); |
| |
| @Override |
| public void characters(char ch[], int start, int length) { |
| buffer.append(ch, start, length); |
| } |
| |
| @Override |
| public void startCDATA() throws SAXException { |
| startCdata++; |
| } |
| |
| @Override |
| public void endCDATA() throws SAXException { |
| endCdata++; |
| } |
| } |
| |
| public void testProcessingInstructions() throws IOException, SAXException { |
| Reader in = new StringReader( |
| "<?bob lee?><a></a>"); |
| |
| ExpatReader reader = new ExpatReader(); |
| TestProcessingInstrutionHandler handler |
| = new TestProcessingInstrutionHandler(); |
| reader.setContentHandler(handler); |
| |
| reader.parse(new InputSource(in)); |
| |
| assertEquals("bob", handler.target); |
| assertEquals("lee", handler.data); |
| } |
| |
| static class TestProcessingInstrutionHandler extends DefaultHandler2 { |
| |
| String target; |
| String data; |
| |
| @Override |
| public void processingInstruction(String target, String data) { |
| this.target = target; |
| this.data = data; |
| } |
| } |
| |
| public void testExternalEntity() throws IOException, SAXException { |
| class Handler extends DefaultHandler { |
| |
| List<String> elementNames = new ArrayList<String>(); |
| StringBuilder text = new StringBuilder(); |
| |
| public InputSource resolveEntity(String publicId, String systemId) |
| throws IOException, SAXException { |
| if (publicId.equals("publicA") && systemId.equals("systemA")) { |
| return new InputSource(new StringReader("<a/>")); |
| } else if (publicId.equals("publicB") |
| && systemId.equals("systemB")) { |
| /* |
| * Explicitly set the encoding here or else the parser will |
| * try to use the parent parser's encoding which is utf-16. |
| */ |
| InputSource inputSource = new InputSource( |
| new ByteArrayInputStream("bob".getBytes("utf-8"))); |
| inputSource.setEncoding("utf-8"); |
| return inputSource; |
| } |
| |
| throw new AssertionError(); |
| } |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, |
| Attributes attributes) throws SAXException { |
| elementNames.add(localName); |
| } |
| |
| @Override |
| public void endElement(String uri, String localName, String qName) |
| throws SAXException { |
| elementNames.add("/" + localName); |
| } |
| |
| @Override |
| public void characters(char ch[], int start, int length) |
| throws SAXException { |
| text.append(ch, start, length); |
| } |
| } |
| |
| Reader in = new StringReader("<?xml version=\"1.0\"?>\n" |
| + "<!DOCTYPE foo [\n" |
| + " <!ENTITY a PUBLIC 'publicA' 'systemA'>\n" |
| + " <!ENTITY b PUBLIC 'publicB' 'systemB'>\n" |
| + "]>\n" |
| + "<foo>\n" |
| + " &a;<b>&b;</b></foo>"); |
| |
| ExpatReader reader = new ExpatReader(); |
| Handler handler = new Handler(); |
| reader.setContentHandler(handler); |
| reader.setEntityResolver(handler); |
| |
| reader.parse(new InputSource(in)); |
| |
| assertEquals(Arrays.asList("foo", "a", "/a", "b", "/b", "/foo"), |
| handler.elementNames); |
| assertEquals("bob", handler.text.toString().trim()); |
| } |
| |
| public void testExternalEntityDownload() throws IOException, SAXException { |
| final MockWebServer server = new MockWebServer(); |
| server.enqueue(new MockResponse().setBody("<bar></bar>")); |
| server.play(); |
| |
| class Handler extends DefaultHandler { |
| final List<String> elementNames = new ArrayList<String>(); |
| |
| @Override public InputSource resolveEntity(String publicId, String systemId) |
| throws IOException { |
| // The parser should have resolved the systemId. |
| assertEquals(server.getUrl("/systemBar").toString(), systemId); |
| return new InputSource(systemId); |
| } |
| |
| @Override public void startElement(String uri, String localName, String qName, |
| Attributes attributes) { |
| elementNames.add(localName); |
| } |
| |
| @Override public void endElement(String uri, String localName, String qName) { |
| elementNames.add("/" + localName); |
| } |
| } |
| |
| // 'systemBar', the external entity, is relative to 'systemFoo': |
| Reader in = new StringReader("<?xml version=\"1.0\"?>\n" |
| + "<!DOCTYPE foo [\n" |
| + " <!ENTITY bar SYSTEM 'systemBar'>\n" |
| + "]>\n" |
| + "<foo>&bar;</foo>"); |
| ExpatReader reader = new ExpatReader(); |
| Handler handler = new Handler(); |
| reader.setContentHandler(handler); |
| reader.setEntityResolver(handler); |
| InputSource source = new InputSource(in); |
| source.setSystemId(server.getUrl("/systemFoo").toString()); |
| reader.parse(source); |
| assertEquals(Arrays.asList("foo", "bar", "/bar", "/foo"), handler.elementNames); |
| } |
| |
| /** |
| * Parses the given xml string and fires events on the given SAX handler. |
| */ |
| private static void parse(String xml, ContentHandler contentHandler) |
| throws SAXException { |
| try { |
| XMLReader reader = new ExpatReader(); |
| reader.setContentHandler(contentHandler); |
| reader.parse(new InputSource(new StringReader(xml))); |
| } catch (IOException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Parses xml from the given reader and fires events on the given SAX |
| * handler. |
| */ |
| private static void parse(Reader in, ContentHandler contentHandler) |
| throws IOException, SAXException { |
| XMLReader reader = new ExpatReader(); |
| reader.setContentHandler(contentHandler); |
| reader.parse(new InputSource(in)); |
| } |
| |
| /** |
| * Parses xml from the given input stream and fires events on the given SAX |
| * handler. |
| */ |
| private static void parse(InputStream in, Encoding encoding, |
| ContentHandler contentHandler) throws IOException, SAXException { |
| try { |
| XMLReader reader = new ExpatReader(); |
| reader.setContentHandler(contentHandler); |
| InputSource source = new InputSource(in); |
| source.setEncoding(encoding.expatName); |
| reader.parse(source); |
| } catch (IOException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Supported character encodings. |
| */ |
| private enum Encoding { |
| |
| US_ASCII("US-ASCII"), |
| UTF_8("UTF-8"), |
| UTF_16("UTF-16"), |
| ISO_8859_1("ISO-8859-1"); |
| |
| final String expatName; |
| |
| Encoding(String expatName) { |
| this.expatName = expatName; |
| } |
| } |
| } |