FileDocCategorySizeDatePackage
SimplePullParser.javaAPI DocAndroid 1.5 API10879Wed May 06 22:41:16 BST 2009com.google.wireless.gdata.parser.xml

SimplePullParser.java

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