| /* |
| * 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); |
| } |
| } |
| } |
| } |