changeset 0:6a4a699eee1a

xml reader from Android framework.
author Mario Torre <neugens.limasoftware@gmail.com>
date Wed, 13 Apr 2011 22:04:02 +0200
parents
children c2237b6c90bf
files src/main/java/org/apache/harmony/xml/ExpatAttributes.java src/main/java/org/apache/harmony/xml/ExpatException.java src/main/java/org/apache/harmony/xml/ExpatParser.java src/main/java/org/apache/harmony/xml/ExpatPullParser.java src/main/java/org/apache/harmony/xml/ExpatReader.java
diffstat 5 files changed, 2274 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/apache/harmony/xml/ExpatAttributes.java	Wed Apr 13 22:04:02 2011 +0200
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2008 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 org.apache.harmony.xml;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Wraps native attribute array.
+ */
+abstract class ExpatAttributes implements Attributes {
+
+    /**
+     * Since we don't do validation, pretty much everything is CDATA type.
+     */
+    private static final String CDATA = "CDATA";
+
+    /**
+     * Gets the number of attributes.
+     */
+    public abstract int getLength();
+
+    /**
+     * Gets the pointer to the parser. We need this so we can get to the
+     * interned string pool.
+     */
+    abstract int getParserPointer();
+
+    /**
+     * Gets the pointer to the underlying attribute array. Can be 0 if the
+     * length is 0.
+     */
+    public abstract int getPointer();
+
+    public String getURI(int index) {
+        if (index < 0 || index >= getLength()) {
+            return null;
+        }
+        return getURI(getParserPointer(), getPointer(), index);
+    }
+
+    public String getLocalName(int index) {
+        return (index < 0 || index >= getLength())
+                ? null
+                : getLocalName(getParserPointer(), getPointer(), index);
+    }
+
+    public String getQName(int index) {
+        return (index < 0 || index >= getLength())
+                ? null
+                : getQName(getParserPointer(), getPointer(), index);
+    }
+
+    public String getType(int index) {
+        return (index < 0 || index >= getLength()) ? null : CDATA;
+    }
+
+    public String getValue(int index) {
+        return (index < 0 || index >= getLength())
+                ? null
+                : getValueByIndex(getPointer(), index);
+    }
+
+    public int getIndex(String uri, String localName) {
+        if (uri == null) {
+            throw new NullPointerException("uri");
+        }
+        if (localName == null) {
+            throw new NullPointerException("local name");
+        }
+        int pointer = getPointer();
+        if (pointer == 0) {
+            return -1;
+        }
+        return getIndex(pointer, uri, localName);
+    }
+
+    public int getIndex(String qName) {
+        if (qName == null) {
+            throw new NullPointerException("uri");
+        }
+        int pointer = getPointer();
+        if (pointer == 0) {
+            return -1;
+        }
+        return getIndexForQName(pointer, qName);
+    }
+
+    public String getType(String uri, String localName) {
+        if (uri == null) {
+            throw new NullPointerException("uri");
+        }
+        if (localName == null) {
+            throw new NullPointerException("local name");
+        }
+        return getIndex(uri, localName) == -1 ? null : CDATA;
+    }
+
+    public String getType(String qName) {
+        return getIndex(qName) == -1 ? null : CDATA;
+    }
+
+    public String getValue(String uri, String localName) {
+        if (uri == null) {
+            throw new NullPointerException("uri");
+        }
+        if (localName == null) {
+            throw new NullPointerException("local name");
+        }
+        int pointer = getPointer();
+        if (pointer == 0) {
+            return null;
+        }
+        return getValue(pointer, uri, localName);
+    }
+
+    public String getValue(String qName) {
+        if (qName == null) {
+            throw new NullPointerException("qName");
+        }
+        int pointer = getPointer();
+        if (pointer == 0) {
+            return null;
+        }
+        return getValueForQName(pointer, qName);
+    }
+
+    private static native String getURI(int pointer, int attributePointer, int index);
+    private static native String getLocalName(int pointer, int attributePointer, int index);
+    private static native String getQName(int pointer, int attributePointer, int index);
+    private static native String getValueByIndex(int attributePointer, int index);
+    private static native int getIndex(int attributePointer, String uri, String localName);
+    private static native int getIndexForQName(int attributePointer, String qName);
+    private static native String getValue(int attributePointer, String uri, String localName);
+    private static native String getValueForQName(int attributePointer, String qName);
+    protected native void freeAttributes(int pointer);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/apache/harmony/xml/ExpatException.java	Wed Apr 13 22:04:02 2011 +0200
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2008 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 org.apache.harmony.xml;
+
+/**
+ * Used internally to propogate Expat errors. We convert these exceptions into
+ * SAXParseExceptions before propogating them to the client.
+ */
+class ExpatException extends Exception {
+
+    public ExpatException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/apache/harmony/xml/ExpatParser.java	Wed Apr 13 22:04:02 2011 +0200
@@ -0,0 +1,809 @@
+/*
+ * 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 org.apache.harmony.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Adapts SAX API to the Expat native XML parser. Not intended for reuse
+ * across documents.
+ *
+ * @see org.apache.harmony.xml.ExpatPullParser
+ * @see org.apache.harmony.xml.ExpatReader
+ */
+class ExpatParser {
+
+    private static final int BUFFER_SIZE = 8096; // in bytes
+
+    /** Pointer to XML_Parser instance. */
+    private int pointer;
+
+    private boolean inStartElement = false;
+    private int attributeCount = -1;
+    private int attributePointer = 0;
+
+    private final Locator locator = new ExpatLocator();
+
+    private final ExpatReader xmlReader;
+
+    private final String publicId;
+    private final String systemId;
+
+    private final String encoding;
+
+    private final ExpatAttributes attributes = new CurrentAttributes();
+
+    private static final String OUTSIDE_START_ELEMENT
+            = "Attributes can only be used within the scope of startElement().";
+
+    /** We default to UTF-8 when the user doesn't specify an encoding. */
+    private static final String DEFAULT_ENCODING = "UTF-8";
+
+    /** Encoding used for Java chars, used to parse Readers and Strings */
+    /*package*/ static final String CHARACTER_ENCODING = "UTF-16";
+
+    /** Timeout for HTTP connections (in ms) */
+    private static final int TIMEOUT = 20 * 1000;
+
+    /**
+     * Constructs a new parser with the specified encoding.
+     */
+    /*package*/ ExpatParser(String encoding, ExpatReader xmlReader,
+            boolean processNamespaces, String publicId, String systemId) {
+        this.publicId = publicId;
+        this.systemId = systemId;
+
+        this.xmlReader = xmlReader;
+
+        /*
+         * TODO: Let Expat try to guess the encoding instead of defaulting.
+         * Unfortunately, I don't know how to tell which encoding Expat picked,
+         * so I won't know how to encode "<externalEntity>" below. The solution
+         * I think is to fix Expat to not require the "<externalEntity>"
+         * workaround.
+         */
+        this.encoding = encoding == null ? DEFAULT_ENCODING : encoding;
+        this.pointer = initialize(
+            this.encoding,
+            processNamespaces
+        );
+    }
+
+    /**
+     * Used by {@link EntityParser}.
+     */
+    private ExpatParser(String encoding, ExpatReader xmlReader, int pointer,
+            String publicId, String systemId) {
+        this.encoding = encoding;
+        this.xmlReader = xmlReader;
+        this.pointer = pointer;
+        this.systemId = systemId;
+        this.publicId = publicId;
+    }
+
+    /**
+     * Initializes native resources.
+     *
+     * @return the pointer to the native parser
+     */
+    private native int initialize(String encoding, boolean namespacesEnabled);
+
+    /**
+     * Called at the start of an element.
+     *
+     * @param uri namespace URI of element or "" if namespace processing is
+     *  disabled
+     * @param localName local name of element or "" if namespace processing is
+     *  disabled
+     * @param qName qualified name or "" if namespace processing is enabled
+     * @param attributePointer pointer to native attribute char*--we keep
+     *  a separate pointer so we can detach it from the parser instance
+     * @param attributeCount number of attributes
+     */
+    /*package*/ void startElement(String uri, String localName, String qName,
+            int attributePointer, int attributeCount) throws SAXException {
+        ContentHandler contentHandler = xmlReader.contentHandler;
+        if (contentHandler == null) {
+            return;
+        }
+
+        try {
+            inStartElement = true;
+            this.attributePointer = attributePointer;
+            this.attributeCount = attributeCount;
+
+            contentHandler.startElement(
+                    uri, localName, qName, this.attributes);
+        } finally {
+            inStartElement = false;
+            this.attributeCount = -1;
+            this.attributePointer = 0;
+        }
+    }
+
+    /*package*/ void endElement(String uri, String localName, String qName)
+            throws SAXException {
+        ContentHandler contentHandler = xmlReader.contentHandler;
+        if (contentHandler != null) {
+            contentHandler.endElement(uri, localName, qName);
+        }
+    }
+
+    /*package*/ void text(char[] text, int length) throws SAXException {
+        ContentHandler contentHandler = xmlReader.contentHandler;
+        if (contentHandler != null) {
+            contentHandler.characters(text, 0, length);
+        }
+    }
+
+    /*package*/ void comment(char[] text, int length) throws SAXException {
+        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
+        if (lexicalHandler != null) {
+            lexicalHandler.comment(text, 0, length);
+        }
+    }
+
+    /*package*/ void startCdata() throws SAXException {
+        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
+        if (lexicalHandler != null) {
+            lexicalHandler.startCDATA();
+        }
+    }
+
+    /*package*/ void endCdata() throws SAXException {
+        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
+        if (lexicalHandler != null) {
+            lexicalHandler.endCDATA();
+        }
+    }
+
+    /*package*/ void startNamespace(String prefix, String uri)
+            throws SAXException {
+        ContentHandler contentHandler = xmlReader.contentHandler;
+        if (contentHandler != null) {
+            contentHandler.startPrefixMapping(prefix, uri);
+        }
+    }
+
+    /*package*/ void endNamespace(String prefix) throws SAXException {
+        ContentHandler contentHandler = xmlReader.contentHandler;
+        if (contentHandler != null) {
+            contentHandler.endPrefixMapping(prefix);
+        }
+    }
+
+    /*package*/ void startDtd(String name, String publicId, String systemId)
+            throws SAXException {
+        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
+        if (lexicalHandler != null) {
+            lexicalHandler.startDTD(name, publicId, systemId);
+        }
+    }
+
+    /*package*/ void endDtd() throws SAXException {
+        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
+        if (lexicalHandler != null) {
+            lexicalHandler.endDTD();
+        }
+    }
+
+    /*package*/ void processingInstruction(String target, String data)
+            throws SAXException {
+        ContentHandler contentHandler = xmlReader.contentHandler;
+        if (contentHandler != null) {
+            contentHandler.processingInstruction(target, data);
+        }
+    }
+
+    /*package*/ void notationDecl(String name, String publicId, String systemId) throws SAXException {
+        DTDHandler dtdHandler = xmlReader.dtdHandler;
+        if (dtdHandler != null) {
+            dtdHandler.notationDecl(name, publicId, systemId);
+        }
+    }
+
+    /*package*/ void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException {
+        DTDHandler dtdHandler = xmlReader.dtdHandler;
+        if (dtdHandler != null) {
+            dtdHandler.unparsedEntityDecl(name, publicId, systemId, notationName);
+        }
+    }
+
+    /**
+     * Handles an external entity.
+     *
+     * @param context to be passed back to Expat when we parse the entity
+     * @param publicId the publicId of the entity
+     * @param systemId the systemId of the entity
+     */
+    /*package*/ void handleExternalEntity(String context, String publicId,
+            String systemId) throws SAXException, IOException {
+        EntityResolver entityResolver = xmlReader.entityResolver;
+        if (entityResolver == null) {
+            return;
+        }
+
+        /*
+         * The spec. is terribly under-specified here. It says that if the
+         * systemId is a URL, we should try to resolve it, but it doesn't
+         * specify how to tell whether or not the systemId is a URL let alone
+         * how to resolve it.
+         *
+         * Other implementations do various insane things. We try to keep it
+         * simple: if the systemId parses as a URI and it's relative, we try to
+         * resolve it against the parent document's systemId. If anything goes
+         * wrong, we go with the original systemId. If crazybob had designed
+         * the API, he would have left all resolving to the EntityResolver.
+         */
+        if (this.systemId != null) {
+            try {
+                URI systemUri = new URI(systemId);
+                if (!systemUri.isAbsolute() && !systemUri.isOpaque()) {
+                    // It could be relative (or it may not be a URI at all!)
+                    URI baseUri = new URI(this.systemId);
+                    systemUri = baseUri.resolve(systemUri);
+
+                    // Replace systemId w/ resolved URI
+                    systemId = systemUri.toString();
+                }
+            } catch (Exception e) {
+                Logger.getLogger(ExpatParser.class.getName()).log(Level.INFO,
+                        "Could not resolve '" + systemId + "' relative to"
+                        + " '" + this.systemId + "' at " + locator, e);
+            }
+        }
+
+        InputSource inputSource = entityResolver.resolveEntity(
+                publicId, systemId);
+        if (inputSource == null) {
+            /*
+             * The spec. actually says that we should try to treat systemId
+             * as a URL and download and parse its contents here, but an
+             * entity resolver can easily accomplish the same by returning
+             * new InputSource(systemId).
+             *
+             * Downloading external entities by default would result in several
+             * unwanted DTD downloads, not to mention pose a security risk
+             * when parsing untrusted XML -- see for example
+             * http://archive.cert.uni-stuttgart.de/bugtraq/2002/10/msg00421.html --
+             * so we just do nothing instead. This also enables the user to
+             * opt out of entity parsing when using
+             * {@link org.xml.sax.helpers.DefaultHandler}, something that
+             * wouldn't be possible otherwise.
+             */
+            return;
+        }
+
+        String encoding = pickEncoding(inputSource);
+        int pointer = createEntityParser(this.pointer, context);
+        try {
+            EntityParser entityParser = new EntityParser(encoding, xmlReader,
+                    pointer, inputSource.getPublicId(),
+                    inputSource.getSystemId());
+
+            parseExternalEntity(entityParser, inputSource);
+        } finally {
+            releaseParser(pointer);
+        }
+    }
+
+    /**
+     * Picks an encoding for an external entity. Defaults to UTF-8.
+     */
+    private String pickEncoding(InputSource inputSource) {
+        Reader reader = inputSource.getCharacterStream();
+        if (reader != null) {
+            return CHARACTER_ENCODING;
+        }
+
+        String encoding = inputSource.getEncoding();
+        return encoding == null ? DEFAULT_ENCODING : encoding;
+    }
+
+    /**
+     * Parses the the external entity provided by the input source.
+     */
+    private void parseExternalEntity(ExpatParser entityParser,
+            InputSource inputSource) throws IOException, SAXException {
+        /*
+         * Expat complains if the external entity isn't wrapped with a root
+         * element so we add one and ignore it later on during parsing.
+         */
+
+        // Try the character stream.
+        Reader reader = inputSource.getCharacterStream();
+        if (reader != null) {
+            try {
+                entityParser.append("<externalEntity>");
+                entityParser.parseFragment(reader);
+                entityParser.append("</externalEntity>");
+            } finally {
+                // TODO: Don't eat original exception when close() throws.
+                reader.close();
+            }
+            return;
+        }
+
+        // Try the byte stream.
+        InputStream in = inputSource.getByteStream();
+        if (in != null) {
+            try {
+                entityParser.append("<externalEntity>"
+                        .getBytes(entityParser.encoding));
+                entityParser.parseFragment(in);
+                entityParser.append("</externalEntity>"
+                        .getBytes(entityParser.encoding));
+            } finally {
+                // TODO: Don't eat original exception when close() throws.
+                in.close();
+            }
+            return;
+        }
+
+        // Make sure we use the user-provided systemId.
+        String systemId = inputSource.getSystemId();
+        if (systemId == null) {
+            // TODO: We could just try our systemId here.
+            throw new ParseException("No input specified.", locator);
+        }
+
+        // Try the system id.
+        in = openUrl(systemId);
+        try {
+            entityParser.append("<externalEntity>"
+                    .getBytes(entityParser.encoding));
+            entityParser.parseFragment(in);
+            entityParser.append("</externalEntity>"
+                    .getBytes(entityParser.encoding));
+        } finally {
+            in.close();
+        }
+    }
+
+    /**
+     * Creates a native entity parser.
+     *
+     * @param parentPointer pointer to parent Expat parser
+     * @param context passed to {@link #handleExternalEntity}
+     * @return pointer to native parser
+     */
+    private static native int createEntityParser(int parentPointer, String context);
+
+    /**
+     * Appends part of an XML document. This parser will parse the given XML to
+     * the extent possible and dispatch to the appropriate methods.
+     *
+     * @param xml a whole or partial snippet of XML
+     * @throws SAXException if an error occurs during parsing
+     */
+    /*package*/ void append(String xml) throws SAXException {
+        try {
+            appendString(this.pointer, xml, false);
+        } catch (ExpatException e) {
+            throw new ParseException(e.getMessage(), this.locator);
+        }
+    }
+
+    private native void appendString(int pointer, String xml, boolean isFinal)
+            throws SAXException, ExpatException;
+
+    /**
+     * Appends part of an XML document. This parser will parse the given XML to
+     * the extent possible and dispatch to the appropriate methods.
+     *
+     * @param xml a whole or partial snippet of XML
+     * @param offset into the char[]
+     * @param length of characters to use
+     * @throws SAXException if an error occurs during parsing
+     */
+    /*package*/ void append(char[] xml, int offset, int length)
+            throws SAXException {
+        try {
+            appendChars(this.pointer, xml, offset, length);
+        } catch (ExpatException e) {
+            throw new ParseException(e.getMessage(), this.locator);
+        }
+    }
+
+    private native void appendChars(int pointer, char[] xml, int offset,
+            int length) throws SAXException, ExpatException;
+
+    /**
+     * Appends part of an XML document. This parser will parse the given XML to
+     * the extent possible and dispatch to the appropriate methods.
+     *
+     * @param xml a whole or partial snippet of XML
+     * @throws SAXException if an error occurs during parsing
+     */
+    /*package*/ void append(byte[] xml) throws SAXException {
+        append(xml, 0, xml.length);
+    }
+
+    /**
+     * Appends part of an XML document. This parser will parse the given XML to
+     * the extent possible and dispatch to the appropriate methods.
+     *
+     * @param xml a whole or partial snippet of XML
+     * @param offset into the byte[]
+     * @param length of bytes to use
+     * @throws SAXException if an error occurs during parsing
+     */
+    /*package*/ void append(byte[] xml, int offset, int length)
+            throws SAXException {
+        try {
+            appendBytes(this.pointer, xml, offset, length);
+        } catch (ExpatException e) {
+            throw new ParseException(e.getMessage(), this.locator);
+        }
+    }
+
+    private native void appendBytes(int pointer, byte[] xml, int offset,
+            int length) throws SAXException, ExpatException;
+
+    /**
+     * Parses an XML document from the given input stream.
+     */
+    /*package*/ void parseDocument(InputStream in) throws IOException,
+            SAXException {
+        startDocument();
+        parseFragment(in);
+        finish();
+        endDocument();
+    }
+
+    /**
+     * Parses an XML Document from the given reader.
+     */
+    /*package*/ void parseDocument(Reader in) throws IOException, SAXException {
+        startDocument();
+        parseFragment(in);
+        finish();
+        endDocument();
+    }
+
+    /**
+     * Parses XML from the given Reader.
+     */
+    private void parseFragment(Reader in) throws IOException, SAXException {
+        char[] buffer = new char[BUFFER_SIZE / 2];
+        int length;
+        while ((length = in.read(buffer)) != -1) {
+            try {
+                appendChars(this.pointer, buffer, 0, length);
+            } catch (ExpatException e) {
+                throw new ParseException(e.getMessage(), locator);
+            }
+        }
+    }
+
+    /**
+     * Parses XML from the given input stream.
+     */
+    private void parseFragment(InputStream in)
+            throws IOException, SAXException {
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int length;
+        while ((length = in.read(buffer)) != -1) {
+            try {
+                appendBytes(this.pointer, buffer, 0, length);
+            } catch (ExpatException e) {
+                throw new ParseException(e.getMessage(), this.locator);
+            }
+        }
+    }
+
+    private void startDocument() throws SAXException {
+        ContentHandler contentHandler = xmlReader.contentHandler;
+        if (contentHandler != null) {
+            contentHandler.setDocumentLocator(this.locator);
+            contentHandler.startDocument();
+        }
+    }
+
+    private void endDocument() throws SAXException {
+        ContentHandler contentHandler;
+        contentHandler = xmlReader.contentHandler;
+        if (contentHandler != null) {
+            contentHandler.endDocument();
+        }
+    }
+
+    /**
+     * Indicate that we're finished parsing.
+     *
+     * @throws SAXException if the xml is incomplete
+     */
+    /*package*/ void finish() throws SAXException {
+        try {
+            appendString(this.pointer, "", true);
+        } catch (ExpatException e) {
+            throw new ParseException(e.getMessage(), this.locator);
+        }
+    }
+
+    @Override protected synchronized void finalize() throws Throwable {
+        try {
+            if (this.pointer != 0) {
+                release(this.pointer);
+                this.pointer = 0;
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Releases all native objects.
+     */
+    private native void release(int pointer);
+
+    /**
+     * Releases native parser only.
+     */
+    private static native void releaseParser(int pointer);
+
+    /**
+     * Initialize static resources.
+     */
+    private static native void staticInitialize(String emptyString);
+
+    static {
+        staticInitialize("");
+    }
+
+    /**
+     * Gets the current line number within the XML file.
+     */
+    private int line() {
+        return line(this.pointer);
+    }
+
+    private static native int line(int pointer);
+
+    /**
+     * Gets the current column number within the XML file.
+     */
+    private int column() {
+        return column(this.pointer);
+    }
+
+    private static native int column(int pointer);
+
+    /**
+     * Clones the current attributes so they can be used outside of
+     * startElement().
+     */
+    /*package*/ Attributes cloneAttributes() {
+        if (!inStartElement) {
+            throw new IllegalStateException(OUTSIDE_START_ELEMENT);
+        }
+
+        if (attributeCount == 0) {
+            return ClonedAttributes.EMPTY;
+        }
+
+        int clonePointer
+                = cloneAttributes(this.attributePointer, this.attributeCount);
+        return new ClonedAttributes(pointer, clonePointer, attributeCount);
+    }
+
+    private static native int cloneAttributes(int pointer, int attributeCount);
+
+    /**
+     * Used for cloned attributes.
+     */
+    private static class ClonedAttributes extends ExpatAttributes {
+
+        private static final Attributes EMPTY = new ClonedAttributes(0, 0, 0);
+
+        private final int parserPointer;
+        private int pointer;
+        private final int length;
+
+        /**
+         * Constructs a Java wrapper for native attributes.
+         *
+         * @param parserPointer pointer to the parse, can be 0 if length is 0.
+         * @param pointer pointer to the attributes array, can be 0 if the
+         *  length is 0.
+         * @param length number of attributes
+         */
+        private ClonedAttributes(int parserPointer, int pointer, int length) {
+            this.parserPointer = parserPointer;
+            this.pointer = pointer;
+            this.length = length;
+        }
+
+        @Override
+        public int getParserPointer() {
+            return this.parserPointer;
+        }
+
+        @Override
+        public int getPointer() {
+            return pointer;
+        }
+
+        @Override
+        public int getLength() {
+            return length;
+        }
+
+        @Override protected synchronized void finalize() throws Throwable {
+            try {
+                if (pointer != 0) {
+                    freeAttributes(pointer);
+                    pointer = 0;
+                }
+            } finally {
+                super.finalize();
+            }
+        }
+    }
+
+    private class ExpatLocator implements Locator {
+
+        public String getPublicId() {
+            return publicId;
+        }
+
+        public String getSystemId() {
+            return systemId;
+        }
+
+        public int getLineNumber() {
+            return line();
+        }
+
+        public int getColumnNumber() {
+            return column();
+        }
+
+        @Override
+        public String toString() {
+            return "Locator[publicId: " + publicId + ", systemId: " + systemId
+                + ", line: " + getLineNumber()
+                + ", column: " + getColumnNumber() + "]";
+        }
+    }
+
+    /**
+     * Attributes that are only valid during startElement().
+     */
+    private class CurrentAttributes extends ExpatAttributes {
+
+        @Override
+        public int getParserPointer() {
+            return pointer;
+        }
+
+        @Override
+        public int getPointer() {
+            if (!inStartElement) {
+                throw new IllegalStateException(OUTSIDE_START_ELEMENT);
+            }
+            return attributePointer;
+        }
+
+        @Override
+        public int getLength() {
+            if (!inStartElement) {
+                throw new IllegalStateException(OUTSIDE_START_ELEMENT);
+            }
+            return attributeCount;
+        }
+    }
+
+    /**
+     * Includes line and column in the message.
+     */
+    private static class ParseException extends SAXParseException {
+
+        private ParseException(String message, Locator locator) {
+            super(makeMessage(message, locator), locator);
+        }
+
+        private static String makeMessage(String message, Locator locator) {
+            return makeMessage(message, locator.getLineNumber(),
+                    locator.getColumnNumber());
+        }
+
+        private static String makeMessage(
+                String message, int line, int column) {
+            return "At line " + line + ", column "
+                    + column + ": " + message;
+        }
+    }
+
+    /**
+     * Opens an InputStream for the given URL.
+     */
+    /*package*/ static InputStream openUrl(String url) throws IOException {
+        try {
+            URLConnection urlConnection = new URL(url).openConnection();
+            urlConnection.setConnectTimeout(TIMEOUT);
+            urlConnection.setReadTimeout(TIMEOUT);
+            urlConnection.setDoInput(true);
+            urlConnection.setDoOutput(false);
+            return urlConnection.getInputStream();
+        } catch (Exception e) {
+            IOException ioe = new IOException("Couldn't open " + url);
+            ioe.initCause(e);
+            throw ioe;
+        }
+    }
+
+    /**
+     * Parses an external entity.
+     */
+    private static class EntityParser extends ExpatParser {
+
+        private int depth = 0;
+
+        private EntityParser(String encoding, ExpatReader xmlReader,
+                int pointer, String publicId, String systemId) {
+            super(encoding, xmlReader, pointer, publicId, systemId);
+        }
+
+        @Override
+        void startElement(String uri, String localName, String qName,
+                int attributePointer, int attributeCount) throws SAXException {
+            /*
+             * Skip topmost element generated by our workaround in
+             * {@link #handleExternalEntity}.
+             */
+            if (depth++ > 0) {
+                super.startElement(uri, localName, qName, attributePointer,
+                        attributeCount);
+            }
+        }
+
+        @Override
+        void endElement(String uri, String localName, String qName)
+                throws SAXException {
+            if (--depth > 0) {
+                super.endElement(uri, localName, qName);
+            }
+        }
+
+        @Override
+        @SuppressWarnings("FinalizeDoesntCallSuperFinalize")
+        protected synchronized void finalize() throws Throwable {
+            /*
+             * Don't release our native resources. We do so explicitly in
+             * {@link #handleExternalEntity} and we don't want to release the
+             * parsing context--our parent is using it.
+             */
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/apache/harmony/xml/ExpatPullParser.java	Wed Apr 13 22:04:02 2011 +0200
@@ -0,0 +1,963 @@
+/*
+ * 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 org.apache.harmony.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Fast, partial XmlPullParser implementation based upon Expat. Does not
+ * support validation or {@code DOCTYPE} processing.
+ */
+public class ExpatPullParser implements XmlPullParser {
+    /**
+     * This feature is identified by http://xmlpull.org/v1/doc/features.html#relaxed
+     * If this feature is supported that means that XmlPull parser will be
+     * lenient when checking XML well formedness.
+     * NOTE: use it only if XML input is not well-formed and in general usage
+     * if this feature is discouraged
+     * NOTE: as there is no definition of what is relaxed XML parsing
+     * therefore what parser will do completely depends on implementation used
+     */
+    public static final String FEATURE_RELAXED =
+            "http://xmlpull.org/v1/doc/features.html#relaxed";
+
+    private static final int BUFFER_SIZE = 8096;
+
+    private static final String NOT_A_START_TAG = "This is not a start tag.";
+
+    private Document document;
+    private boolean processNamespaces = false;
+    private boolean relaxed = false;
+
+    public void setFeature(String name, boolean state)
+            throws XmlPullParserException {
+        if (name == null) {
+            // Required by API.
+            throw new IllegalArgumentException("Null feature name");
+        }
+
+        if (name.equals(FEATURE_PROCESS_NAMESPACES)) {
+            processNamespaces = state;
+            return;
+        }
+
+        if (name.equals(FEATURE_RELAXED)) {
+            relaxed = true;
+            return;
+        }
+
+        // You're free to turn these features off because we don't support them.
+        if (!state && (name.equals(FEATURE_REPORT_NAMESPACE_ATTRIBUTES)
+                || name.equals(FEATURE_PROCESS_DOCDECL)
+                || name.equals(FEATURE_VALIDATION))) {
+            return;
+        }
+
+        throw new XmlPullParserException("Unsupported feature: " + name);
+    }
+
+    public boolean getFeature(String name) {
+        if (name == null) {
+            // Required by API.
+            throw new IllegalArgumentException("Null feature name");
+        }
+
+        // We always support namespaces, but no other features.
+        return name.equals(FEATURE_PROCESS_NAMESPACES) && processNamespaces;
+    }
+
+    /**
+     * Returns true if this parser processes namespaces.
+     *
+     * @see #setNamespaceProcessingEnabled(boolean)
+     */
+    public boolean isNamespaceProcessingEnabled() {
+        return processNamespaces;
+    }
+
+    /**
+     * Enables or disables namespace processing. Set to false by default.
+     *
+     * @see #isNamespaceProcessingEnabled()
+     */
+    public void setNamespaceProcessingEnabled(boolean processNamespaces) {
+        this.processNamespaces = processNamespaces;
+    }
+
+    public void setProperty(String name, Object value)
+            throws XmlPullParserException {
+        if (name == null) {
+            // Required by API.
+            throw new IllegalArgumentException("Null feature name");
+        }
+
+        // We don't support any properties.
+        throw new XmlPullParserException("Properties aren't supported.");
+    }
+
+    public Object getProperty(String name) {
+        return null;
+    }
+
+    public void setInput(Reader in) throws XmlPullParserException {
+        this.document = new CharDocument(in, processNamespaces);
+    }
+
+    public void setInput(InputStream in, String encodingName)
+            throws XmlPullParserException {
+        this.document = new ByteDocument(in, encodingName, processNamespaces);
+    }
+
+    public String getInputEncoding() {
+        return this.document.getEncoding();
+    }
+
+    /**
+     * Not supported.
+     *
+     * @throws UnsupportedOperationException always
+     */
+    public void defineEntityReplacementText(String entityName,
+            String replacementText) throws XmlPullParserException {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getNamespaceCount(int depth) throws XmlPullParserException {
+        return document.currentEvent.namespaceStack.countAt(depth);
+    }
+
+    public String getNamespacePrefix(int pos) throws XmlPullParserException {
+        String prefix = document.currentEvent.namespaceStack.prefixAt(pos);
+        @SuppressWarnings("StringEquality")
+        boolean hasPrefix = prefix != "";
+        return hasPrefix ? prefix : null;
+    }
+
+    public String getNamespaceUri(int pos) throws XmlPullParserException {
+        return document.currentEvent.namespaceStack.uriAt(pos);
+    }
+
+    public String getNamespace(String prefix) {
+        // In XmlPullParser API, null == default namespace.
+        if (prefix == null) {
+            // Internally, we use empty string instead of null.
+            prefix = "";
+        }
+
+        return document.currentEvent.namespaceStack.uriFor(prefix);
+    }
+
+    public int getDepth() {
+        return this.document.getDepth();
+    }
+
+    public String getPositionDescription() {
+        return "line " + getLineNumber() + ", column " + getColumnNumber();
+    }
+
+    /**
+     * Not supported.
+     *
+     * @return {@literal -1} always
+     */
+    public int getLineNumber() {
+        // We would have to record the line number in each event.
+        return -1;
+    }
+
+    /**
+     * Not supported.
+     *
+     * @return {@literal -1} always
+     */
+    public int getColumnNumber() {
+        // We would have to record the column number in each event.
+        return -1;
+    }
+
+    public boolean isWhitespace() throws XmlPullParserException {
+        if (getEventType() != TEXT) {
+            throw new XmlPullParserException("Not on text.");
+        }
+
+        String text = getText();
+
+        if (text.length() == 0) {
+            return true;
+        }
+
+        int length = text.length();
+        for (int i = 0; i < length; i++) {
+            if (!Character.isWhitespace(text.charAt(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public String getText() {
+        final StringBuilder builder = this.document.currentEvent.getText();
+        return builder == null ? null : builder.toString();
+    }
+
+    public char[] getTextCharacters(int[] holderForStartAndLength) {
+        final StringBuilder builder = this.document.currentEvent.getText();
+
+        final int length = builder.length();
+        char[] characters = new char[length];
+        builder.getChars(0, length, characters, 0);
+
+        holderForStartAndLength[0] = 0;
+        holderForStartAndLength[1] = length;
+
+        return characters;
+    }
+
+    public String getNamespace() {
+        return this.document.currentEvent.getNamespace();
+    }
+
+    public String getName() {
+        return this.document.currentEvent.getName();
+    }
+
+    /**
+     * Not supported.
+     *
+     * @throws UnsupportedOperationException always
+     */
+    public String getPrefix() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isEmptyElementTag() throws XmlPullParserException {
+        return this.document.isCurrentElementEmpty();
+    }
+
+    public int getAttributeCount() {
+        return this.document.currentEvent.getAttributeCount();
+    }
+
+    public String getAttributeNamespace(int index) {
+        return this.document.currentEvent.getAttributeNamespace(index);
+    }
+
+    public String getAttributeName(int index) {
+        return this.document.currentEvent.getAttributeName(index);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @throws UnsupportedOperationException always
+     */
+    public String getAttributePrefix(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getAttributeType(int index) {
+        return "CDATA";
+    }
+
+    public boolean isAttributeDefault(int index) {
+        return false;
+    }
+
+    public String getAttributeValue(int index) {
+        return this.document.currentEvent.getAttributeValue(index);
+    }
+
+    public String getAttributeValue(String namespace, String name) {
+        return this.document.currentEvent.getAttributeValue(namespace, name);
+    }
+
+    public int getEventType() throws XmlPullParserException {
+        return this.document.currentEvent.getType();
+    }
+
+    public int next() throws XmlPullParserException, IOException {
+        return this.document.dequeue();
+    }
+
+    /**
+     * Not supported.
+     *
+     * @throws UnsupportedOperationException always
+     */
+    public int nextToken() throws XmlPullParserException, IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void require(int type, String namespace, String name)
+            throws XmlPullParserException, IOException {
+        if (type != getEventType()
+                || (namespace != null && !namespace.equals(getNamespace()))
+                || (name != null && !name.equals(getName()))) {
+            throw new XmlPullParserException("expected "
+                    + TYPES[type] + getPositionDescription());
+        }
+    }
+
+    public String nextText() throws XmlPullParserException, IOException {
+        if (this.document.currentEvent.getType() != START_TAG)
+            throw new XmlPullParserException("Not on start tag.");
+
+        int next = this.document.dequeue();
+        switch (next) {
+            case TEXT: return getText();
+            case END_TAG: return "";
+            default: throw new XmlPullParserException(
+                "Unexpected event type: " + TYPES[next]);
+        }
+    }
+
+    public int nextTag() throws XmlPullParserException, IOException {
+        int eventType = next();
+        if (eventType == TEXT && isWhitespace()) {
+            eventType = next();
+        }
+        if (eventType != START_TAG && eventType != END_TAG) {
+            throw new XmlPullParserException(
+                "Expected start or end tag", this, null);
+        }
+        return eventType;
+    }
+
+    /**
+     * Immutable namespace stack. Pushing a new namespace on to the stack
+     * only results in one object allocation. Most operations are O(N) where
+     * N is the stack size. Accessing recently pushed namespaces, like those
+     * for the current element, is significantly faster.
+     */
+    static class NamespaceStack {
+
+        /** An empty stack. */
+        static final NamespaceStack EMPTY = new NamespaceStack();
+
+        private final NamespaceStack parent;
+        private final String prefix;
+        private final String uri;
+        private final int index;
+        private final int depth;
+
+        /**
+         * Constructs an actual namespace stack node. Internally, the nodes
+         * and the stack are one in the same making for a very efficient
+         * implementation. The user just sees an immutable stack and the
+         * builder.
+         */
+        private NamespaceStack(NamespaceStack parent, String prefix,
+                String uri, int depth) {
+            this.parent = parent;
+            this.prefix = prefix;
+            this.uri = uri;
+            this.index = parent.index + 1;
+            this.depth = depth;
+        }
+
+        /**
+         * Constructs a dummy node which only serves to point to the bottom
+         * of the stack. Using an actual node instead of null simplifies the
+         * code.
+         */
+        private NamespaceStack() {
+            this.parent = null;
+            this.prefix = null;
+            this.uri = null;
+
+            // This node has an index of -1 since the actual first node in the
+            // stack has index 0.
+            this.index = -1;
+
+            // The actual first node will have a depth of 1.
+            this.depth = 0;
+        }
+
+        String uriFor(String prefix) {
+            for (NamespaceStack node = this; node.index >= 0;
+                    node = node.parent) {
+                if (node.prefix.equals(prefix)) {
+                    return node.uri;
+                }
+            }
+
+            // Not found.
+            return null;
+        }
+
+        /**
+         * Gets the prefix at the given index in the stack.
+         */
+        String prefixAt(int index) {
+            return nodeAt(index).prefix;
+        }
+
+        /**
+         * Gets the URI at the given index in the stack.
+         */
+        String uriAt(int index) {
+            return nodeAt(index).uri;
+        }
+
+        private NamespaceStack nodeAt(int index) {
+            if (index > this.index) {
+                throw new IndexOutOfBoundsException("Index > size.");
+            }
+            if (index < 0) {
+                throw new IndexOutOfBoundsException("Index < 0.");
+            }
+
+            NamespaceStack node = this;
+            while (index != node.index) {
+                node = node.parent;
+            }
+            return node;
+        }
+
+        /**
+         * Gets the size of the stack at the given element depth.
+         */
+        int countAt(int depth) {
+            if (depth > this.depth) {
+                throw new IndexOutOfBoundsException("Depth > maximum.");
+            }
+            if (depth < 0) {
+                throw new IndexOutOfBoundsException("Depth < 0.");
+            }
+
+            NamespaceStack node = this;
+            while (depth < node.depth) {
+                node = node.parent;
+            }
+            return node.index + 1;
+        }
+
+        /** Builds a NamespaceStack. */
+        static class Builder {
+
+            NamespaceStack top = EMPTY;
+
+            /**
+             * Pushes a namespace onto the stack.
+             *
+             * @param depth of the element upon which the namespace was
+             *  declared
+             */
+            void push(String prefix, String uri, int depth) {
+                top = new NamespaceStack(top, prefix, uri, depth);
+            }
+
+            /**
+             * Pops all namespaces from the given element depth.
+             */
+            void pop(int depth) {
+                // Remove all nodes at the specified depth.
+                while (top != null && top.depth == depth) {
+                    top = top.parent;
+                }
+            }
+
+            /** Returns the current stack. */
+            NamespaceStack build() {
+                return top;
+            }
+        }
+    }
+
+    /**
+     * Base class for events. Implements event chaining and defines event API
+     * along with common implementations which can be overridden.
+     */
+    static abstract class Event {
+
+        /** Element depth at the time of this event. */
+        final int depth;
+
+        /** The namespace stack at the time of this event. */
+        final NamespaceStack namespaceStack;
+
+        /** Next event in the queue. */
+        Event next = null;
+
+        Event(int depth, NamespaceStack namespaceStack) {
+            this.depth = depth;
+            this.namespaceStack = namespaceStack;
+        }
+
+        void setNext(Event next) {
+            this.next = next;
+        }
+
+        Event getNext() {
+            return next;
+        }
+
+        StringBuilder getText() {
+            return null;
+        }
+
+        String getNamespace() {
+            return null;
+        }
+
+        String getName() {
+            return null;
+        }
+
+        int getAttributeCount() {
+            return -1;
+        }
+
+        String getAttributeNamespace(int index) {
+            throw new IndexOutOfBoundsException(NOT_A_START_TAG);
+        }
+
+        String getAttributeName(int index) {
+            throw new IndexOutOfBoundsException(NOT_A_START_TAG);
+        }
+
+        String getAttributeValue(int index) {
+            throw new IndexOutOfBoundsException(NOT_A_START_TAG);
+        }
+
+        abstract int getType();
+
+        String getAttributeValue(String namespace, String name) {
+            throw new IndexOutOfBoundsException(NOT_A_START_TAG);
+        }
+
+        public int getDepth() {
+            return this.depth;
+        }
+    }
+
+    static class StartDocumentEvent extends Event {
+
+        public StartDocumentEvent() {
+            super(0, NamespaceStack.EMPTY);
+        }
+
+        @Override
+        int getType() {
+            return START_DOCUMENT;
+        }
+    }
+
+    static class StartTagEvent extends Event {
+
+        final String name;
+        final String namespace;
+        final Attributes attributes;
+        final boolean processNamespaces;
+
+        StartTagEvent(String namespace,
+                String name,
+                ExpatParser expatParser,
+                int depth,
+                NamespaceStack namespaceStack,
+                boolean processNamespaces) {
+            super(depth, namespaceStack);
+            this.namespace = namespace;
+            this.name = name;
+            this.attributes = expatParser.cloneAttributes();
+            this.processNamespaces = processNamespaces;
+        }
+
+        @Override
+        String getNamespace() {
+            return namespace;
+        }
+
+        @Override
+        String getName() {
+            return name;
+        }
+
+        @Override
+        int getAttributeCount() {
+            return attributes.getLength();
+        }
+
+        @Override
+        String getAttributeNamespace(int index) {
+            return attributes.getURI(index);
+        }
+
+        @Override
+        String getAttributeName(int index) {
+            return processNamespaces ? attributes.getLocalName(index)
+                    : attributes.getQName(index);
+        }
+
+        @Override
+        String getAttributeValue(int index) {
+            return attributes.getValue(index);
+        }
+
+        @Override
+        String getAttributeValue(String namespace, String name) {
+            if (namespace == null) {
+                namespace = "";
+            }
+
+            return attributes.getValue(namespace, name);
+        }
+
+        @Override
+        int getType() {
+            return START_TAG;
+        }
+    }
+
+    static class EndTagEvent extends Event {
+
+        final String namespace;
+        final String localName;
+
+        EndTagEvent(String namespace, String localName, int depth,
+                NamespaceStack namespaceStack) {
+            super(depth, namespaceStack);
+            this.namespace = namespace;
+            this.localName = localName;
+        }
+
+        @Override
+        String getName() {
+            return this.localName;
+        }
+
+        @Override
+        String getNamespace() {
+            return this.namespace;
+        }
+
+        @Override
+        int getType() {
+            return END_TAG;
+        }
+    }
+
+    static class TextEvent extends Event {
+
+        final StringBuilder builder;
+
+        public TextEvent(int initialCapacity, int depth,
+                NamespaceStack namespaceStack) {
+            super(depth, namespaceStack);
+            this.builder = new StringBuilder(initialCapacity);
+        }
+
+        @Override
+        int getType() {
+            return TEXT;
+        }
+
+        @Override
+        StringBuilder getText() {
+            return this.builder;
+        }
+
+        void append(char[] text, int start, int length) {
+            builder.append(text, start, length);
+        }
+    }
+
+    static class EndDocumentEvent extends Event {
+
+        EndDocumentEvent() {
+            super(0, NamespaceStack.EMPTY);
+        }
+
+        @Override
+        Event getNext() {
+            throw new IllegalStateException("End of document.");
+        }
+
+        @Override
+        void setNext(Event next) {
+            throw new IllegalStateException("End of document.");
+        }
+
+        @Override
+        int getType() {
+            return END_DOCUMENT;
+        }
+    }
+
+    /**
+     * Encapsulates the parsing context of the current document.
+     */
+    abstract class Document {
+
+        final String encoding;
+        final ExpatParser parser;
+        final boolean processNamespaces;
+
+        TextEvent textEvent = null;
+        boolean finished = false;
+
+        Document(String encoding, boolean processNamespaces) {
+            this.encoding = encoding;
+            this.processNamespaces = processNamespaces;
+
+            ExpatReader xmlReader = new ExpatReader();
+            xmlReader.setContentHandler(new SaxHandler());
+
+            this.parser = new ExpatParser(
+                    encoding, xmlReader, processNamespaces, null, null);
+        }
+
+        /** Namespace stack builder. */
+        NamespaceStack.Builder namespaceStackBuilder
+                = new NamespaceStack.Builder();
+
+        Event currentEvent = new StartDocumentEvent();
+        Event last = currentEvent;
+
+        /**
+         * Sends some more XML to the parser.
+         */
+        void pump() throws IOException, XmlPullParserException {
+            if (this.finished) {
+                return;
+            }
+
+            int length = buffer();
+
+            // End of document.
+            if (length == -1) {
+                this.finished = true;
+                if (!relaxed) {
+                    try {
+                        parser.finish();
+                    } catch (SAXException e) {
+                        throw new XmlPullParserException(
+                            "Premature end of document.", ExpatPullParser.this, e);
+                    }
+                }
+                add(new EndDocumentEvent());
+                return;
+            }
+
+            if (length == 0) {
+                return;
+            }
+
+            flush(parser, length);
+        }
+
+        /**
+         * Reads data into the buffer.
+         *
+         * @return the length of data buffered or {@code -1} if we've reached
+         *  the end of the data.
+         */
+        abstract int buffer() throws IOException;
+
+        /**
+         * Sends buffered data to the parser.
+         *
+         * @param parser the parser to flush to
+         * @param length of data buffered
+         */
+        abstract void flush(ExpatParser parser, int length)
+                throws XmlPullParserException;
+
+        /**
+         * Adds an event.
+         */
+        void add(Event event) {
+            // Flush pre-exising text event if necessary.
+            if (textEvent != null) {
+                last.setNext(textEvent);
+                last = textEvent;
+                textEvent = null;
+            }
+
+            last.setNext(event);
+            last = event;
+        }
+
+        /**
+         * Moves to the next event in the queue.
+         *
+         * @return type of next event
+         */
+        int dequeue() throws XmlPullParserException, IOException {
+            Event next;
+
+            while ((next = currentEvent.getNext()) == null) {
+                pump();
+            }
+
+            currentEvent.next = null;
+            currentEvent = next;
+
+            return currentEvent.getType();
+        }
+
+        String getEncoding() {
+            return this.encoding;
+        }
+
+        int getDepth() {
+            return currentEvent.getDepth();
+        }
+
+        /**
+         * Returns true if we're on a start element and the next event is
+         * its corresponding end element.
+         *
+         * @throws XmlPullParserException if we aren't on a start element
+         */
+        boolean isCurrentElementEmpty() throws XmlPullParserException {
+            if (currentEvent.getType() != START_TAG) {
+                throw new XmlPullParserException(NOT_A_START_TAG);
+            }
+
+            Event next;
+
+            try {
+                while ((next = currentEvent.getNext()) == null) {
+                    pump();
+                }
+            } catch (IOException ex) {
+                throw new XmlPullParserException(ex.toString());
+            }
+
+            return next.getType() == END_TAG;
+        }
+
+        private class SaxHandler implements ContentHandler {
+
+            int depth = 0;
+
+            public void startPrefixMapping(String prefix, String uri)
+                    throws SAXException {
+                // Depth + 1--we aren't actually in the element yet.
+                namespaceStackBuilder.push(prefix, uri, depth + 1);
+            }
+
+            public void startElement(String uri, String localName, String qName,
+                    Attributes attributes) {
+                String name = processNamespaces ? localName : qName;
+
+                add(new StartTagEvent(uri, name, parser, ++this.depth,
+                        namespaceStackBuilder.build(), processNamespaces));
+            }
+
+            public void endElement(String uri, String localName, String qName) {
+                String name = processNamespaces ? localName : qName;
+
+                int depth = this.depth--;
+                add(new EndTagEvent(uri, name, depth,
+                        namespaceStackBuilder.build()));
+                namespaceStackBuilder.pop(depth);
+            }
+
+            public void characters(char[] ch, int start, int length) {
+                // Ignore empty strings.
+                if (length == 0) {
+                    return;
+                }
+
+                // Start a new text event if necessary.
+                if (textEvent == null) {
+                    textEvent = new TextEvent(length, this.depth,
+                            namespaceStackBuilder.build());
+                }
+
+                // Append to an existing text event.
+                textEvent.append(ch, start, length);
+            }
+
+            public void setDocumentLocator(Locator locator) {}
+            public void startDocument() throws SAXException {}
+            public void endDocument() throws SAXException {}
+            public void endPrefixMapping(String prefix) throws SAXException {}
+            public void ignorableWhitespace(char[] ch, int start, int length)
+                    throws SAXException {}
+            public void processingInstruction(String target, String data)
+                    throws SAXException {}
+            public void skippedEntity(String name) throws SAXException {}
+        }
+    }
+
+    class CharDocument extends Document {
+
+        final char[] buffer = new char[BUFFER_SIZE / 2];
+        final Reader in;
+
+        CharDocument(Reader in, boolean processNamespaces) {
+            super("UTF-16", processNamespaces);
+            this.in = in;
+        }
+
+        @Override
+        int buffer() throws IOException {
+            return in.read(buffer);
+        }
+
+        @Override
+        void flush(ExpatParser parser, int length)
+                throws XmlPullParserException {
+            try {
+                parser.append(buffer, 0, length);
+            } catch (SAXException e) {
+                throw new XmlPullParserException(
+                        "Error parsing document.", ExpatPullParser.this, e);
+            }
+        }
+    }
+
+    class ByteDocument extends Document {
+
+        final byte[] buffer = new byte[BUFFER_SIZE];
+        final InputStream in;
+
+        ByteDocument(InputStream in, String encoding,
+                boolean processNamespaces) {
+            super(encoding, processNamespaces);
+            this.in = in;
+        }
+
+        @Override
+        int buffer() throws IOException {
+            return in.read(buffer);
+        }
+
+        @Override
+        void flush(ExpatParser parser, int length)
+                throws XmlPullParserException {
+            try {
+                parser.append(buffer, 0, length);
+            } catch (SAXException e) {
+                throw new XmlPullParserException(
+                        "Error parsing document.", ExpatPullParser.this, e);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/apache/harmony/xml/ExpatReader.java	Wed Apr 13 22:04:02 2011 +0200
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2008 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 org.apache.harmony.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * SAX wrapper around Expat. Interns strings. Does not support validation.
+ * Does not support {@link DTDHandler}.
+ */
+public class ExpatReader implements XMLReader {
+    /*
+     * ExpatParser accesses these fields directly during parsing. The user
+     * should be able to safely change them during parsing.
+     */
+    /*package*/ ContentHandler contentHandler;
+    /*package*/ DTDHandler dtdHandler;
+    /*package*/ EntityResolver entityResolver;
+    /*package*/ ErrorHandler errorHandler;
+    /*package*/ LexicalHandler lexicalHandler;
+
+    private boolean processNamespaces = true;
+    private boolean processNamespacePrefixes = false;
+
+    private static final String LEXICAL_HANDLER_PROPERTY
+            = "http://xml.org/sax/properties/lexical-handler";
+
+    private static class Feature {
+
+        private static final String BASE_URI = "http://xml.org/sax/features/";
+
+        private static final String VALIDATION = BASE_URI + "validation";
+        private static final String NAMESPACES = BASE_URI + "namespaces";
+        private static final String NAMESPACE_PREFIXES
+                = BASE_URI + "namespace-prefixes";
+        private static final String STRING_INTERNING
+                = BASE_URI + "string-interning";
+    }
+
+    public boolean getFeature(String name)
+            throws SAXNotRecognizedException, SAXNotSupportedException {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+
+        if (name.equals(Feature.VALIDATION)) {
+            return false;
+        }
+
+        if (name.equals(Feature.NAMESPACES)) {
+            return processNamespaces;
+        }
+
+        if (name.equals(Feature.NAMESPACE_PREFIXES)) {
+            return processNamespacePrefixes;
+        }
+
+        if (name.equals(Feature.STRING_INTERNING)) {
+            return true;
+        }
+
+        throw new SAXNotRecognizedException(name);
+    }
+
+    public void setFeature(String name, boolean value)
+            throws SAXNotRecognizedException, SAXNotSupportedException {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+
+        if (name.equals(Feature.VALIDATION)) {
+            if (value) {
+                throw new SAXNotSupportedException("Cannot enable " + name);
+            } else {
+                // Default.
+                return;
+            }
+        }
+
+        if (name.equals(Feature.NAMESPACES)) {
+            processNamespaces = value;
+            return;
+        }
+
+        if (name.equals(Feature.NAMESPACE_PREFIXES)) {
+            processNamespacePrefixes = value;
+            return;
+        }
+
+        if (name.equals(Feature.STRING_INTERNING)) {
+            if (value) {
+                // Default.
+                return;
+            } else {
+                throw new SAXNotSupportedException("Cannot disable " + name);
+            }
+        }
+
+        throw new SAXNotRecognizedException(name);
+    }
+
+    public Object getProperty(String name)
+            throws SAXNotRecognizedException, SAXNotSupportedException {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+
+        if (name.equals(LEXICAL_HANDLER_PROPERTY)) {
+            return lexicalHandler;
+        }
+
+        throw new SAXNotRecognizedException(name);
+    }
+
+    public void setProperty(String name, Object value)
+            throws SAXNotRecognizedException, SAXNotSupportedException {
+        if (name == null) {
+            throw new NullPointerException("name");
+        }
+
+        if (name.equals(LEXICAL_HANDLER_PROPERTY)) {
+            // The object must implement LexicalHandler
+            if (value instanceof LexicalHandler || value == null) {
+                this.lexicalHandler = (LexicalHandler) value;
+                return;
+            }
+            throw new SAXNotSupportedException("value doesn't implement " +
+                    "org.xml.sax.ext.LexicalHandler");
+        }
+
+        throw new SAXNotRecognizedException(name);
+    }
+
+    public void setEntityResolver(EntityResolver resolver) {
+        this.entityResolver = resolver;
+    }
+
+    public EntityResolver getEntityResolver() {
+        return entityResolver;
+    }
+
+    public void setDTDHandler(DTDHandler dtdHandler) {
+        this.dtdHandler = dtdHandler;
+    }
+
+    public DTDHandler getDTDHandler() {
+        return dtdHandler;
+    }
+
+    public void setContentHandler(ContentHandler handler) {
+        this.contentHandler = handler;
+    }
+
+    public ContentHandler getContentHandler() {
+        return this.contentHandler;
+    }
+
+    public void setErrorHandler(ErrorHandler handler) {
+        this.errorHandler = handler;
+    }
+
+    public ErrorHandler getErrorHandler() {
+        return errorHandler;
+    }
+
+    /**
+     * Returns the current lexical handler.
+     *
+     * @return the current lexical handler, or null if none has been registered
+     * @see #setLexicalHandler
+     */
+    public LexicalHandler getLexicalHandler() {
+        return lexicalHandler;
+    }
+
+    /**
+     * Registers a lexical event handler. Supports neither
+     * {@link LexicalHandler#startEntity(String)} nor
+     * {@link LexicalHandler#endEntity(String)}.
+     *
+     * <p>If the application does not register a lexical handler, all
+     * lexical events reported by the SAX parser will be silently
+     * ignored.</p>
+     *
+     * <p>Applications may register a new or different handler in the
+     * middle of a parse, and the SAX parser must begin using the new
+     * handler immediately.</p>
+     *
+     * @param lexicalHandler listens for lexical events
+     * @see #getLexicalHandler()
+     */
+    public void setLexicalHandler(LexicalHandler lexicalHandler) {
+        this.lexicalHandler = lexicalHandler;
+    }
+
+    /**
+     * Returns true if this SAX parser processes namespaces.
+     *
+     * @see #setNamespaceProcessingEnabled(boolean)
+     */
+    public boolean isNamespaceProcessingEnabled() {
+        return processNamespaces;
+    }
+
+    /**
+     * Enables or disables namespace processing. Set to true by default. If you
+     * enable namespace processing, the parser will invoke
+     * {@link ContentHandler#startPrefixMapping(String, String)} and
+     * {@link ContentHandler#endPrefixMapping(String)}, and it will filter
+     * out namespace declarations from element attributes.
+     *
+     * @see #isNamespaceProcessingEnabled()
+     */
+    public void setNamespaceProcessingEnabled(boolean processNamespaces) {
+        this.processNamespaces = processNamespaces;
+    }
+
+    public void parse(InputSource input) throws IOException, SAXException {
+        if (processNamespacePrefixes && processNamespaces) {
+            /*
+             * Expat has XML_SetReturnNSTriplet, but that still doesn't
+             * include xmlns attributes like this feature requires. We may
+             * have to implement namespace processing ourselves if we want
+             * this (not too difficult). We obviously "support" namespace
+             * prefixes if namespaces are disabled.
+             */
+            throw new SAXNotSupportedException("The 'namespace-prefix' " +
+                    "feature is not supported while the 'namespaces' " +
+                    "feature is enabled.");
+        }
+
+        // Try the character stream.
+        Reader reader = input.getCharacterStream();
+        if (reader != null) {
+            try {
+                parse(reader, input.getPublicId(), input.getSystemId());
+            } finally {
+                // TODO: Don't eat original exception when close() throws.
+                reader.close();
+            }
+            return;
+        }
+
+        // Try the byte stream.
+        InputStream in = input.getByteStream();
+        String encoding = input.getEncoding();
+        if (in != null) {
+            try {
+                parse(in, encoding, input.getPublicId(), input.getSystemId());
+            } finally {
+                // TODO: Don't eat original exception when close() throws.
+                in.close();
+            }
+            return;
+        }
+
+        String systemId = input.getSystemId();
+        if (systemId == null) {
+            throw new SAXException("No input specified.");
+        }
+
+        // Try the system id.
+        in = ExpatParser.openUrl(systemId);
+        try {
+            parse(in, encoding, input.getPublicId(), systemId);
+        } finally {
+            in.close();
+        }
+    }
+
+    private void parse(Reader in, String publicId, String systemId)
+            throws IOException, SAXException {
+        ExpatParser parser = new ExpatParser(
+                ExpatParser.CHARACTER_ENCODING,
+                this,
+                processNamespaces,
+                publicId,
+                systemId
+        );
+        parser.parseDocument(in);
+    }
+
+    private void parse(InputStream in, String encoding, String publicId,
+            String systemId) throws IOException, SAXException {
+        ExpatParser parser = new ExpatParser(
+                encoding,
+                this,
+                processNamespaces,
+                publicId,
+                systemId
+        );
+        parser.parseDocument(in);
+    }
+
+    public void parse(String systemId) throws IOException, SAXException {
+        parse(new InputSource(systemId));
+    }
+}