Home | History | Annotate | Download | only in xml
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package libcore.xml;
     18 
     19 import com.google.mockwebserver.MockResponse;
     20 import com.google.mockwebserver.MockWebServer;
     21 import java.io.ByteArrayInputStream;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.io.Reader;
     25 import java.io.StringReader;
     26 import java.util.ArrayList;
     27 import java.util.Arrays;
     28 import java.util.HashMap;
     29 import java.util.List;
     30 import java.util.Map;
     31 import junit.framework.Assert;
     32 import junit.framework.TestCase;
     33 import org.apache.harmony.xml.ExpatReader;
     34 import org.xml.sax.Attributes;
     35 import org.xml.sax.ContentHandler;
     36 import org.xml.sax.InputSource;
     37 import org.xml.sax.Locator;
     38 import org.xml.sax.SAXException;
     39 import org.xml.sax.SAXParseException;
     40 import org.xml.sax.XMLReader;
     41 import org.xml.sax.ext.DefaultHandler2;
     42 import org.xml.sax.helpers.DefaultHandler;
     43 
     44 public class ExpatSaxParserTest extends TestCase {
     45 
     46     private static final String SNIPPET = "<dagny dad=\"bob\">hello</dagny>";
     47 
     48     public void testGlobalReferenceTableOverflow() throws Exception {
     49         // We used to use a JNI global reference per interned string.
     50         // Framework apps have a limit of 2000 JNI global references per VM.
     51         StringBuilder xml = new StringBuilder();
     52         xml.append("<root>");
     53         for (int i = 0; i < 4000; ++i) {
     54             xml.append("<tag" + i + ">");
     55             xml.append("</tag" + i + ">");
     56         }
     57         xml.append("</root>");
     58         parse(xml.toString(), new DefaultHandler());
     59     }
     60 
     61     public void testExceptions() {
     62         // From startElement().
     63         ContentHandler contentHandler = new DefaultHandler() {
     64             @Override
     65             public void startElement(String uri, String localName,
     66                     String qName, Attributes attributes)
     67                     throws SAXException {
     68                 throw new SAXException();
     69             }
     70         };
     71         try {
     72             parse(SNIPPET, contentHandler);
     73             fail();
     74         } catch (SAXException checked) { /* expected */ }
     75 
     76         // From endElement().
     77         contentHandler = new DefaultHandler() {
     78             @Override
     79             public void endElement(String uri, String localName,
     80                     String qName)
     81                     throws SAXException {
     82                 throw new SAXException();
     83             }
     84         };
     85         try {
     86             parse(SNIPPET, contentHandler);
     87             fail();
     88         } catch (SAXException checked) { /* expected */ }
     89 
     90         // From characters().
     91         contentHandler = new DefaultHandler() {
     92             @Override
     93             public void characters(char ch[], int start, int length)
     94                     throws SAXException {
     95                 throw new SAXException();
     96             }
     97         };
     98         try {
     99             parse(SNIPPET, contentHandler);
    100             fail();
    101         } catch (SAXException checked) { /* expected */ }
    102     }
    103 
    104     public void testSax() {
    105         try {
    106             // Parse String.
    107             TestHandler handler = new TestHandler();
    108             parse(SNIPPET, handler);
    109             validate(handler);
    110 
    111             // Parse Reader.
    112             handler = new TestHandler();
    113             parse(new StringReader(SNIPPET), handler);
    114             validate(handler);
    115 
    116             // Parse InputStream.
    117             handler = new TestHandler();
    118             parse(new ByteArrayInputStream(SNIPPET.getBytes()),
    119                     Encoding.UTF_8, handler);
    120             validate(handler);
    121         } catch (SAXException e) {
    122             throw new RuntimeException(e);
    123         } catch (IOException e) {
    124             throw new RuntimeException(e);
    125         }
    126     }
    127 
    128     static void validate(TestHandler handler) {
    129         assertEquals("dagny", handler.startElementName);
    130         assertEquals("dagny", handler.endElementName);
    131         assertEquals("hello", handler.text.toString());
    132     }
    133 
    134     static class TestHandler extends DefaultHandler {
    135 
    136         String startElementName;
    137         String endElementName;
    138         StringBuilder text = new StringBuilder();
    139 
    140         @Override
    141         public void startElement(String uri, String localName, String qName,
    142                 Attributes attributes) throws SAXException {
    143 
    144             assertNull(this.startElementName);
    145             this.startElementName = localName;
    146 
    147             // Validate attributes.
    148             assertEquals(1, attributes.getLength());
    149             assertEquals("", attributes.getURI(0));
    150             assertEquals("dad", attributes.getLocalName(0));
    151             assertEquals("bob", attributes.getValue(0));
    152             assertEquals(0, attributes.getIndex("", "dad"));
    153             assertEquals("bob", attributes.getValue("", "dad"));
    154         }
    155 
    156         @Override
    157         public void endElement(String uri, String localName, String qName)
    158                 throws SAXException {
    159             assertNull(this.endElementName);
    160             this.endElementName = localName;
    161         }
    162 
    163         @Override
    164         public void characters(char ch[], int start, int length)
    165                 throws SAXException {
    166             this.text.append(ch, start, length);
    167         }
    168     }
    169 
    170     static final String XML =
    171         "<one xmlns='ns:default' xmlns:n1='ns:1' a='b'>\n"
    172               + "  <n1:two c='d' n1:e='f' xmlns:n2='ns:2'>text</n1:two>\n"
    173               + "</one>";
    174 
    175     public void testNamespaces() {
    176         try {
    177             NamespaceHandler handler = new NamespaceHandler();
    178             parse(XML, handler);
    179             handler.validate();
    180         } catch (SAXException e) {
    181             throw new RuntimeException(e);
    182         }
    183     }
    184 
    185     static class NamespaceHandler implements ContentHandler {
    186 
    187         Locator locator;
    188         boolean documentStarted;
    189         boolean documentEnded;
    190         Map<String, String> prefixMappings = new HashMap<String, String>();
    191 
    192         boolean oneStarted;
    193         boolean twoStarted;
    194         boolean oneEnded;
    195         boolean twoEnded;
    196 
    197         public void validate() {
    198             assertTrue(documentEnded);
    199         }
    200 
    201         public void setDocumentLocator(Locator locator) {
    202             this.locator = locator;
    203         }
    204 
    205         public void startDocument() throws SAXException {
    206             documentStarted = true;
    207             assertNotNull(locator);
    208             assertEquals(0, prefixMappings.size());
    209             assertFalse(documentEnded);
    210         }
    211 
    212         public void endDocument() throws SAXException {
    213             assertTrue(documentStarted);
    214             assertTrue(oneEnded);
    215             assertTrue(twoEnded);
    216             assertEquals(0, prefixMappings.size());
    217             documentEnded = true;
    218         }
    219 
    220         public void startPrefixMapping(String prefix, String uri)
    221                 throws SAXException {
    222             prefixMappings.put(prefix, uri);
    223         }
    224 
    225         public void endPrefixMapping(String prefix) throws SAXException {
    226             assertNotNull(prefixMappings.remove(prefix));
    227         }
    228 
    229         public void startElement(String uri, String localName, String qName,
    230                 Attributes atts) throws SAXException {
    231 
    232             if (localName == "one") {
    233                 assertEquals(2, prefixMappings.size());
    234 
    235                 assertEquals(1, locator.getLineNumber());
    236 
    237                 assertFalse(oneStarted);
    238                 assertFalse(twoStarted);
    239                 assertFalse(oneEnded);
    240                 assertFalse(twoEnded);
    241 
    242                 oneStarted = true;
    243 
    244                 assertSame("ns:default", uri);
    245                 assertEquals("one", qName);
    246 
    247                 // Check atts.
    248                 assertEquals(1, atts.getLength());
    249 
    250                 assertSame("", atts.getURI(0));
    251                 assertSame("a", atts.getLocalName(0));
    252                 assertEquals("b", atts.getValue(0));
    253                 assertEquals(0, atts.getIndex("", "a"));
    254                 assertEquals("b", atts.getValue("", "a"));
    255 
    256                 return;
    257             }
    258 
    259             if (localName == "two") {
    260                 assertEquals(3, prefixMappings.size());
    261 
    262                 assertTrue(oneStarted);
    263                 assertFalse(twoStarted);
    264                 assertFalse(oneEnded);
    265                 assertFalse(twoEnded);
    266 
    267                 twoStarted = true;
    268 
    269                 assertSame("ns:1", uri);
    270                 Assert.assertEquals("n1:two", qName);
    271 
    272                 // Check atts.
    273                 assertEquals(2, atts.getLength());
    274 
    275                 assertSame("", atts.getURI(0));
    276                 assertSame("c", atts.getLocalName(0));
    277                 assertEquals("d", atts.getValue(0));
    278                 assertEquals(0, atts.getIndex("", "c"));
    279                 assertEquals("d", atts.getValue("", "c"));
    280 
    281                 assertSame("ns:1", atts.getURI(1));
    282                 assertSame("e", atts.getLocalName(1));
    283                 assertEquals("f", atts.getValue(1));
    284                 assertEquals(1, atts.getIndex("ns:1", "e"));
    285                 assertEquals("f", atts.getValue("ns:1", "e"));
    286 
    287                 // We shouldn't find these.
    288                 assertEquals(-1, atts.getIndex("ns:default", "e"));
    289                 assertEquals(null, atts.getValue("ns:default", "e"));
    290 
    291                 return;
    292             }
    293 
    294             fail();
    295          }
    296 
    297         public void endElement(String uri, String localName, String qName)
    298                 throws SAXException {
    299             if (localName == "one") {
    300                 assertEquals(3, locator.getLineNumber());
    301 
    302                 assertTrue(oneStarted);
    303                 assertTrue(twoStarted);
    304                 assertTrue(twoEnded);
    305                 assertFalse(oneEnded);
    306 
    307                 oneEnded = true;
    308 
    309                 assertSame("ns:default", uri);
    310                 assertEquals("one", qName);
    311 
    312                 return;
    313             }
    314 
    315             if (localName == "two") {
    316                 assertTrue(oneStarted);
    317                 assertTrue(twoStarted);
    318                 assertFalse(twoEnded);
    319                 assertFalse(oneEnded);
    320 
    321                 twoEnded = true;
    322 
    323                 assertSame("ns:1", uri);
    324                 assertEquals("n1:two", qName);
    325 
    326                 return;
    327             }
    328 
    329             fail();
    330         }
    331 
    332         public void characters(char ch[], int start, int length)
    333                 throws SAXException {
    334             String s = new String(ch, start, length).trim();
    335 
    336             if (!s.equals("")) {
    337                 assertTrue(oneStarted);
    338                 assertTrue(twoStarted);
    339                 assertFalse(oneEnded);
    340                 assertFalse(twoEnded);
    341                 assertEquals("text", s);
    342             }
    343         }
    344 
    345         public void ignorableWhitespace(char ch[], int start, int length)
    346                 throws SAXException {
    347             fail();
    348         }
    349 
    350         public void processingInstruction(String target, String data)
    351                 throws SAXException {
    352             fail();
    353         }
    354 
    355         public void skippedEntity(String name) throws SAXException {
    356             fail();
    357         }
    358     }
    359 
    360     private TestDtdHandler runDtdTest(String s) throws Exception {
    361         Reader in = new StringReader(s);
    362         ExpatReader reader = new ExpatReader();
    363         TestDtdHandler handler = new TestDtdHandler();
    364         reader.setContentHandler(handler);
    365         reader.setDTDHandler(handler);
    366         reader.setLexicalHandler(handler);
    367         reader.parse(new InputSource(in));
    368         return handler;
    369     }
    370 
    371     public void testDtdDoctype() throws Exception {
    372         TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee'><a></a>");
    373         assertEquals("foo", handler.name);
    374         assertEquals("bar", handler.publicId);
    375         assertEquals("tee", handler.systemId);
    376         assertTrue(handler.ended);
    377     }
    378 
    379     public void testDtdUnparsedEntity_system() throws Exception {
    380         TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!ENTITY ent SYSTEM 'blah' NDATA poop> ]><a></a>");
    381         assertEquals("ent", handler.ueName);
    382         assertEquals(null, handler.uePublicId);
    383         assertEquals("blah", handler.ueSystemId);
    384         assertEquals("poop", handler.ueNotationName);
    385     }
    386 
    387     public void testDtdUnparsedEntity_public() throws Exception {
    388         TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!ENTITY ent PUBLIC 'a' 'b' NDATA poop> ]><a></a>");
    389         assertEquals("ent", handler.ueName);
    390         assertEquals("a", handler.uePublicId);
    391         assertEquals("b", handler.ueSystemId);
    392         assertEquals("poop", handler.ueNotationName);
    393     }
    394 
    395     public void testDtdNotation_system() throws Exception {
    396         TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!NOTATION sn SYSTEM 'nf2'> ]><a></a>");
    397         assertEquals("sn", handler.ndName);
    398         assertEquals(null, handler.ndPublicId);
    399         assertEquals("nf2", handler.ndSystemId);
    400     }
    401 
    402     public void testDtdNotation_public() throws Exception {
    403         TestDtdHandler handler = runDtdTest("<?xml version=\"1.0\"?><!DOCTYPE foo PUBLIC 'bar' 'tee' [ <!NOTATION pn PUBLIC 'nf1'> ]><a></a>");
    404         assertEquals("pn", handler.ndName);
    405         assertEquals("nf1", handler.ndPublicId);
    406         assertEquals(null, handler.ndSystemId);
    407     }
    408 
    409     static class TestDtdHandler extends DefaultHandler2 {
    410 
    411         String name;
    412         String publicId;
    413         String systemId;
    414         String ndName;
    415         String ndPublicId;
    416         String ndSystemId;
    417         String ueName;
    418         String uePublicId;
    419         String ueSystemId;
    420         String ueNotationName;
    421 
    422         boolean ended;
    423 
    424         Locator locator;
    425 
    426         @Override
    427         public void startDTD(String name, String publicId, String systemId) {
    428             this.name = name;
    429             this.publicId = publicId;
    430             this.systemId = systemId;
    431         }
    432 
    433         @Override
    434         public void endDTD() {
    435             ended = true;
    436         }
    437 
    438         @Override
    439         public void setDocumentLocator(Locator locator) {
    440             this.locator = locator;
    441         }
    442 
    443         @Override
    444         public void notationDecl(String name, String publicId, String systemId) {
    445             this.ndName = name;
    446             this.ndPublicId = publicId;
    447             this.ndSystemId = systemId;
    448         }
    449 
    450         @Override
    451         public void unparsedEntityDecl(String entityName, String publicId, String systemId, String notationName) {
    452             this.ueName = entityName;
    453             this.uePublicId = publicId;
    454             this.ueSystemId = systemId;
    455             this.ueNotationName = notationName;
    456         }
    457     }
    458 
    459     public void testCdata() throws Exception {
    460         Reader in = new StringReader(
    461             "<a><![CDATA[<b></b>]]> <![CDATA[<c></c>]]></a>");
    462 
    463         ExpatReader reader = new ExpatReader();
    464         TestCdataHandler handler = new TestCdataHandler();
    465         reader.setContentHandler(handler);
    466         reader.setLexicalHandler(handler);
    467 
    468         reader.parse(new InputSource(in));
    469 
    470         assertEquals(2, handler.startCdata);
    471         assertEquals(2, handler.endCdata);
    472         assertEquals("<b></b> <c></c>", handler.buffer.toString());
    473     }
    474 
    475     static class TestCdataHandler extends DefaultHandler2 {
    476 
    477         int startCdata, endCdata;
    478         StringBuffer buffer = new StringBuffer();
    479 
    480         @Override
    481         public void characters(char ch[], int start, int length) {
    482             buffer.append(ch, start, length);
    483         }
    484 
    485         @Override
    486         public void startCDATA() throws SAXException {
    487             startCdata++;
    488         }
    489 
    490         @Override
    491         public void endCDATA() throws SAXException {
    492             endCdata++;
    493         }
    494     }
    495 
    496     public void testProcessingInstructions() throws IOException, SAXException {
    497         Reader in = new StringReader(
    498             "<?bob lee?><a></a>");
    499 
    500         ExpatReader reader = new ExpatReader();
    501         TestProcessingInstrutionHandler handler
    502                 = new TestProcessingInstrutionHandler();
    503         reader.setContentHandler(handler);
    504 
    505         reader.parse(new InputSource(in));
    506 
    507         assertEquals("bob", handler.target);
    508         assertEquals("lee", handler.data);
    509     }
    510 
    511     static class TestProcessingInstrutionHandler extends DefaultHandler2 {
    512 
    513         String target;
    514         String data;
    515 
    516         @Override
    517         public void processingInstruction(String target, String data) {
    518             this.target = target;
    519             this.data = data;
    520         }
    521     }
    522 
    523     public void testExternalEntity() throws IOException, SAXException {
    524         class Handler extends DefaultHandler {
    525 
    526             List<String> elementNames = new ArrayList<String>();
    527             StringBuilder text = new StringBuilder();
    528 
    529             public InputSource resolveEntity(String publicId, String systemId)
    530                     throws IOException, SAXException {
    531                 if (publicId.equals("publicA") && systemId.equals("systemA")) {
    532                     return new InputSource(new StringReader("<a/>"));
    533                 } else if (publicId.equals("publicB")
    534                         && systemId.equals("systemB")) {
    535                     /*
    536                      * Explicitly set the encoding here or else the parser will
    537                      * try to use the parent parser's encoding which is utf-16.
    538                      */
    539                     InputSource inputSource = new InputSource(
    540                             new ByteArrayInputStream("bob".getBytes("utf-8")));
    541                     inputSource.setEncoding("utf-8");
    542                     return inputSource;
    543                 }
    544 
    545                 throw new AssertionError();
    546             }
    547 
    548             @Override
    549             public void startElement(String uri, String localName, String qName,
    550                     Attributes attributes) throws SAXException {
    551                 elementNames.add(localName);
    552             }
    553 
    554             @Override
    555             public void endElement(String uri, String localName, String qName)
    556                     throws SAXException {
    557                 elementNames.add("/" + localName);
    558             }
    559 
    560             @Override
    561             public void characters(char ch[], int start, int length)
    562                     throws SAXException {
    563                 text.append(ch, start, length);
    564             }
    565         }
    566 
    567         Reader in = new StringReader("<?xml version=\"1.0\"?>\n"
    568             + "<!DOCTYPE foo [\n"
    569             + "  <!ENTITY a PUBLIC 'publicA' 'systemA'>\n"
    570             + "  <!ENTITY b PUBLIC 'publicB' 'systemB'>\n"
    571             + "]>\n"
    572             + "<foo>\n"
    573             + "  &a;<b>&b;</b></foo>");
    574 
    575         ExpatReader reader = new ExpatReader();
    576         Handler handler = new Handler();
    577         reader.setContentHandler(handler);
    578         reader.setEntityResolver(handler);
    579 
    580         reader.parse(new InputSource(in));
    581 
    582         assertEquals(Arrays.asList("foo", "a", "/a", "b", "/b", "/foo"),
    583                 handler.elementNames);
    584         assertEquals("bob", handler.text.toString().trim());
    585     }
    586 
    587     public void testExternalEntityDownload() throws IOException, SAXException {
    588         final MockWebServer server = new MockWebServer();
    589         server.enqueue(new MockResponse().setBody("<bar></bar>"));
    590         server.play();
    591 
    592         class Handler extends DefaultHandler {
    593             final List<String> elementNames = new ArrayList<String>();
    594 
    595             @Override public InputSource resolveEntity(String publicId, String systemId)
    596                     throws IOException {
    597                 // The parser should have resolved the systemId.
    598                 assertEquals(server.getUrl("/systemBar").toString(), systemId);
    599                 return new InputSource(systemId);
    600             }
    601 
    602             @Override public void startElement(String uri, String localName, String qName,
    603                     Attributes attributes) {
    604                 elementNames.add(localName);
    605             }
    606 
    607             @Override public void endElement(String uri, String localName, String qName) {
    608                 elementNames.add("/" + localName);
    609             }
    610         }
    611 
    612         // 'systemBar', the external entity, is relative to 'systemFoo':
    613         Reader in = new StringReader("<?xml version=\"1.0\"?>\n"
    614             + "<!DOCTYPE foo [\n"
    615             + "  <!ENTITY bar SYSTEM 'systemBar'>\n"
    616             + "]>\n"
    617             + "<foo>&bar;</foo>");
    618         ExpatReader reader = new ExpatReader();
    619         Handler handler = new Handler();
    620         reader.setContentHandler(handler);
    621         reader.setEntityResolver(handler);
    622         InputSource source = new InputSource(in);
    623         source.setSystemId(server.getUrl("/systemFoo").toString());
    624         reader.parse(source);
    625         assertEquals(Arrays.asList("foo", "bar", "/bar", "/foo"), handler.elementNames);
    626         server.shutdown();
    627     }
    628 
    629     /**
    630      * A little endian UTF-16 file with an odd number of bytes.
    631      */
    632     public void testBug28698301_1() throws Exception {
    633         checkBug28698301("bug28698301-1.xml");
    634     }
    635 
    636     /**
    637      * A little endian UTF-16 file with an even number of bytes that didn't exhibit the problem
    638      * reported in the bug.
    639      */
    640     public void testBug28698301_2() throws Exception {
    641         checkBug28698301("bug28698301-2.xml");
    642     }
    643 
    644     /**
    645      * A big endian UTF-16 file with an odd number of bytes.
    646      */
    647     public void testBug28698301_3() throws Exception {
    648         checkBug28698301("bug28698301-3.xml");
    649     }
    650 
    651     /**
    652      * This tests what happens when UTF-16 input (little and big endian) that has an odd number of
    653      * bytes (and hence is invalid UTF-16) is parsed by Expat.
    654      *
    655      * <p>Prior to the patch the files would cause the pointer into the byte buffer to jump past
    656      * the end of the buffer and keep reading. Once it had jumped past it would continue reading
    657      * from memory until it hit a check that caused it to stop or caused a SIGSEGV. If a SIGSEGV
    658      * was not thrown that lead to spurious and misleading errors being reported.
    659      *
    660      * <p>The initial jump was caused because it was not checking to make sure that there were
    661      * enough bytes to read a whole UTF-16 character. It kept reading because most of the buffer
    662      * range checks used == and != rather than >= and <. The patch fixes the initial jump and then
    663      * uses inequalities in the range check to fail fast in the event of another overflow bug.
    664      */
    665     private void checkBug28698301(String name) throws IOException, SAXException {
    666         InputStream is = getClass().getResourceAsStream(name);
    667         try {
    668             parse(is, Encoding.UTF_16, new TestHandler());
    669         } catch (SAXParseException exception) {
    670             String message = exception.getMessage();
    671             if (!message.contains("no element found")) {
    672                 fail("Expected 'no element found' exception, found: " + message);
    673             }
    674         }
    675     }
    676 
    677     /**
    678      * Parses the given xml string and fires events on the given SAX handler.
    679      */
    680     private static void parse(String xml, ContentHandler contentHandler)
    681             throws SAXException {
    682         try {
    683             XMLReader reader = new ExpatReader();
    684             reader.setContentHandler(contentHandler);
    685             reader.parse(new InputSource(new StringReader(xml)));
    686         } catch (IOException e) {
    687             throw new AssertionError(e);
    688         }
    689     }
    690 
    691     /**
    692      * Parses xml from the given reader and fires events on the given SAX
    693      * handler.
    694      */
    695     private static void parse(Reader in, ContentHandler contentHandler)
    696             throws IOException, SAXException {
    697         XMLReader reader = new ExpatReader();
    698         reader.setContentHandler(contentHandler);
    699         reader.parse(new InputSource(in));
    700     }
    701 
    702     /**
    703      * Parses xml from the given input stream and fires events on the given SAX
    704      * handler.
    705      */
    706     private static void parse(InputStream in, Encoding encoding,
    707             ContentHandler contentHandler) throws IOException, SAXException {
    708         try {
    709             XMLReader reader = new ExpatReader();
    710             reader.setContentHandler(contentHandler);
    711             InputSource source = new InputSource(in);
    712             source.setEncoding(encoding.expatName);
    713             reader.parse(source);
    714         } catch (IOException e) {
    715             throw new AssertionError(e);
    716         }
    717     }
    718 
    719     /**
    720      * Supported character encodings.
    721      */
    722     private enum Encoding {
    723 
    724         US_ASCII("US-ASCII"),
    725         UTF_8("UTF-8"),
    726         UTF_16("UTF-16"),
    727         ISO_8859_1("ISO-8859-1");
    728 
    729         final String expatName;
    730 
    731         Encoding(String expatName) {
    732             this.expatName = expatName;
    733         }
    734     }
    735 }
    736