Merge "Fix XML DOM test failures and close guard warnings." into dalvik-dev
diff --git a/luni/src/main/java/libcore/base/Streams.java b/luni/src/main/java/libcore/base/Streams.java
index 0ef5189..037d988 100644
--- a/luni/src/main/java/libcore/base/Streams.java
+++ b/luni/src/main/java/libcore/base/Streams.java
@@ -20,6 +20,7 @@
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.concurrent.atomic.AtomicReference;
 
 public final class Streams {
@@ -112,4 +113,19 @@
 
         return skipped;
     }
+
+    /**
+     * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
+     * Returns the total number of bytes transferred.
+     */
+    public static int copy(InputStream in, OutputStream out) throws IOException {
+        int total = 0;
+        byte[] buffer = new byte[8192];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            total += c;
+            out.write(buffer, 0, c);
+        }
+        return total;
+    }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java b/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
index 224e59e..e2ff801 100644
--- a/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
@@ -19,8 +19,8 @@
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLConnection;
-import java.util.StringTokenizer;
 import javax.xml.parsers.DocumentBuilder;
+import libcore.io.IoUtils;
 import org.apache.harmony.xml.dom.CDATASectionImpl;
 import org.apache.harmony.xml.dom.DOMImplementationImpl;
 import org.apache.harmony.xml.dom.DocumentImpl;
@@ -93,7 +93,7 @@
     @Override
     public Document parse(InputSource source) throws SAXException, IOException {
         if (source == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("source == null");
         }
 
         String namespaceURI = null;
@@ -105,8 +105,8 @@
                 dom, namespaceURI, qualifiedName, doctype, inputEncoding);
         document.setDocumentURI(systemId);
 
+        KXmlParser parser = new KXmlParser();
         try {
-            KXmlParser parser = new KXmlParser();
             parser.keepNamespaceAttributes();
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, namespaceAware);
 
@@ -121,24 +121,22 @@
                 // TODO: if null, extract the inputEncoding from the Content-Type header?
                 parser.setInput(urlConnection.getInputStream(), inputEncoding);
             } else {
-                throw new SAXParseException(
-                        "InputSource needs a stream, reader or URI", null);
+                throw new SAXParseException("InputSource needs a stream, reader or URI", null);
             }
 
-            if(parser.nextToken() == XmlPullParser.END_DOCUMENT) {
-                throw new SAXParseException(
-                        "Unexpected end of document", null);
+            if (parser.nextToken() == XmlPullParser.END_DOCUMENT) {
+                throw new SAXParseException("Unexpected end of document", null);
             }
 
             parse(parser, document, document, XmlPullParser.END_DOCUMENT);
 
             parser.require(XmlPullParser.END_DOCUMENT, null, null);
         } catch (XmlPullParserException ex) {
-            if(ex.getDetail() instanceof IOException) {
-                throw (IOException)ex.getDetail();
+            if (ex.getDetail() instanceof IOException) {
+                throw (IOException) ex.getDetail();
             }
-            if(ex.getDetail() instanceof RuntimeException) {
-                throw (RuntimeException)ex.getDetail();
+            if (ex.getDetail() instanceof RuntimeException) {
+                throw (RuntimeException) ex.getDetail();
             }
 
             LocatorImpl locator = new LocatorImpl();
@@ -148,14 +146,15 @@
             locator.setLineNumber(ex.getLineNumber());
             locator.setColumnNumber(ex.getColumnNumber());
 
-            SAXParseException newEx = new SAXParseException(ex.getMessage(),
-                    locator);
+            SAXParseException newEx = new SAXParseException(ex.getMessage(), locator);
 
             if (errorHandler != null) {
                 errorHandler.error(newEx);
             }
 
             throw newEx;
+        } finally {
+            IoUtils.closeQuietly(parser);
         }
 
         return document;
@@ -178,7 +177,7 @@
      * @throws XmlPullParserException If a parsing error occurs.
      * @throws IOException If a general IO error occurs.
      */
-    private void parse(XmlPullParser parser, DocumentImpl document, Node node,
+    private void parse(KXmlParser parser, DocumentImpl document, Node node,
             int endToken) throws XmlPullParserException, IOException {
 
         int token = parser.getEventType();
@@ -205,45 +204,10 @@
                 node.appendChild(document.createProcessingInstruction(target,
                         data));
             } else if (token == XmlPullParser.DOCDECL) {
-                /*
-                 * Found a document type declaration. Unfortunately KXML doesn't
-                 * have the necessary details. Do we parse it ourselves, or do
-                 * we silently ignore it, since it isn't mandatory in DOM 2
-                 * anyway?
-                 */
-                StringTokenizer tokenizer = new StringTokenizer(parser.getText());
-                if (tokenizer.hasMoreTokens()) {
-                    String name = tokenizer.nextToken();
-                    String pubid = null;
-                    String sysid = null;
-
-                    if (tokenizer.hasMoreTokens()) {
-                        String text = tokenizer.nextToken();
-
-                        if ("SYSTEM".equals(text)) {
-                            if (tokenizer.hasMoreTokens()) {
-                                sysid = tokenizer.nextToken();
-                            }
-                        } else if ("PUBLIC".equals(text)) {
-                            if (tokenizer.hasMoreTokens()) {
-                                pubid = tokenizer.nextToken();
-                            }
-                            if (tokenizer.hasMoreTokens()) {
-                                sysid = tokenizer.nextToken();
-                            }
-                        }
-                    }
-
-                    if (pubid != null && pubid.length() >= 2 && pubid.startsWith("\"") && pubid.endsWith("\"")) {
-                        pubid = pubid.substring(1, pubid.length() - 1);
-                    }
-
-                    if (sysid != null && sysid.length() >= 2 && sysid.startsWith("\"") && sysid.endsWith("\"")) {
-                        sysid = sysid.substring(1, sysid.length() - 1);
-                    }
-
-                    document.appendChild(new DocumentTypeImpl(document, name, pubid, sysid));
-                }
+                String name = parser.getRootElementName();
+                String publicId = parser.getPublicId();
+                String systemId = parser.getSystemId();
+                document.appendChild(new DocumentTypeImpl(document, name, publicId, systemId));
 
             } else if (token == XmlPullParser.COMMENT) {
                 /*
diff --git a/luni/src/test/java/libcore/java/util/jar/DalvikExecTest.java b/luni/src/test/java/libcore/java/util/jar/DalvikExecTest.java
index 1f66653..24ecd84 100644
--- a/luni/src/test/java/libcore/java/util/jar/DalvikExecTest.java
+++ b/luni/src/test/java/libcore/java/util/jar/DalvikExecTest.java
@@ -27,6 +27,7 @@
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
 import junit.framework.TestCase;
+import libcore.base.Streams;
 import static tests.support.Support_Exec.execAndGetOutput;
 import tests.support.resource.Support_Resources;
 
@@ -113,7 +114,7 @@
 
         // Fill in the classes.dex contents, i.e. the Dalvik executable code:
         // (See below for the detailed source code contents.)
-        Support_Resources.writeResourceToStream("cts_dalvikExecTest_classes.dex", jarOut);
+        Streams.copy(Support_Resources.getResourceStream("cts_dalvikExecTest_classes.dex"), jarOut);
 
         // Now add a resource file:
         //
@@ -168,7 +169,7 @@
 
         // Fill in the classes.dex contents, i.e. the Dalvik executable code:
         // (See below for the detailed source code contents.)
-        Support_Resources.writeResourceToStream("cts_dalvikExecTest_classes.dex", jarOut);
+        Streams.copy(Support_Resources.getResourceStream("cts_dalvikExecTest_classes.dex"), jarOut);
 
         // Now add a resource file:
         //
diff --git a/luni/src/test/java/libcore/xml/PullParserTest.java b/luni/src/test/java/libcore/xml/PullParserTest.java
index dc5a133..21342b2 100644
--- a/luni/src/test/java/libcore/xml/PullParserTest.java
+++ b/luni/src/test/java/libcore/xml/PullParserTest.java
@@ -254,7 +254,8 @@
         assertEquals(XmlPullParser.START_TAG, parser.next());
         assertEquals(XmlPullParser.TEXT, parser.next());
         assertEquals(null, parser.getName());
-        assertEquals("&aaa;", parser.getText());
+        assertEquals("Expected unresolved entities to be left in-place. The old parser "
+                + "would resolve these to the empty string.", "&aaa;", parser.getText());
         assertEquals(XmlPullParser.END_TAG, parser.next());
     }
 
@@ -279,7 +280,7 @@
         assertEquals(XmlPullParser.END_TAG, parser.next());
     }
 
-    public void testEntityInAttributeWithNextToken() throws Exception {
+    public void testEntityInAttributeUsingNextToken() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo bar=\"&amp;\"></foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.nextToken());
@@ -287,6 +288,45 @@
         assertEquals("&", parser.getAttributeValue(null, "bar"));
     }
 
+    public void testMissingEntitiesInAttributesUsingNext() throws Exception {
+        XmlPullParser parser = newPullParser();
+        parser.setInput(new StringReader("<foo b='&aaa;'></foo>"));
+        assertNextFails(parser);
+    }
+
+    public void testMissingEntitiesInAttributesUsingNextWithRelaxed() throws Exception {
+        XmlPullParser parser = newPullParser();
+        parser.setInput(new StringReader("<foo b='&aaa;'></foo>"));
+        parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true);
+        assertEquals(XmlPullParser.START_TAG, parser.nextToken());
+        assertEquals(1, parser.getAttributeCount());
+        assertEquals("b", parser.getAttributeName(0));
+        assertEquals("Expected unresolved entities to be left in-place. The old parser "
+                + "would resolve these to the empty string.", "&aaa;", parser.getAttributeValue(0));
+    }
+
+    public void testMissingEntitiesInAttributesUsingNextToken() throws Exception {
+        XmlPullParser parser = newPullParser();
+        parser.setInput(new StringReader("<foo b='&aaa;'></foo>"));
+        testMissingEntitiesInAttributesUsingNextToken(parser);
+    }
+
+    public void testMissingEntitiesInAttributesUsingNextTokenWithRelaxed() throws Exception {
+        XmlPullParser parser = newPullParser();
+        parser.setInput(new StringReader("<foo b='&aaa;'></foo>"));
+        parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true);
+        testMissingEntitiesInAttributesUsingNextToken(parser);
+    }
+
+    private void testMissingEntitiesInAttributesUsingNextToken(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        assertEquals(XmlPullParser.START_TAG, parser.nextToken());
+        assertEquals(1, parser.getAttributeCount());
+        assertEquals("b", parser.getAttributeName(0));
+        assertEquals("Expected unresolved entities to be left in-place. The old parser "
+                + "would resolve these to the empty string.", "&aaa;", parser.getAttributeValue(0));
+    }
+
     public void testGreaterThanInText() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>></foo>"));
@@ -392,7 +432,7 @@
         assertEquals("]]]]]]]]]]]]]]]]]]]]]]]", parser.getText());
     }
 
-    public void testCommentWithNext() throws Exception {
+    public void testCommentUsingNext() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>ab<!-- comment! -->cd</foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.next());
@@ -400,7 +440,7 @@
         assertEquals("abcd", parser.getText());
     }
 
-    public void testCommentWithNextToken() throws Exception {
+    public void testCommentUsingNextToken() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>ab<!-- comment! -->cd</foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.next());
@@ -412,7 +452,7 @@
         assertEquals("cd", parser.getText());
     }
 
-    public void testCdataWithNext() throws Exception {
+    public void testCdataUsingNext() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>ab<![CDATA[cdef]]gh&amp;i]]>jk</foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.next());
@@ -421,7 +461,7 @@
         assertEquals(XmlPullParser.END_TAG, parser.next());
     }
 
-    public void testCdataWithNextToken() throws Exception {
+    public void testCdataUsingNextToken() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>ab<![CDATA[cdef]]gh&amp;i]]>jk</foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.next());
@@ -442,7 +482,7 @@
         assertEquals("]]>", parser.getText());
     }
 
-    public void testProcessingInstructionWithNext() throws Exception {
+    public void testProcessingInstructionUsingNext() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>ab<?cd efg hij?>kl</foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.next());
@@ -451,7 +491,7 @@
         assertEquals(XmlPullParser.END_TAG, parser.next());
     }
 
-    public void testProcessingInstructionWithNextToken() throws Exception {
+    public void testProcessingInstructionUsingNextToken() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>ab<?cd efg hij?>kl</foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.nextToken());
@@ -464,7 +504,7 @@
         assertEquals(XmlPullParser.END_TAG, parser.next());
     }
 
-    public void testWhitespaceWithNextToken() throws Exception {
+    public void testWhitespaceUsingNextToken() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("  \n  <foo> \n </foo>   \n   "));
         assertEquals(XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken());
@@ -513,7 +553,7 @@
         assertEquals("7,7", parser.getLineNumber() + "," + parser.getColumnNumber());
     }
 
-    public void testEmptyEntityReferenceWithNext() throws Exception {
+    public void testEmptyEntityReferenceUsingNext() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>&empty;</foo>"));
         parser.defineEntityReplacementText("empty", "");
@@ -521,7 +561,7 @@
         assertEquals(XmlPullParser.END_TAG, parser.next());
     }
 
-    public void testEmptyEntityReferenceWithNextToken() throws Exception {
+    public void testEmptyEntityReferenceUsingNextToken() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo>&empty;</foo>"));
         parser.defineEntityReplacementText("empty", "");
@@ -532,14 +572,14 @@
         assertEquals(XmlPullParser.END_TAG, parser.nextToken());
     }
 
-    public void testEmptyCdataWithNext() throws Exception {
+    public void testEmptyCdataUsingNext() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo><![CDATA[]]></foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.next());
         assertEquals(XmlPullParser.END_TAG, parser.next());
     }
 
-    public void testEmptyCdataWithNextToken() throws Exception {
+    public void testEmptyCdataUsingNextToken() throws Exception {
         XmlPullParser parser = newPullParser();
         parser.setInput(new StringReader("<foo><![CDATA[]]></foo>"));
         assertEquals(XmlPullParser.START_TAG, parser.next());
diff --git a/luni/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java b/luni/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
index 04e2c53..5bcc7ac 100644
--- a/luni/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
+++ b/luni/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
@@ -15,22 +15,22 @@
  */
 package tests.api.javax.xml.parsers;
 
+import dalvik.annotation.KnownFailure;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.OutputStream;
 import java.util.HashMap;
 import java.util.Vector;
-
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
-
 import junit.framework.TestCase;
-
 import org.xml.sax.HandlerBase;
 import org.xml.sax.InputSource;
 import org.xml.sax.Parser;
@@ -40,18 +40,12 @@
 import org.xml.sax.XMLReader;
 import org.xml.sax.ext.LexicalHandler;
 import org.xml.sax.helpers.DefaultHandler;
-
 import tests.api.javax.xml.parsers.SAXParserTestSupport.MyDefaultHandler;
 import tests.api.javax.xml.parsers.SAXParserTestSupport.MyHandler;
 import tests.api.org.xml.sax.support.BrokenInputStream;
 import tests.api.org.xml.sax.support.MethodLogger;
 import tests.api.org.xml.sax.support.MockHandler;
 import tests.support.resource.Support_Resources;
-import dalvik.annotation.KnownFailure;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.TestTargets;
 
 @SuppressWarnings("deprecation")
 @TestTargetClass(SAXParser.class)
@@ -454,10 +448,8 @@
         args = {org.xml.sax.InputSource.class, org.xml.sax.helpers.DefaultHandler.class}
     )
     public void test_parseLorg_xml_sax_InputSourceLorg_xml_sax_helpers_DefaultHandler()
-    throws Exception {
-
+            throws Exception {
         for(int i = 0; i < list_wf.length; i++) {
-
             HashMap<String, String> hm = new SAXParserTestSupport().readFile(
                     list_out_dh[i].getPath());
             MyDefaultHandler dh = new MyDefaultHandler();
@@ -466,15 +458,13 @@
             assertTrue(SAXParserTestSupport.equalsMaps(hm, dh.createData()));
         }
 
-        for(int i = 0; i < list_nwf.length; i++) {
+        for (File file : list_nwf) {
             try {
                 MyDefaultHandler dh = new MyDefaultHandler();
-                InputSource is = new InputSource(
-                        new FileInputStream(list_nwf[i]));
+                InputSource is = new InputSource(new FileInputStream(file));
                 parser.parse(is, dh);
                 fail("SAXException is not thrown");
-            } catch(org.xml.sax.SAXException se) {
-                //expected
+            } catch (SAXException expected) {
             }
         }
 
@@ -482,23 +472,21 @@
             MyDefaultHandler dh = new MyDefaultHandler();
             parser.parse((InputSource) null, dh);
             fail("java.lang.IllegalArgumentException is not thrown");
-        } catch(java.lang.IllegalArgumentException iae) {
-            //expected
+        } catch (IllegalArgumentException expected) {
         }
 
-        try {
-            InputSource is = new InputSource(new FileInputStream(list_wf[0]));
-            parser.parse(is, (DefaultHandler) null);
-        } catch(java.lang.IllegalArgumentException iae) {
-            fail("java.lang.IllegalArgumentException is thrown");
-        }
+        InputSource is = new InputSource(new FileInputStream(list_wf[0]));
+        parser.parse(is, (DefaultHandler) null);
 
+        InputStream in = null;
         try {
-            InputSource is = new InputSource(new BrokenInputStream(new FileInputStream(list_wf[0]), 10));
+            in = new BrokenInputStream(new FileInputStream(list_wf[0]), 10);
+            is = new InputSource(in);
             parser.parse(is, (DefaultHandler) null);
             fail("IOException expected");
-        } catch(IOException e) {
-            // Expected
+        } catch(IOException expected) {
+        } finally {
+            in.close();
         }
     }
 
@@ -508,35 +496,22 @@
         method = "parse",
         args = {org.xml.sax.InputSource.class, org.xml.sax.HandlerBase.class}
     )
-    public void testParseInputSourceHandlerBase() {
+    public void testParseInputSourceHandlerBase() throws Exception {
         for(int i = 0; i < list_wf.length; i++) {
-            try {
-                HashMap<String, String> hm = sp.readFile(
-                        list_out_hb[i].getPath());
-                MyHandler dh = new MyHandler();
-                InputSource is = new InputSource(new FileInputStream(list_wf[i]));
-                parser.parse(is, dh);
-                assertTrue(SAXParserTestSupport.equalsMaps(hm,
-                        dh.createData()));
-            } catch (IOException ioe) {
-                fail("Unexpected IOException " + ioe.toString());
-            } catch (SAXException sax) {
-                fail("Unexpected SAXException " + sax.toString());
-            }
+            HashMap<String, String> hm = sp.readFile(list_out_hb[i].getPath());
+            MyHandler dh = new MyHandler();
+            InputSource is = new InputSource(new FileInputStream(list_wf[i]));
+            parser.parse(is, dh);
+            assertTrue(SAXParserTestSupport.equalsMaps(hm, dh.createData()));
         }
 
-        for(int i = 0; i < list_nwf.length; i++) {
+        for (File file : list_nwf) {
             try {
                 MyHandler dh = new MyHandler();
-                InputSource is = new InputSource(new FileInputStream(list_nwf[i]));
+                InputSource is = new InputSource(new FileInputStream(file));
                 parser.parse(is, dh);
                 fail("SAXException is not thrown");
-            } catch(org.xml.sax.SAXException se) {
-                //expected
-            } catch (FileNotFoundException fne) {
-                fail("Unexpected FileNotFoundException " + fne.toString());
-            } catch (IOException ioe) {
-                fail("Unexpected IOException " + ioe.toString());
+            } catch (SAXException expected) {
             }
         }
 
@@ -544,67 +519,29 @@
             MyHandler dh = new MyHandler();
             parser.parse((InputSource) null, dh);
             fail("java.lang.IllegalArgumentException is not thrown");
-        } catch(java.lang.IllegalArgumentException iae) {
-            //expected
-        } catch (IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
+        } catch(IllegalArgumentException expected) {
         }
 
-        try {
-            InputSource is = new InputSource(new FileInputStream(list_wf[0]));
-            parser.parse(is, (HandlerBase) null);
-        } catch(java.lang.IllegalArgumentException iae) {
-            fail("java.lang.IllegalArgumentException is thrown");
-        } catch (FileNotFoundException fne) {
-            fail("Unexpected FileNotFoundException " + fne.toString());
-        } catch(IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
-        }
+        InputSource is = new InputSource(new FileInputStream(list_wf[0]));
+        parser.parse(is, (HandlerBase) null);
 
         // Reader case
-        try {
-            InputSource is = new InputSource(new InputStreamReader(
-                    new FileInputStream(list_wf[0])));
-            parser.parse(is, (HandlerBase) null);
-        } catch(java.lang.IllegalArgumentException iae) {
-            fail("java.lang.IllegalArgumentException is thrown");
-        } catch (FileNotFoundException fne) {
-            fail("Unexpected FileNotFoundException " + fne.toString());
-        } catch(IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
-        }
+        is = new InputSource(new InputStreamReader(new FileInputStream(list_wf[0])));
+        parser.parse(is, (HandlerBase) null);
 
         // SystemID case
-        try {
-            InputSource is = new InputSource(list_wf[0].toURI().toString());
-            parser.parse(is, (HandlerBase) null);
-        } catch(java.lang.IllegalArgumentException iae) {
-            fail("java.lang.IllegalArgumentException is thrown");
-        } catch (FileNotFoundException fne) {
-            fail("Unexpected FileNotFoundException " + fne.toString());
-        } catch(IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
-        }
+        is = new InputSource(list_wf[0].toURI().toString());
+        parser.parse(is, (HandlerBase) null);
 
         // Inject IOException
+        InputStream in = null;
         try {
-            InputStream is = new BrokenInputStream(
-                    new FileInputStream(list_wf[0]), 10);
-            parser.parse(is, (HandlerBase) null,
-                    SAXParserTestSupport.XML_SYSTEM_ID);
+            in = new BrokenInputStream(new FileInputStream(list_wf[0]), 10);
+            parser.parse(in, (HandlerBase) null, SAXParserTestSupport.XML_SYSTEM_ID);
             fail("IOException expected");
-        } catch(IOException e) {
-            // Expected
-        } catch (Exception e) {
-            throw new RuntimeException("Unexpected exception", e);
+        } catch(IOException expected) {
+        } finally {
+            in.close();
         }
     }
 
@@ -784,35 +721,22 @@
         method = "parse",
         args = {java.io.InputStream.class, org.xml.sax.HandlerBase.class}
     )
-    public void testParseInputStreamHandlerBase() {
+    public void testParseInputStreamHandlerBase() throws Exception {
         for(int i = 0; i < list_wf.length; i++) {
-            try {
-                HashMap<String, String> hm = sp.readFile(
-                        list_out_hb[i].getPath());
-                MyHandler dh = new MyHandler();
-                InputStream is = new FileInputStream(list_wf[i]);
-                parser.parse(is, dh);
-                assertTrue(SAXParserTestSupport.equalsMaps(hm,
-                        dh.createData()));
-            } catch (IOException ioe) {
-                fail("Unexpected IOException " + ioe.toString());
-            } catch (SAXException sax) {
-                fail("Unexpected SAXException " + sax.toString());
-            }
+            HashMap<String, String> hm = sp.readFile(list_out_hb[i].getPath());
+            MyHandler dh = new MyHandler();
+            InputStream is = new FileInputStream(list_wf[i]);
+            parser.parse(is, dh);
+            assertTrue(SAXParserTestSupport.equalsMaps(hm, dh.createData()));
         }
 
-        for(int i = 0; i < list_nwf.length; i++) {
+        for (File file : list_nwf) {
             try {
                 MyHandler dh = new MyHandler();
-                InputStream is = new FileInputStream(list_nwf[i]);
+                InputStream is = new FileInputStream(file);
                 parser.parse(is, dh);
                 fail("SAXException is not thrown");
-            } catch(org.xml.sax.SAXException se) {
-                //expected
-            } catch (FileNotFoundException fne) {
-                fail("Unexpected FileNotFoundException " + fne.toString());
-            } catch (IOException ioe) {
-                fail("Unexpected IOException " + ioe.toString());
+            } catch (SAXException expected) {
             }
         }
 
@@ -820,37 +744,21 @@
             MyHandler dh = new MyHandler();
             parser.parse((InputStream) null, dh);
             fail("java.lang.IllegalArgumentException is not thrown");
-        } catch(java.lang.IllegalArgumentException iae) {
-            //expected
-        } catch (IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
+        } catch (IllegalArgumentException expected) {
         }
 
-        try {
-            InputStream is = new FileInputStream(list_wf[0]);
-            parser.parse(is, (HandlerBase) null);
-        } catch(java.lang.IllegalArgumentException iae) {
-            fail("java.lang.IllegalArgumentException is thrown");
-        } catch (FileNotFoundException fne) {
-            fail("Unexpected FileNotFoundException " + fne.toString());
-        } catch(IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
-        }
+        InputStream is = new FileInputStream(list_wf[0]);
+        parser.parse(is, (HandlerBase) null);
 
         // Inject IOException
         try {
-            InputStream is = new BrokenInputStream(
-                    new FileInputStream(list_wf[0]), 10);
+            is = new BrokenInputStream(new FileInputStream(list_wf[0]), 10);
             parser.parse(is, (HandlerBase) null);
             fail("IOException expected");
         } catch(IOException e) {
             // Expected
-        } catch (Exception e) {
-            throw new RuntimeException("Unexpected exception", e);
+        } finally {
+            is.close();
         }
     }
 
@@ -860,76 +768,43 @@
         method = "parse",
         args = {java.io.InputStream.class, org.xml.sax.HandlerBase.class, java.lang.String.class}
     )
-    public void testParseInputStreamHandlerBaseString() {
+    public void testParseInputStreamHandlerBaseString() throws Exception {
         for(int i = 0; i < list_wf.length; i++) {
-            try {
-                HashMap<String, String> hm = sp.readFile(
-                        list_out_hb[i].getPath());
-                MyHandler dh = new MyHandler();
-                InputStream is = new FileInputStream(list_wf[i]);
-                parser.parse(is, dh, SAXParserTestSupport.XML_SYSTEM_ID);
-                assertTrue(SAXParserTestSupport.equalsMaps(hm,
-                        dh.createData()));
-            } catch (IOException ioe) {
-                fail("Unexpected IOException " + ioe.toString());
-            } catch (SAXException sax) {
-                fail("Unexpected SAXException " + sax.toString());
-            }
+            HashMap<String, String> hm = sp.readFile(list_out_hb[i].getPath());
+            MyHandler dh = new MyHandler();
+            InputStream is = new FileInputStream(list_wf[i]);
+            parser.parse(is, dh, SAXParserTestSupport.XML_SYSTEM_ID);
+            assertTrue(SAXParserTestSupport.equalsMaps(hm, dh.createData()));
         }
 
-        for(int i = 0; i < list_nwf.length; i++) {
+        for (File file : list_nwf) {
             try {
                 MyHandler dh = new MyHandler();
-                InputStream is = new FileInputStream(list_nwf[i]);
+                InputStream is = new FileInputStream(file);
                 parser.parse(is, dh, SAXParserTestSupport.XML_SYSTEM_ID);
                 fail("SAXException is not thrown");
-            } catch(org.xml.sax.SAXException se) {
-                //expected
-            } catch (FileNotFoundException fne) {
-                fail("Unexpected FileNotFoundException " + fne.toString());
-            } catch (IOException ioe) {
-                fail("Unexpected IOException " + ioe.toString());
+            } catch (SAXException expected) {
             }
         }
 
         try {
             MyHandler dh = new MyHandler();
-            parser.parse((InputStream) null, dh,
-                    SAXParserTestSupport.XML_SYSTEM_ID);
+            parser.parse(null, dh, SAXParserTestSupport.XML_SYSTEM_ID);
             fail("java.lang.IllegalArgumentException is not thrown");
-        } catch(java.lang.IllegalArgumentException iae) {
-            //expected
-        } catch (IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
+        } catch(IllegalArgumentException expected) {
         }
 
-        try {
-            InputStream is = new FileInputStream(list_wf[0]);
-            parser.parse(is, (HandlerBase) null,
-                    SAXParserTestSupport.XML_SYSTEM_ID);
-        } catch(java.lang.IllegalArgumentException iae) {
-            fail("java.lang.IllegalArgumentException is thrown");
-        } catch (FileNotFoundException fne) {
-            fail("Unexpected FileNotFoundException " + fne.toString());
-        } catch(IOException ioe) {
-            fail("Unexpected IOException " + ioe.toString());
-        } catch(SAXException sax) {
-            fail("Unexpected SAXException " + sax.toString());
-        }
+        InputStream is = new FileInputStream(list_wf[0]);
+        parser.parse(is, (HandlerBase) null, SAXParserTestSupport.XML_SYSTEM_ID);
 
         // Inject IOException
         try {
-            InputStream is = new BrokenInputStream(
-                    new FileInputStream(list_wf[0]), 10);
-            parser.parse(is, (HandlerBase) null,
-                    SAXParserTestSupport.XML_SYSTEM_ID);
+            is = new BrokenInputStream(new FileInputStream(list_wf[0]), 10);
+            parser.parse(is, (HandlerBase) null, SAXParserTestSupport.XML_SYSTEM_ID);
             fail("IOException expected");
-        } catch(IOException e) {
-            // Expected
-        } catch (Exception e) {
-            throw new RuntimeException("Unexpected exception", e);
+        } catch(IOException expected) {
+        } finally {
+            is.close();
         }
     }
 
diff --git a/luni/src/test/java/tests/api/org/xml/sax/support/BrokenInputStream.java b/luni/src/test/java/tests/api/org/xml/sax/support/BrokenInputStream.java
index 578fb60..8136b86 100644
--- a/luni/src/test/java/tests/api/org/xml/sax/support/BrokenInputStream.java
+++ b/luni/src/test/java/tests/api/org/xml/sax/support/BrokenInputStream.java
@@ -47,4 +47,8 @@
         return stream.read();
     }
 
+    @Override
+    public void close() throws IOException {
+        stream.close();
+    }
 }
\ No newline at end of file
diff --git a/luni/src/test/java/tests/xml/DomTest.java b/luni/src/test/java/tests/xml/DomTest.java
index eb3a842..0966c5d 100644
--- a/luni/src/test/java/tests/xml/DomTest.java
+++ b/luni/src/test/java/tests/xml/DomTest.java
@@ -1385,6 +1385,38 @@
         assertEquals(root.getChildNodes().item(0), current);
     }
 
+    public void testPublicIdAndSystemId() throws Exception {
+        document = builder.parse(new InputSource(new StringReader(
+                " <!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
+                        + " \"http://www.w3.org/TR/html4/strict.dtd\">"
+                        + "<html></html>")));
+        doctype = document.getDoctype();
+        assertEquals("html", doctype.getName());
+        assertEquals("-//W3C//DTD HTML 4.01//EN", doctype.getPublicId());
+        assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId());
+    }
+
+    public void testSystemIdOnly() throws Exception {
+        document = builder.parse(new InputSource(new StringReader(
+                " <!DOCTYPE html SYSTEM \"http://www.w3.org/TR/html4/strict.dtd\">"
+                        + "<html></html>")));
+        doctype = document.getDoctype();
+        assertEquals("html", doctype.getName());
+        assertNull(doctype.getPublicId());
+        assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId());
+    }
+
+    public void testSingleQuotedPublicIdAndSystemId() throws Exception {
+        document = builder.parse(new InputSource(new StringReader(
+                " <!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN'"
+                        + " 'http://www.w3.org/TR/html4/strict.dtd'>"
+                        + "<html></html>")));
+        doctype = document.getDoctype();
+        assertEquals("html", doctype.getName());
+        assertEquals("-//W3C//DTD HTML 4.01//EN", doctype.getPublicId());
+        assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId());
+    }
+
     private class RecordingHandler implements UserDataHandler {
         final Set<String> calls = new HashSet<String>();
         public void handle(short operation, String key, Object data, Node src, Node dst) {
diff --git a/support/src/test/java/tests/support/resource/Support_Resources.java b/support/src/test/java/tests/support/resource/Support_Resources.java
index 9643347..3927883 100644
--- a/support/src/test/java/tests/support/resource/Support_Resources.java
+++ b/support/src/test/java/tests/support/resource/Support_Resources.java
@@ -17,6 +17,7 @@
 
 package tests.support.resource;
 
+import libcore.base.Streams;
 import tests.support.Support_Configuration;
 
 import java.io.File;
@@ -24,9 +25,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.MalformedURLException;
-import java.net.URISyntaxException;
 import java.net.URL;
 
 public class Support_Resources {
@@ -116,23 +115,17 @@
         return File.createTempFile("hyts_", suffix, null);
     }
 
-    public static void copyLocalFileto(File dest, InputStream in)
-            throws FileNotFoundException, IOException {
+    public static void copyLocalFileto(File dest, InputStream in) throws IOException {
         if (!dest.exists()) {
             FileOutputStream out = new FileOutputStream(dest);
-            int result;
-            byte[] buf = new byte[4096];
-            while ((result = in.read(buf)) != -1) {
-                out.write(buf, 0, result);
-            }
-            in.close();
+            Streams.copy(in, out);
             out.close();
             dest.deleteOnExit();
         }
+        in.close();
     }
 
-    public static File getExternalLocalFile(String url) throws IOException,
-            MalformedURLException {
+    public static File getExternalLocalFile(String url) throws IOException {
         File resources = createTempFolder();
         InputStream in = new URL(url).openStream();
         File temp = new File(resources.toString() + "/local.tmp");
@@ -151,7 +144,6 @@
      * @return - resource input stream
      */
     public static InputStream getResourceStream(String name) {
-
         InputStream is = Support_Resources.class.getResourceAsStream(name);
 
         if (is == null) {
@@ -165,51 +157,6 @@
         return is;
     }
 
-    /**
-     * Util method to write resource files directly to an OutputStream.
-     *
-     * @param name - name of resource file.
-     * @param out - OutputStream to write to.
-     * @return - number of bytes written to out.
-     */
-    public static int writeResourceToStream(String name, OutputStream out) {
-        InputStream input = getResourceStream(name);
-        byte[] buffer = new byte[512];
-        int total = 0;
-        int count;
-        try {
-            count = input.read(buffer);
-            while (count != -1) {
-                out.write(buffer, 0, count);
-                total = total + count;
-                count = input.read(buffer);
-            }
-            return total;
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to write to passed stream.", e);
-        }
-    }
-
-    /**
-     * Util method to get absolute path to resource file
-     *
-     * @param name - name of resource file
-     * @return - path to resource
-     */
-    public static String getAbsoluteResourcePath(String name) {
-
-        URL url = ClassLoader.getSystemClassLoader().getResource(name);
-        if (url == null) {
-            throw new RuntimeException("Failed to load resource: " + name);
-        }
-
-        try {
-            return new File(url.toURI()).getAbsolutePath();
-        } catch (URISyntaxException e) {
-            throw new RuntimeException("Failed to load resource: " + name);
-        }
-    }
-
     public static File resourceToTempFile(String path) throws IOException {
         File f = File.createTempFile("out", ".xml");
         f.deleteOnExit();
diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java
index 5b84d93..ba905a9 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlParser.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java
@@ -22,6 +22,7 @@
 
 package org.kxml2.io;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -35,7 +36,7 @@
 /**
  * An XML pull parser with limited support for parsing internal DTDs.
  */
-public class KXmlParser implements XmlPullParser {
+public class KXmlParser implements XmlPullParser, Closeable {
 
     private final String PROPERTY_XMLDECL_VERSION
             = "http://xmlpull.org/v1/doc/properties.html#xmldecl-version";
@@ -89,6 +90,9 @@
 
     private String version;
     private Boolean standalone;
+    private String rootElementName;
+    private String systemId;
+    private String publicId;
 
     /**
      * True if the {@code <!DOCTYPE>} contents are handled. The DTD defines
@@ -345,6 +349,7 @@
         name = null;
         namespace = null;
         attributeCount = -1;
+        boolean throwOnResolveFailure = !justOneToken;
 
         while (true) {
             switch (type) {
@@ -354,7 +359,7 @@
              * the end of the document.
              */
             case START_TAG:
-                parseStartTag(false);
+                parseStartTag(false, throwOnResolveFailure);
                 return type;
             case END_TAG:
                 readEndTag();
@@ -369,13 +374,13 @@
             case ENTITY_REF:
                 if (justOneToken) {
                     StringBuilder entityTextBuilder = new StringBuilder();
-                    readEntity(entityTextBuilder, true, ValueContext.TEXT);
+                    readEntity(entityTextBuilder, true, throwOnResolveFailure, ValueContext.TEXT);
                     text = entityTextBuilder.toString();
                     break;
                 }
                 // fall-through
             case TEXT:
-                text = readValue('<', !justOneToken, ValueContext.TEXT);
+                text = readValue('<', !justOneToken, throwOnResolveFailure, ValueContext.TEXT);
                 if (depth == 0 && isWhitespace) {
                     type = IGNORABLE_WHITESPACE;
                 }
@@ -504,7 +509,7 @@
         }
 
         read(START_PROCESSING_INSTRUCTION);
-        parseStartTag(true);
+        parseStartTag(true, true);
 
         if (attributeCount < 1 || !"version".equals(attributes[2])) {
             checkRelaxed("version expected");
@@ -561,8 +566,8 @@
     private void readDoctype() throws IOException, XmlPullParserException {
         read(START_DOCTYPE);
         skip();
-        readName();
-        readExternalId(true);
+        rootElementName = readName();
+        readExternalId(true, true);
         skip();
         if (peekCharacter() == '[') {
             readInternalSubset();
@@ -582,7 +587,7 @@
      *
      * Returns true if any ID was read.
      */
-    private boolean readExternalId(boolean requireSystemName)
+    private boolean readExternalId(boolean requireSystemName, boolean assignFields)
             throws IOException, XmlPullParserException {
         skip();
         int c = peekCharacter();
@@ -592,7 +597,11 @@
         } else if (c == 'P') {
             read(PUBLIC);
             skip();
-            readQuotedId();
+            if (assignFields) {
+                publicId = readQuotedId(true);
+            } else {
+                readQuotedId(false);
+            }
         } else {
             return false;
         }
@@ -606,23 +615,32 @@
             }
         }
 
-        readQuotedId();
+        if (assignFields) {
+            systemId = readQuotedId(true);
+        } else {
+            readQuotedId(false);
+        }
         return true;
     }
 
+    private static final char[] SINGLE_QUOTE = new char[] { '\'' };
+    private static final char[] DOUBLE_QUOTE = new char[] { '"' };
+
     /**
      * Reads a quoted string, performing no entity escaping of the contents.
      */
-    private void readQuotedId() throws IOException, XmlPullParserException {
+    private String readQuotedId(boolean returnText) throws IOException, XmlPullParserException {
         int quote = peekCharacter();
-        if (quote != '"' && quote != '\'') {
+        char[] delimiter;
+        if (quote == '"') {
+            delimiter = DOUBLE_QUOTE;
+        } else if (quote == '\'') {
+            delimiter = SINGLE_QUOTE;
+        } else {
             throw new XmlPullParserException("Expected a quoted string", this, null);
         }
         position++;
-        while (peekCharacter() != quote) {
-            position++;
-        }
-        position++;
+        return readUntil(delimiter, returnText);
     }
 
     private void readInternalSubset() throws IOException, XmlPullParserException {
@@ -814,7 +832,7 @@
             if (c == '"' || c == '\'') {
                 position++;
                 // TODO: does this do escaping correctly?
-                String value = readValue((char) c, true, ValueContext.ATTRIBUTE);
+                String value = readValue((char) c, true, true, ValueContext.ATTRIBUTE);
                 position++;
                 defineAttributeDefault(elementName, attributeName, value);
             }
@@ -863,7 +881,7 @@
         int quote = peekCharacter();
         if (quote == '"' || quote == '\'') {
             position++;
-            String value = readValue((char) quote, true, ValueContext.ENTITY_DECLARATION);
+            String value = readValue((char) quote, true, false, ValueContext.ENTITY_DECLARATION);
             position++;
             if (generalEntity && processDocDecl) {
                 if (documentEntities == null) {
@@ -871,7 +889,7 @@
                 }
                 documentEntities.put(name, value.toCharArray());
             }
-        } else if (readExternalId(true)) {
+        } else if (readExternalId(true, false)) {
             skip();
             if (peekCharacter() == NDATA[0]) {
                 read(NDATA);
@@ -890,7 +908,7 @@
         read(START_NOTATION);
         skip();
         readName();
-        if (!readExternalId(false)) {
+        if (!readExternalId(false, false)) {
             throw new XmlPullParserException(
                     "Expected external ID or public ID for notation", this, null);
         }
@@ -988,7 +1006,8 @@
     /**
      * Sets name and attributes
      */
-    private void parseStartTag(boolean xmldecl) throws IOException, XmlPullParserException {
+    private void parseStartTag(boolean xmldecl, boolean throwOnResolveFailure)
+            throws IOException, XmlPullParserException {
         if (!xmldecl) {
             read('<');
         }
@@ -1056,7 +1075,8 @@
                     throw new XmlPullParserException("attr value delimiter missing!", this, null);
                 }
 
-                attributes[i + 3] = readValue(delimiter, true, ValueContext.ATTRIBUTE);
+                attributes[i + 3] = readValue(delimiter, true, throwOnResolveFailure,
+                        ValueContext.ATTRIBUTE);
 
                 if (delimiter != ' ') {
                     position++; // end quote
@@ -1116,8 +1136,8 @@
      * resolved entity to {@code out}. If the entity cannot be read or resolved,
      * {@code out} will contain the partial entity reference.
      */
-    private void readEntity(StringBuilder out, boolean isEntityToken, ValueContext valueContext)
-            throws IOException, XmlPullParserException {
+    private void readEntity(StringBuilder out, boolean isEntityToken, boolean throwOnResolveFailure,
+            ValueContext valueContext) throws IOException, XmlPullParserException {
         int start = out.length();
 
         if (buffer[position++] != '&') {
@@ -1202,7 +1222,7 @@
 
         // keep the unresolved entity "&code;" in the text for relaxed clients
         unresolved = true;
-        if (!isEntityToken) {
+        if (throwOnResolveFailure) {
             checkRelaxed("unresolved: &" + code + ";");
         }
     }
@@ -1227,7 +1247,7 @@
      * @param delimiter {@code <} for text, {@code "} and {@code '} for quoted
      *     attributes, or a space for unquoted attributes.
      */
-    private String readValue(char delimiter, boolean resolveEntities,
+    private String readValue(char delimiter, boolean resolveEntities, boolean throwOnResolveFailure,
             ValueContext valueContext) throws IOException, XmlPullParserException {
 
         /*
@@ -1318,7 +1338,7 @@
 
             } else if (c == '&') {
                 isWhitespace = false; // TODO: what if the entity resolves to whitespace?
-                readEntity(result, false, valueContext);
+                readEntity(result, false, throwOnResolveFailure, valueContext);
                 start = position;
                 continue;
 
@@ -1649,6 +1669,12 @@
         }
     }
 
+    public void close() throws IOException {
+        if (reader != null) {
+            reader.close();
+        }
+    }
+
     public boolean getFeature(String feature) {
         if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) {
             return processNsp;
@@ -1693,6 +1719,30 @@
         }
     }
 
+    /**
+     * Returns the root element's name if it was declared in the DTD. This
+     * equals the first tag's name for valid documents.
+     */
+    public String getRootElementName() {
+        return rootElementName;
+    }
+
+    /**
+     * Returns the document's system ID if it was declared. This is typically a
+     * string like {@code http://www.w3.org/TR/html4/strict.dtd}.
+     */
+    public String getSystemId() {
+        return systemId;
+    }
+
+    /**
+     * Returns the document's public ID if it was declared. This is typically a
+     * string like {@code -//W3C//DTD HTML 4.01//EN}.
+     */
+    public String getPublicId() {
+        return publicId;
+    }
+
     public int getNamespaceCount(int depth) {
         if (depth > this.depth) {
             throw new IndexOutOfBoundsException();
@@ -1709,7 +1759,6 @@
     }
 
     public String getNamespace(String prefix) {
-
         if ("xml".equals(prefix)) {
             return "http://www.w3.org/XML/1998/namespace";
         }