FileDocCategorySizeDatePackage
ExpatParser.javaAPI DocAndroid 1.5 API26002Wed May 06 22:41:06 BST 2009org.apache.harmony.xml

ExpatParser

public class ExpatParser extends Object
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

Fields Summary
private static final int
BUFFER_SIZE
private int
pointer
Pointer to XML_Parser instance.
private boolean
inStartElement
private int
attributeCount
private int
attributePointer
private final Locator
locator
private final ExpatReader
xmlReader
private final String
publicId
private final String
systemId
private final String
encoding
private final ExpatAttributes
attributes
private static final String
OUTSIDE_START_ELEMENT
private static final String
DEFAULT_ENCODING
We default to UTF-8 when the user doesn't specify an encoding.
static final String
CHARACTER_ENCODING
Encoding used for Java chars, used to parse Readers and Strings
private static final int
TIMEOUT
Timeout for HTTP connections (in ms)
Constructors Summary
ExpatParser(String encoding, ExpatReader xmlReader, boolean processNamespaces, String publicId, String systemId)
Constructs a new parser with the specified encoding.


                 
    /*package*/    
                  
        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
        );
    
private ExpatParser(String encoding, ExpatReader xmlReader, int pointer, String publicId, String systemId)
Used by {@link EntityParser}.

        this.encoding = encoding;
        this.xmlReader = xmlReader;
        this.pointer = pointer;
        this.systemId = systemId;
        this.publicId = publicId;
    
Methods Summary
voidappend(java.lang.String xml)
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

        try {
            append(this.pointer, xml, false);
        } catch (ExpatException e) {
            throw new ParseException(e.getMessage(), this.locator);
        }
    
private native voidappend(int pointer, java.lang.String xml, boolean isFinal)

voidappend(char[] xml, int offset, int 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 char[]
param
length of characters to use
throws
SAXException if an error occurs during parsing

        try {
            append(this.pointer, xml, offset, length);
        } catch (ExpatException e) {
            throw new ParseException(e.getMessage(), this.locator);
        }
    
private native voidappend(int pointer, char[] xml, int offset, int length)

voidappend(byte[] xml)
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

        append(xml, 0, xml.length);
    
voidappend(byte[] xml, int offset, int 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

        try {
            append(this.pointer, xml, offset, length);
        } catch (ExpatException e) {
            throw new ParseException(e.getMessage(), this.locator);
        }
    
private native voidappend(int pointer, byte[] xml, int offset, int length)

org.xml.sax.AttributescloneAttributes()
Clones the current attributes so they can be used outside of startElement().

        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 intcloneAttributes(int pointer, int attributeCount)

private intcolumn()
Gets the current column number within the XML file.

        return column(this.pointer);
    
private static native intcolumn(int pointer)

voidcomment(char[] text, int length)

        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
        if (lexicalHandler != null) {
            lexicalHandler.comment(text, 0, length);
        }
    
private static native intcreateEntityParser(int parentPointer, java.lang.String context, java.lang.String encoding)
Creates a native entity parser.

param
parentPointer pointer to parent Expat parser
param
context passed to {@link #handleExternalEntity}
param
encoding
return
pointer to native parser

voidendCdata()

        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
        if (lexicalHandler != null) {
            lexicalHandler.endCDATA();
        }
    
private voidendDocument()

        ContentHandler contentHandler;
        contentHandler = xmlReader.contentHandler;
        if (contentHandler != null) {
            contentHandler.endDocument();
        }
    
voidendDtd()

        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
        if (lexicalHandler != null) {
            lexicalHandler.endDTD();
        }
    
voidendElement(java.lang.String uri, java.lang.String localName, java.lang.String qName)

        ContentHandler contentHandler = xmlReader.contentHandler;
        if (contentHandler != null) {
            contentHandler.endElement(uri, localName, qName);
        }
    
voidendNamespace(java.lang.String prefix)

        ContentHandler contentHandler = xmlReader.contentHandler;
        if (contentHandler != null) {
            contentHandler.endPrefixMapping(prefix);
        }
    
protected synchronized voidfinalize()

        if (this.pointer != 0) {
            release(this.pointer);
            this.pointer = 0;
        }
    
voidfinish()
Indicate that we're finished parsing.

throws
SAXException if the xml is incomplete

        try {
            append(this.pointer, "", true);
        } catch (ExpatException e) {
            throw new ParseException(e.getMessage(), this.locator);
        }
    
voidhandleExternalEntity(java.lang.String context, java.lang.String publicId, java.lang.String systemId)
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

        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 (http://tinyurl.com/56ggrk),
             * 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, encoding);
        try {
            EntityParser entityParser = new EntityParser(encoding, xmlReader,
                    pointer, inputSource.getPublicId(),
                    inputSource.getSystemId());

            parseExternalEntity(entityParser, inputSource);
        } finally {
            releaseParser(pointer);
        }
    
private native intinitialize(java.lang.String encoding, boolean namespacesEnabled)
Initializes native resources.

return
the pointer to the native parser

private intline()
Gets the current line number within the XML file.

        staticInitialize("");
    
        return line(this.pointer);
    
private static native intline(int pointer)

static java.io.InputStreamopenUrl(java.lang.String url)
Opens an InputStream for the given URL.

        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;
        }
    
voidparseDocument(java.io.InputStream in)
Parses an XML document from the given input stream.

        startDocument();
        parseFragment(in);
        finish();
        endDocument();
    
voidparseDocument(java.io.Reader in)
Parses an XML Document from the given reader.

        startDocument();
        parseFragment(in);
        finish();
        endDocument();
    
private voidparseExternalEntity(org.apache.harmony.xml.ExpatParser entityParser, org.xml.sax.InputSource inputSource)
Parses the the external entity provided by the input source.

        /*
         * 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();
        }
    
private voidparseFragment(java.io.Reader in)
Parses XML from the given Reader.

        char[] buffer = new char[BUFFER_SIZE / 2];
        int length;
        while ((length = in.read(buffer)) != -1) {
            try {
                append(this.pointer, buffer, 0, length);
            } catch (ExpatException e) {
                throw new ParseException(e.getMessage(), locator);
            }
        }
    
private voidparseFragment(java.io.InputStream in)
Parses XML from the given input stream.

        byte[] buffer = new byte[BUFFER_SIZE];
        int length;
        while ((length = in.read(buffer)) != -1) {
            try {
                append(this.pointer, buffer, 0, length);
            } catch (ExpatException e) {
                throw new ParseException(e.getMessage(), this.locator);
            }
        }
    
private java.lang.StringpickEncoding(org.xml.sax.InputSource inputSource)
Picks an encoding for an external entity. Defaults to UTF-8.

        Reader reader = inputSource.getCharacterStream();
        if (reader != null) {
            return CHARACTER_ENCODING;
        }

        String encoding = inputSource.getEncoding();
        return encoding == null ? DEFAULT_ENCODING : encoding;
    
voidprocessingInstruction(java.lang.String target, java.lang.String data)

        ContentHandler contentHandler = xmlReader.contentHandler;
        if (contentHandler != null) {
            contentHandler.processingInstruction(target, data);
        }        
    
private native voidrelease(int pointer)
Releases all native objects.

private static native voidreleaseParser(int pointer)
Releases native parser only.

voidstartCdata()

        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
        if (lexicalHandler != null) {
            lexicalHandler.startCDATA();
        }
    
private voidstartDocument()

        ContentHandler contentHandler = xmlReader.contentHandler;
        if (contentHandler != null) {
            contentHandler.setDocumentLocator(this.locator);
            contentHandler.startDocument();
        }
    
voidstartDtd(java.lang.String name, java.lang.String publicId, java.lang.String systemId)

        LexicalHandler lexicalHandler = xmlReader.lexicalHandler;
        if (lexicalHandler != null) {
            lexicalHandler.startDTD(name, publicId, systemId);
        }
    
voidstartElement(java.lang.String uri, java.lang.String localName, java.lang.String qName, int attributePointer, int attributeCount)
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

        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;
        }
    
voidstartNamespace(java.lang.String prefix, java.lang.String uri)

        ContentHandler contentHandler = xmlReader.contentHandler;
        if (contentHandler != null) {
            contentHandler.startPrefixMapping(prefix, uri);
        }
    
private static native voidstaticInitialize(java.lang.String emptyString)
Initialize static resources.

voidtext(char[] text, int length)

        ContentHandler contentHandler = xmlReader.contentHandler;
        if (contentHandler != null) {
            contentHandler.characters(text, 0, length);
        }