blob: 44fb9217f7707d04c3abc4b125b4399a78da049c [file] [log] [blame]
/*
* 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 com.google.wireless.gdata2.parser.xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* This is an abstraction of a pull parser that provides several benefits:<ul>
* <li>it is easier to use robustly because it makes it trivial to handle unexpected tags (which
* might have children)</li>
* <li>it makes the handling of text (cdata) blocks more convenient</li>
* <li>it provides convenient methods for getting a mandatory attribute (and throwing an exception
* if it is missing) or an optional attribute (and using a default value if it is missing)
* </ul>
*/
public class SimplePullParser {
public static final String TEXT_TAG = "![CDATA[";
private final XmlPullParser mParser;
private String mCurrentStartTag;
/**
* Constructs a new SimplePullParser to parse the xml
* @param parser the underlying parser to use
*/
public SimplePullParser(XmlPullParser parser) {
mParser = parser;
mCurrentStartTag = null;
}
/**
* Returns the tag of the next element whose depth is parentDepth plus one
* or null if there are no more such elements before the next start tag. When this returns,
* getDepth() and all methods relating to attributes will refer to the element whose tag is
* returned.
*
* @param parentDepth the depth of the parrent of the item to be returned
* @param textBuffer if null then text blocks will be ignored. If
* non-null then text blocks will be added to the builder and TEXT_TAG
* will be returned when one is found
* @return the next of the next child element's tag, TEXT_TAG if a text block is found, or null
* if there are no more child elements or DATA blocks
* @throws IOException propogated from the underlying parser
* @throws ParseException if there was an error parsing the xml.
*/
public String nextTagOrText(int parentDepth, StringBuffer textBuffer)
throws IOException, ParseException {
while (true) {
int eventType = 0;
try {
eventType = mParser.next();
} catch (XmlPullParserException e) {
throw new ParseException(e);
}
int depth = mParser.getDepth();
mCurrentStartTag = null;
if (eventType == XmlPullParser.START_TAG && depth == parentDepth + 1) {
mCurrentStartTag = mParser.getName();
// TODO: this is an example of how to do logging of the XML
// if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < depth; i++) sb.append(" ");
// sb.append("<").append(mParser.getName());
// int count = mParser.getAttributeCount();
// for (int i = 0; i < count; i++) {
// sb.append(" ");
// sb.append(mParser.getAttributeName(i));
// sb.append("=\"");
// sb.append(mParser.getAttributeValue(i));
// sb.append("\"");
// }
// sb.append(">");
// Log.d(mLogTag, sb.toString());
// }
return mParser.getName();
}
if (eventType == XmlPullParser.END_TAG && depth == parentDepth) {
// TODO: this is an example of how to do logging of the XML
// if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < depth; i++) sb.append(" ");
// sb.append("</>"); // Not quite valid xml but it gets the job done.
// Log.d(mLogTag, sb.toString());
// }
return null;
}
if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) {
return null;
}
if (eventType == XmlPullParser.TEXT && depth == parentDepth) {
if (textBuffer == null) {
continue;
}
String text = mParser.getText();
textBuffer.append(text);
return TEXT_TAG;
}
}
}
/**
* The same as nextTagOrText(int, StringBuilder) but ignores text blocks.
*/
public String nextTag(int parentDepth) throws IOException, ParseException {
return nextTagOrText(parentDepth, null /* ignore text */);
}
/**
* Returns the depth of the current element. The depth is 0 before the first
* element has been returned, 1 after that, etc.
*
* @return the depth of the current element
*/
public int getDepth() {
return mParser.getDepth();
}
/**
* Consumes the rest of the children, accumulating any text at this level into the builder.
*
* @param textBuffer
* @throws IOException propogated from the XmlPullParser
* @throws ParseException if there was an error parsing the xml.
*/
public void readRemainingText(int parentDepth, StringBuffer textBuffer)
throws IOException, ParseException {
while (nextTagOrText(parentDepth, textBuffer) != null) {
}
}
/**
* Returns the number of attributes on the current element.
*
* @return the number of attributes on the current element
*/
public int numAttributes() {
return mParser.getAttributeCount();
}
/**
* Returns the name of the nth attribute on the current element.
*
* @return the name of the nth attribute on the current element
*/
public String getAttributeName(int i) {
return mParser.getAttributeName(i);
}
/**
* Returns the namespace of the nth attribute on the current element.
*
* @return the namespace of the nth attribute on the current element
*/
public String getAttributeNamespace(int i) {
return mParser.getAttributeNamespace(i);
}
/**
* Returns the string value of the named attribute.
*
* @param namespace the namespace of the attribute
* @param name the name of the attribute
* @param defaultValue the value to return if the attribute is not specified
* @return the value of the attribute
*/
public String getStringAttribute(
String namespace, String name, String defaultValue) {
String value = mParser.getAttributeValue(namespace, name);
if (null == value) return defaultValue;
return value;
}
/**
* Returns the string value of the named attribute. An exception will
* be thrown if the attribute is not present.
*
* @param namespace the namespace of the attribute
* @param name the name of the attribute @return the value of the attribute
* @throws ParseException thrown if the attribute is missing
*/
public String getStringAttribute(String namespace, String name) throws ParseException {
String value = mParser.getAttributeValue(namespace, name);
if (null == value) {
throw new ParseException(
"missing '" + name + "' attribute on '" + mCurrentStartTag + "' element");
}
return value;
}
/**
* Returns the string value of the named attribute. An exception will
* be thrown if the attribute is not a valid integer.
*
* @param namespace the namespace of the attribute
* @param name the name of the attribute
* @param defaultValue the value to return if the attribute is not specified
* @return the value of the attribute
* @throws ParseException thrown if the attribute not a valid integer.
*/
public int getIntAttribute(String namespace, String name, int defaultValue)
throws ParseException {
String value = mParser.getAttributeValue(namespace, name);
if (null == value) return defaultValue;
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new ParseException("Cannot parse '" + value + "' as an integer");
}
}
/**
* Returns the string value of the named attribute. An exception will
* be thrown if the attribute is not present or is not a valid integer.
*
* @param namespace the namespace of the attribute
* @param name the name of the attribute @return the value of the attribute
* @throws ParseException thrown if the attribute is missing or not a valid integer.
*/
public int getIntAttribute(String namespace, String name)
throws ParseException {
String value = getStringAttribute(namespace, name);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new ParseException("Cannot parse '" + value + "' as an integer");
}
}
/**
* Returns the string value of the named attribute. An exception will
* be thrown if the attribute is not a valid long.
*
* @param namespace the namespace of the attribute
* @param name the name of the attribute @return the value of the attribute
* @throws ParseException thrown if the attribute is not a valid long.
*/
public long getLongAttribute(String namespace, String name, long defaultValue)
throws ParseException {
String value = mParser.getAttributeValue(namespace, name);
if (null == value) return defaultValue;
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
throw new ParseException("Cannot parse '" + value + "' as a long");
}
}
/**
* Returns the string value of the named attribute. An exception will
* be thrown if the attribute is not present or is not a valid long.
*
* @param namespace the namespace of the attribute
* @param name the name of the attribute @return the value of the attribute
* @throws ParseException thrown if the attribute is missing or not a valid long.
*/
public long getLongAttribute(String namespace, String name)
throws ParseException {
String value = getStringAttribute(namespace, name);
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
throw new ParseException("Cannot parse '" + value + "' as a long");
}
}
public static final class ParseException extends Exception {
public ParseException(String message) {
super(message);
}
public ParseException(String message, Throwable cause) {
super(message, cause);
}
public ParseException(Throwable cause) {
super(cause);
}
}
}