summaryrefslogtreecommitdiff
path: root/src/com/google/wireless/gdata/parser/xml/SimplePullParser.java
blob: d16aa5f9c5a734ddd3fb5071fc2a82084829b733 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/*
 * 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);
    }
  }
}