blob: ba905a9d242afed2cafa90ced39c9976fb4f966c [file] [log] [blame]
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to deal
5 * in the Software without restriction, including without limitation the rights
6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 * sell copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 * IN THE SOFTWARE. */
20
21// Contributors: Paul Hackenberger (unterminated entity handling in relaxed mode)
22
23package org.kxml2.io;
24
Jesse Wilson086fd022010-11-29 16:01:57 -080025import java.io.Closeable;
Jesse Wilsonccd79e22010-11-04 14:02:20 -070026import java.io.IOException;
27import java.io.InputStream;
28import java.io.InputStreamReader;
29import java.io.Reader;
30import java.util.HashMap;
31import java.util.Map;
Jesse Wilsona78c2aa2010-11-14 12:01:08 -080032import libcore.internal.StringPool;
Jesse Wilsonccd79e22010-11-04 14:02:20 -070033import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080035
Jesse Wilsonccd79e22010-11-04 14:02:20 -070036/**
Jesse Wilson76c85882010-11-24 18:26:03 -080037 * An XML pull parser with limited support for parsing internal DTDs.
Jesse Wilsonccd79e22010-11-04 14:02:20 -070038 */
Jesse Wilson086fd022010-11-29 16:01:57 -080039public class KXmlParser implements XmlPullParser, Closeable {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080040
Jesse Wilson76c85882010-11-24 18:26:03 -080041 private final String PROPERTY_XMLDECL_VERSION
42 = "http://xmlpull.org/v1/doc/properties.html#xmldecl-version";
43 private final String PROPERTY_XMLDECL_STANDALONE
44 = "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone";
45 private final String PROPERTY_LOCATION = "http://xmlpull.org/v1/doc/properties.html#location";
46 private static final String FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed";
47
48 private static final Map<String, String> DEFAULT_ENTITIES = new HashMap<String, String>();
49 static {
50 DEFAULT_ENTITIES.put("lt", "<");
51 DEFAULT_ENTITIES.put("gt", ">");
52 DEFAULT_ENTITIES.put("amp", "&");
53 DEFAULT_ENTITIES.put("apos", "'");
54 DEFAULT_ENTITIES.put("quot", "\"");
55 }
56
Jesse Wilson7fac0472010-11-23 17:02:32 -080057 private static final int ELEMENTDECL = 11;
58 private static final int ENTITYDECL = 12;
59 private static final int ATTLISTDECL = 13;
60 private static final int NOTATIONDECL = 14;
61 private static final int PARAMETER_ENTITY_REF = 15;
Jesse Wilsonfda724d2010-11-04 18:32:03 -070062 private static final char[] START_COMMENT = { '<', '!', '-', '-' };
63 private static final char[] END_COMMENT = { '-', '-', '>' };
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -080064 private static final char[] COMMENT_DOUBLE_DASH = { '-', '-' };
Jesse Wilsonfda724d2010-11-04 18:32:03 -070065 private static final char[] START_CDATA = { '<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[' };
66 private static final char[] END_CDATA = { ']', ']', '>' };
67 private static final char[] START_PROCESSING_INSTRUCTION = { '<', '?' };
68 private static final char[] END_PROCESSING_INSTRUCTION = { '?', '>' };
69 private static final char[] START_DOCTYPE = { '<', '!', 'D', 'O', 'C', 'T', 'Y', 'P', 'E' };
Jesse Wilson7fac0472010-11-23 17:02:32 -080070 private static final char[] SYSTEM = { 'S', 'Y', 'S', 'T', 'E', 'M' };
71 private static final char[] PUBLIC = { 'P', 'U', 'B', 'L', 'I', 'C' };
72 private static final char[] START_ELEMENT = { '<', '!', 'E', 'L', 'E', 'M', 'E', 'N', 'T' };
73 private static final char[] START_ATTLIST = { '<', '!', 'A', 'T', 'T', 'L', 'I', 'S', 'T' };
74 private static final char[] START_ENTITY = { '<', '!', 'E', 'N', 'T', 'I', 'T', 'Y' };
75 private static final char[] START_NOTATION = { '<', '!', 'N', 'O', 'T', 'A', 'T', 'I', 'O', 'N' };
76 private static final char[] EMPTY = new char[] { 'E', 'M', 'P', 'T', 'Y' };
77 private static final char[] ANY = new char[]{ 'A', 'N', 'Y' };
78 private static final char[] NDATA = new char[]{ 'N', 'D', 'A', 'T', 'A' };
79 private static final char[] NOTATION = new char[]{ 'N', 'O', 'T', 'A', 'T', 'I', 'O', 'N' };
80 private static final char[] REQUIRED = new char[] { 'R', 'E', 'Q', 'U', 'I', 'R', 'E', 'D' };
81 private static final char[] IMPLIED = new char[] { 'I', 'M', 'P', 'L', 'I', 'E', 'D' };
82 private static final char[] FIXED = new char[] { 'F', 'I', 'X', 'E', 'D' };
Jesse Wilsonfda724d2010-11-04 18:32:03 -070083
Elliott Hughesd21d78f2010-05-13 11:32:57 -070084 static final private String UNEXPECTED_EOF = "Unexpected EOF";
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080085 static final private String ILLEGAL_TYPE = "Wrong event type";
Jesse Wilsonfda724d2010-11-04 18:32:03 -070086 static final private int XML_DECLARATION = 998;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080087
88 // general
Jesse Wilsonfda724d2010-11-04 18:32:03 -070089 private String location;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080090
91 private String version;
92 private Boolean standalone;
Jesse Wilson086fd022010-11-29 16:01:57 -080093 private String rootElementName;
94 private String systemId;
95 private String publicId;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080096
Jesse Wilson76c85882010-11-24 18:26:03 -080097 /**
98 * True if the {@code <!DOCTYPE>} contents are handled. The DTD defines
99 * entity values and default attribute values. These values are parsed at
100 * inclusion time and may contain both tags and entity references.
101 *
102 * <p>If this is false, the user must {@link #defineEntityReplacementText
103 * define entity values manually}. Such entity values are literal strings
104 * and will not be parsed. There is no API to define default attributes
105 * manually.
106 */
107 private boolean processDocDecl;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800108 private boolean processNsp;
109 private boolean relaxed;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700110 private boolean keepNamespaceAttributes;
Jesse Wilson76c85882010-11-24 18:26:03 -0800111
112 /**
113 * Entities defined in or for this document. This map is created lazily.
114 */
115 private Map<String, char[]> documentEntities;
116
117 /**
118 * Default attributes in this document. The outer map's key is the element
119 * name; the inner map's key is the attribute name. Both keys should be
120 * without namespace adjustments. This map is created lazily.
121 */
122 private Map<String, Map<String, String>> defaultAttributes;
123
124
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800125 private int depth;
126 private String[] elementStack = new String[16];
127 private String[] nspStack = new String[8];
128 private int[] nspCounts = new int[4];
129
130 // source
131
132 private Reader reader;
133 private String encoding;
Jesse Wilson76c85882010-11-24 18:26:03 -0800134 private ContentSource nextContentSource;
135 private char[] buffer = new char[8192];
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700136 private int position = 0;
137 private int limit = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800138
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700139 /*
140 * Track the number of newlines and columns preceding the current buffer. To
141 * compute the line and column of a position in the buffer, compute the line
142 * and column in the buffer and add the preceding values.
143 */
144 private int bufferStartLine;
145 private int bufferStartColumn;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800146
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700147 // the current token
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800148
149 private int type;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800150 private boolean isWhitespace;
151 private String namespace;
152 private String prefix;
153 private String name;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700154 private String text;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800155
156 private boolean degenerated;
157 private int attributeCount;
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800158
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700159 /*
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800160 * The current element's attributes arranged in groups of 4:
161 * i + 0 = attribute namespace URI
162 * i + 1 = attribute namespace prefix
163 * i + 2 = attribute qualified name (may contain ":", as in "html:h1")
164 * i + 3 = attribute value
165 */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800166 private String[] attributes = new String[16];
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700167
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800168 private String error;
169
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800170 private boolean unresolved;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800171
Jesse Wilsona78c2aa2010-11-14 12:01:08 -0800172 public final StringPool stringPool = new StringPool();
173
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800174 /**
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700175 * Retains namespace attributes like {@code xmlns="http://foo"} or {@code xmlns:foo="http:foo"}
176 * in pulled elements. Most applications will only be interested in the effective namespaces of
177 * their elements, so these attributes aren't useful. But for structure preserving wrappers like
178 * DOM, it is necessary to keep the namespace data around.
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800179 */
180 public void keepNamespaceAttributes() {
181 this.keepNamespaceAttributes = true;
182 }
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800183
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700184 private boolean adjustNsp() throws XmlPullParserException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800185 boolean any = false;
186
187 for (int i = 0; i < attributeCount << 2; i += 4) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800188 String attrName = attributes[i + 2];
189 int cut = attrName.indexOf(':');
190 String prefix;
191
192 if (cut != -1) {
193 prefix = attrName.substring(0, cut);
194 attrName = attrName.substring(cut + 1);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700195 } else if (attrName.equals("xmlns")) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800196 prefix = attrName;
197 attrName = null;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700198 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800199 continue;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700200 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800201
202 if (!prefix.equals("xmlns")) {
203 any = true;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700204 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800205 int j = (nspCounts[depth]++) << 1;
206
207 nspStack = ensureCapacity(nspStack, j + 2);
208 nspStack[j] = attrName;
209 nspStack[j + 1] = attributes[i + 3];
210
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700211 if (attrName != null && attributes[i + 3].isEmpty()) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700212 checkRelaxed("illegal empty namespace");
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700213 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800214
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800215 if (keepNamespaceAttributes) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700216 // explicitly set the namespace for unprefixed attributes
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800217 // such as xmlns="http://foo"
218 attributes[i] = "http://www.w3.org/2000/xmlns/";
219 any = true;
220 } else {
221 System.arraycopy(
222 attributes,
223 i + 4,
224 attributes,
225 i,
226 ((--attributeCount) << 2) - i);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800227
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800228 i -= 4;
229 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800230 }
231 }
232
233 if (any) {
234 for (int i = (attributeCount << 2) - 4; i >= 0; i -= 4) {
235
236 String attrName = attributes[i + 2];
237 int cut = attrName.indexOf(':');
238
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700239 if (cut == 0 && !relaxed) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800240 throw new RuntimeException(
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700241 "illegal attribute name: " + attrName + " at " + this);
242 } else if (cut != -1) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800243 String attrPrefix = attrName.substring(0, cut);
244
245 attrName = attrName.substring(cut + 1);
246
247 String attrNs = getNamespace(attrPrefix);
248
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700249 if (attrNs == null && !relaxed) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800250 throw new RuntimeException(
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700251 "Undefined Prefix: " + attrPrefix + " in " + this);
252 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800253
254 attributes[i] = attrNs;
255 attributes[i + 1] = attrPrefix;
256 attributes[i + 2] = attrName;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800257 }
258 }
259 }
260
261 int cut = name.indexOf(':');
262
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700263 if (cut == 0) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700264 checkRelaxed("illegal tag name: " + name);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700265 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800266
267 if (cut != -1) {
268 prefix = name.substring(0, cut);
269 name = name.substring(cut + 1);
270 }
271
272 this.namespace = getNamespace(prefix);
273
274 if (this.namespace == null) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700275 if (prefix != null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700276 checkRelaxed("undefined prefix: " + prefix);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700277 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800278 this.namespace = NO_NAMESPACE;
279 }
280
281 return any;
282 }
283
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700284 private String[] ensureCapacity(String[] arr, int required) {
285 if (arr.length >= required) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800286 return arr;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700287 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800288 String[] bigger = new String[required + 16];
289 System.arraycopy(arr, 0, bigger, 0, arr.length);
290 return bigger;
291 }
292
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700293 private void checkRelaxed(String errorMessage) throws XmlPullParserException {
294 if (!relaxed) {
295 throw new XmlPullParserException(errorMessage, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700296 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700297 if (error == null) {
298 error = "Error: " + errorMessage;
299 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800300 }
301
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800302 public int next() throws XmlPullParserException, IOException {
303 return next(false);
304 }
305
306 public int nextToken() throws XmlPullParserException, IOException {
307 return next(true);
308 }
309
310 private int next(boolean justOneToken) throws IOException, XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700311 if (reader == null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700312 throw new XmlPullParserException("setInput() must be called first.", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700313 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800314
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700315 if (type == END_TAG) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800316 depth--;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700317 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800318
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800319 // degenerated needs to be handled before error because of possible
320 // processor expectations(!)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800321
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800322 if (degenerated) {
323 degenerated = false;
324 type = END_TAG;
325 return type;
326 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800327
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800328 if (error != null) {
329 if (justOneToken) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700330 text = error;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800331 type = COMMENT;
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800332 error = null;
333 return type;
334 } else {
335 error = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800336 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800337 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800338
Jesse Wilson7fac0472010-11-23 17:02:32 -0800339 type = peekType(false);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800340
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800341 if (type == XML_DECLARATION) {
342 readXmlDeclaration();
Jesse Wilson7fac0472010-11-23 17:02:32 -0800343 type = peekType(false);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800344 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800345
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800346 text = null;
347 isWhitespace = true;
348 prefix = null;
349 name = null;
350 namespace = null;
351 attributeCount = -1;
Jesse Wilson086fd022010-11-29 16:01:57 -0800352 boolean throwOnResolveFailure = !justOneToken;
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800353
354 while (true) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800355 switch (type) {
356
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800357 /*
358 * Return immediately after encountering a start tag, end tag, or
359 * the end of the document.
360 */
361 case START_TAG:
Jesse Wilson086fd022010-11-29 16:01:57 -0800362 parseStartTag(false, throwOnResolveFailure);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800363 return type;
364 case END_TAG:
365 readEndTag();
366 return type;
367 case END_DOCUMENT:
368 return type;
369
370 /*
371 * Return after any text token when we're looking for a single
372 * token. Otherwise concatenate all text between tags.
373 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700374 case ENTITY_REF:
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800375 if (justOneToken) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700376 StringBuilder entityTextBuilder = new StringBuilder();
Jesse Wilson086fd022010-11-29 16:01:57 -0800377 readEntity(entityTextBuilder, true, throwOnResolveFailure, ValueContext.TEXT);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700378 text = entityTextBuilder.toString();
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800379 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700380 }
381 // fall-through
382 case TEXT:
Jesse Wilson086fd022010-11-29 16:01:57 -0800383 text = readValue('<', !justOneToken, throwOnResolveFailure, ValueContext.TEXT);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700384 if (depth == 0 && isWhitespace) {
385 type = IGNORABLE_WHITESPACE;
386 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800387 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700388 case CDSECT:
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700389 read(START_CDATA);
390 text = readUntil(END_CDATA, true);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800391 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700392
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800393 /*
394 * Comments, processing instructions and declarations are returned
395 * when we're looking for a single token. Otherwise they're skipped.
396 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700397 case COMMENT:
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800398 String commentText = readComment(justOneToken);
399 if (justOneToken) {
400 text = commentText;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700401 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800402 break;
403 case PROCESSING_INSTRUCTION:
404 read(START_PROCESSING_INSTRUCTION);
405 String processingInstruction = readUntil(END_PROCESSING_INSTRUCTION, justOneToken);
406 if (justOneToken) {
407 text = processingInstruction;
408 }
409 break;
410 case DOCDECL:
Jesse Wilson7fac0472010-11-23 17:02:32 -0800411 readDoctype();
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800412 if (justOneToken) {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800413 text = ""; // TODO: support capturing the doctype text
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800414 }
415 break;
Jesse Wilson7fac0472010-11-23 17:02:32 -0800416
417 default:
418 throw new XmlPullParserException("Unexpected token", this, null);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800419 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800420
421 if (justOneToken) {
422 return type;
423 }
424
425 if (type == IGNORABLE_WHITESPACE) {
426 text = null;
427 }
428
429 /*
430 * We've read all that we can of a non-empty text block. Always
431 * report this as text, even if it was a CDATA block or entity
432 * reference.
433 */
Jesse Wilson7fac0472010-11-23 17:02:32 -0800434 int peek = peekType(false);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800435 if (text != null && !text.isEmpty() && peek < TEXT) {
436 type = TEXT;
437 return type;
438 }
439
440 type = peek;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800441 }
442 }
443
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700444 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700445 * Reads text until the specified delimiter is encountered. Consumes the
446 * text and the delimiter.
447 *
448 * @param returnText true to return the read text excluding the delimiter;
449 * false to return null.
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700450 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700451 private String readUntil(char[] delimiter, boolean returnText)
452 throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700453 int start = position;
454 StringBuilder result = null;
455
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800456 if (returnText && text != null) {
457 result = new StringBuilder();
458 result.append(text);
459 }
460
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700461 search:
462 while (true) {
463 if (position + delimiter.length >= limit) {
464 if (start < position && returnText) {
465 if (result == null) {
466 result = new StringBuilder();
467 }
468 result.append(buffer, start, position - start);
469 }
470 if (!fillBuffer(delimiter.length)) {
471 checkRelaxed(UNEXPECTED_EOF);
472 type = COMMENT;
473 return null;
474 }
475 start = position;
476 }
477
478 // TODO: replace with Arrays.equals(buffer, position, delimiter, 0, delimiter.length)
479 // when the VM has better method inlining
480 for (int i = 0; i < delimiter.length; i++) {
481 if (buffer[position + i] != delimiter[i]) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700482 position++;
483 continue search;
484 }
485 }
486
487 break;
488 }
489
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700490 int end = position;
491 position += delimiter.length;
492
493 if (!returnText) {
494 return null;
495 } else if (result == null) {
Jesse Wilsona78c2aa2010-11-14 12:01:08 -0800496 return stringPool.get(buffer, start, end - start);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700497 } else {
498 result.append(buffer, start, end - start);
499 return result.toString();
500 }
501 }
502
503 /**
504 * Returns true if an XML declaration was read.
505 */
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800506 private void readXmlDeclaration() throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700507 if (bufferStartLine != 0 || bufferStartColumn != 0 || position != 0) {
508 checkRelaxed("processing instructions must not start with xml");
509 }
510
511 read(START_PROCESSING_INSTRUCTION);
Jesse Wilson086fd022010-11-29 16:01:57 -0800512 parseStartTag(true, true);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700513
514 if (attributeCount < 1 || !"version".equals(attributes[2])) {
515 checkRelaxed("version expected");
516 }
517
518 version = attributes[3];
519
520 int pos = 1;
521
522 if (pos < attributeCount && "encoding".equals(attributes[2 + 4])) {
523 encoding = attributes[3 + 4];
524 pos++;
525 }
526
527 if (pos < attributeCount && "standalone".equals(attributes[4 * pos + 2])) {
528 String st = attributes[3 + 4 * pos];
529 if ("yes".equals(st)) {
530 standalone = Boolean.TRUE;
531 } else if ("no".equals(st)) {
532 standalone = Boolean.FALSE;
533 } else {
534 checkRelaxed("illegal standalone value: " + st);
535 }
536 pos++;
537 }
538
539 if (pos != attributeCount) {
540 checkRelaxed("unexpected attributes in XML declaration");
541 }
542
543 isWhitespace = true;
544 text = null;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700545 }
546
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800547 private String readComment(boolean returnText) throws IOException, XmlPullParserException {
548 read(START_COMMENT);
549
550 if (relaxed) {
551 return readUntil(END_COMMENT, returnText);
552 }
553
554 String commentText = readUntil(COMMENT_DOUBLE_DASH, returnText);
555 if (peekCharacter() != '>') {
556 throw new XmlPullParserException("Comments may not contain --", this, null);
557 }
558 position++;
559 return commentText;
560 }
561
Jesse Wilson7fac0472010-11-23 17:02:32 -0800562 /**
563 * Read the document's DTD. Although this parser is non-validating, the DTD
564 * must be parsed to capture entity values and default attribute values.
565 */
566 private void readDoctype() throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700567 read(START_DOCTYPE);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800568 skip();
Jesse Wilson086fd022010-11-29 16:01:57 -0800569 rootElementName = readName();
570 readExternalId(true, true);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800571 skip();
572 if (peekCharacter() == '[') {
573 readInternalSubset();
574 }
575 skip();
576 read('>');
577 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700578
Jesse Wilson7fac0472010-11-23 17:02:32 -0800579 /**
580 * Reads an external ID of one of these two forms:
581 * SYSTEM "quoted system name"
582 * PUBLIC "quoted public id" "quoted system name"
583 *
584 * If the system name is not required, this also supports lone public IDs of
585 * this form:
586 * PUBLIC "quoted public id"
587 *
588 * Returns true if any ID was read.
589 */
Jesse Wilson086fd022010-11-29 16:01:57 -0800590 private boolean readExternalId(boolean requireSystemName, boolean assignFields)
Jesse Wilson7fac0472010-11-23 17:02:32 -0800591 throws IOException, XmlPullParserException {
592 skip();
593 int c = peekCharacter();
594
595 if (c == 'S') {
596 read(SYSTEM);
597 } else if (c == 'P') {
598 read(PUBLIC);
599 skip();
Jesse Wilson086fd022010-11-29 16:01:57 -0800600 if (assignFields) {
601 publicId = readQuotedId(true);
602 } else {
603 readQuotedId(false);
604 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800605 } else {
606 return false;
607 }
608
609 skip();
610
611 if (!requireSystemName) {
612 int delimiter = peekCharacter();
613 if (delimiter != '"' && delimiter != '\'') {
614 return true; // no system name!
615 }
616 }
617
Jesse Wilson086fd022010-11-29 16:01:57 -0800618 if (assignFields) {
619 systemId = readQuotedId(true);
620 } else {
621 readQuotedId(false);
622 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800623 return true;
624 }
625
Jesse Wilson086fd022010-11-29 16:01:57 -0800626 private static final char[] SINGLE_QUOTE = new char[] { '\'' };
627 private static final char[] DOUBLE_QUOTE = new char[] { '"' };
628
Jesse Wilson7fac0472010-11-23 17:02:32 -0800629 /**
630 * Reads a quoted string, performing no entity escaping of the contents.
631 */
Jesse Wilson086fd022010-11-29 16:01:57 -0800632 private String readQuotedId(boolean returnText) throws IOException, XmlPullParserException {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800633 int quote = peekCharacter();
Jesse Wilson086fd022010-11-29 16:01:57 -0800634 char[] delimiter;
635 if (quote == '"') {
636 delimiter = DOUBLE_QUOTE;
637 } else if (quote == '\'') {
638 delimiter = SINGLE_QUOTE;
639 } else {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800640 throw new XmlPullParserException("Expected a quoted string", this, null);
641 }
642 position++;
Jesse Wilson086fd022010-11-29 16:01:57 -0800643 return readUntil(delimiter, returnText);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800644 }
645
646 private void readInternalSubset() throws IOException, XmlPullParserException {
647 read('[');
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800648
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800649 while (true) {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800650 skip();
651 if (peekCharacter() == ']') {
652 position++;
653 return;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800654 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700655
Jesse Wilson7fac0472010-11-23 17:02:32 -0800656 int declarationType = peekType(true);
657 switch (declarationType) {
658 case ELEMENTDECL:
659 readElementDeclaration();
660 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700661
Jesse Wilson7fac0472010-11-23 17:02:32 -0800662 case ATTLISTDECL:
663 readAttributeListDeclaration();
664 break;
665
666 case ENTITYDECL:
667 readEntityDeclaration();
668 break;
669
670 case NOTATIONDECL:
671 readNotationDeclaration();
672 break;
673
674 case PROCESSING_INSTRUCTION:
675 read(START_PROCESSING_INSTRUCTION);
676 readUntil(END_PROCESSING_INSTRUCTION, false);
677 break;
678
679 case COMMENT:
680 readComment(false);
681 break;
682
683 case PARAMETER_ENTITY_REF:
684 throw new XmlPullParserException(
685 "Parameter entity references are not supported", this, null);
686
687 default:
688 throw new XmlPullParserException("Unexpected token", this, null);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700689 }
690 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800691 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700692
Jesse Wilson7fac0472010-11-23 17:02:32 -0800693 /**
694 * Read an element declaration. This contains a name and a content spec.
695 * <!ELEMENT foo EMPTY >
696 * <!ELEMENT foo (bar?,(baz|quux)) >
697 * <!ELEMENT foo (#PCDATA|bar)* >
698 */
699 private void readElementDeclaration() throws IOException, XmlPullParserException {
700 read(START_ELEMENT);
701 skip();
702 readName();
703 readContentSpec();
704 skip();
705 read('>');
706 }
707
708 /**
709 * Read an element content spec. This is a regular expression-like pattern
710 * of names or other content specs. The following operators are supported:
711 * sequence: (a,b,c)
712 * choice: (a|b|c)
713 * optional: a?
714 * one or more: a+
715 * any number: a*
716 *
717 * The special name '#PCDATA' is permitted but only if it is the first
718 * element of the first group:
719 * (#PCDATA|a|b)
720 *
721 * The top-level element must be either a choice, a sequence, or one of the
722 * special names EMPTY and ANY.
723 */
724 private void readContentSpec() throws IOException, XmlPullParserException {
725 // this implementation is very lenient; it scans for balanced parens only
726 skip();
727 int c = peekCharacter();
728 if (c == '(') {
729 int depth = 0;
730 do {
731 if (c == '(') {
732 depth++;
733 } else if (c == ')') {
734 depth--;
735 }
736 position++;
737 c = peekCharacter();
738 } while (depth > 0);
739
740 if (c == '*' || c == '?' || c == '+') {
741 position++;
742 }
743 } else if (c == EMPTY[0]) {
744 read(EMPTY);
745 } else if (c == ANY[0]) {
746 read(ANY);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800747 } else {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800748 throw new XmlPullParserException("Expected element content spec", this, null);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800749 }
750 }
751
Jesse Wilson7fac0472010-11-23 17:02:32 -0800752 /**
753 * Reads an attribute list declaration such as the following:
754 * <!ATTLIST foo
755 * bar CDATA #IMPLIED
756 * quux (a|b|c) "c"
757 * baz NOTATION (a|b|c) #FIXED "c">
758 *
759 * Each attribute has a name, type and default.
760 *
761 * Types are one of the built-in types (CDATA, ID, IDREF, IDREFS, ENTITY,
762 * ENTITIES, NMTOKEN, or NMTOKENS), an enumerated type "(list|of|options)"
763 * or NOTATION followed by an enumerated type.
764 *
765 * The default is either #REQUIRED, #IMPLIED, #FIXED, a quoted value, or
766 * #FIXED with a quoted value.
767 */
768 private void readAttributeListDeclaration() throws IOException, XmlPullParserException {
769 read(START_ATTLIST);
770 skip();
771 String elementName = readName();
772
773 while (true) {
774 skip();
775 int c = peekCharacter();
776 if (c == '>') {
777 position++;
778 return;
779 }
780
781 // attribute name
782 String attributeName = readName();
783
784 // attribute type
785 skip();
786 if (position + 1 >= limit && !fillBuffer(2)) {
787 throw new XmlPullParserException("Malformed attribute list", this, null);
788 }
789 if (buffer[position] == NOTATION[0] && buffer[position + 1] == NOTATION[1]) {
790 read(NOTATION);
791 skip();
792 }
793 c = peekCharacter();
794 if (c == '(') {
795 position++;
796 while (true) {
797 skip();
798 readName();
799 skip();
800 c = peekCharacter();
801 if (c == ')') {
802 position++;
803 break;
804 } else if (c == '|') {
805 position++;
806 } else {
807 throw new XmlPullParserException("Malformed attribute type", this, null);
808 }
809 }
810 } else {
811 readName();
812 }
813
814 // default value
815 skip();
816 c = peekCharacter();
817 if (c == '#') {
818 position++;
819 c = peekCharacter();
820 if (c == 'R') {
821 read(REQUIRED);
822 } else if (c == 'I') {
823 read(IMPLIED);
824 } else if (c == 'F') {
825 read(FIXED);
826 } else {
827 throw new XmlPullParserException("Malformed attribute type", this, null);
828 }
829 skip();
830 c = peekCharacter();
831 }
832 if (c == '"' || c == '\'') {
833 position++;
834 // TODO: does this do escaping correctly?
Jesse Wilson086fd022010-11-29 16:01:57 -0800835 String value = readValue((char) c, true, true, ValueContext.ATTRIBUTE);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800836 position++;
837 defineAttributeDefault(elementName, attributeName, value);
838 }
839 }
840 }
841
842 private void defineAttributeDefault(String elementName, String attributeName, String value) {
Jesse Wilson76c85882010-11-24 18:26:03 -0800843 if (defaultAttributes == null) {
844 defaultAttributes = new HashMap<String, Map<String, String>>();
845 }
846 Map<String, String> elementAttributes = defaultAttributes.get(elementName);
847 if (elementAttributes == null) {
848 elementAttributes = new HashMap<String, String>();
849 defaultAttributes.put(elementName, elementAttributes);
850 }
851 elementAttributes.put(attributeName, value);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800852 }
853
854 /**
855 * Read an entity declaration. The value of internal entities are inline:
856 * <!ENTITY foo "bar">
857 *
858 * The values of external entities must be retrieved by URL or path:
859 * <!ENTITY foo SYSTEM "http://host/file">
860 * <!ENTITY foo PUBLIC "-//Android//Foo//EN" "http://host/file">
861 * <!ENTITY foo SYSTEM "../file.png" NDATA png>
862 *
863 * Entities may be general or parameterized. Parameterized entities are
864 * marked by a percent sign. Such entities may only be used in the DTD:
865 * <!ENTITY % foo "bar">
866 */
867 private void readEntityDeclaration() throws IOException, XmlPullParserException {
868 read(START_ENTITY);
869 boolean generalEntity = true;
870
871 skip();
872 if (peekCharacter() == '%') {
873 generalEntity = false;
874 position++;
875 skip();
876 }
877
878 String name = readName();
879
880 skip();
881 int quote = peekCharacter();
882 if (quote == '"' || quote == '\'') {
883 position++;
Jesse Wilson086fd022010-11-29 16:01:57 -0800884 String value = readValue((char) quote, true, false, ValueContext.ENTITY_DECLARATION);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800885 position++;
Jesse Wilson76c85882010-11-24 18:26:03 -0800886 if (generalEntity && processDocDecl) {
887 if (documentEntities == null) {
888 documentEntities = new HashMap<String, char[]>();
889 }
890 documentEntities.put(name, value.toCharArray());
Jesse Wilson7fac0472010-11-23 17:02:32 -0800891 }
Jesse Wilson086fd022010-11-29 16:01:57 -0800892 } else if (readExternalId(true, false)) {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800893 skip();
894 if (peekCharacter() == NDATA[0]) {
895 read(NDATA);
896 skip();
897 readName();
898 }
899 } else {
900 throw new XmlPullParserException("Expected entity value or external ID", this, null);
901 }
902
903 skip();
904 read('>');
905 }
906
907 private void readNotationDeclaration() throws IOException, XmlPullParserException {
908 read(START_NOTATION);
909 skip();
910 readName();
Jesse Wilson086fd022010-11-29 16:01:57 -0800911 if (!readExternalId(false, false)) {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800912 throw new XmlPullParserException(
913 "Expected external ID or public ID for notation", this, null);
914 }
915 skip();
916 read('>');
917 }
918
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700919 private void readEndTag() throws IOException, XmlPullParserException {
920 read('<');
921 read('/');
922 name = readName(); // TODO: pass the expected name in as a hint?
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800923 skip();
924 read('>');
925
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700926 int sp = (depth - 1) * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800927
928 if (depth == 0) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700929 checkRelaxed("read end tag " + name + " with no tags open");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800930 type = COMMENT;
931 return;
932 }
933
Jesse Wilson3b8cee42010-11-19 14:47:30 -0800934 if (name.equals(elementStack[sp + 3])) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700935 namespace = elementStack[sp];
936 prefix = elementStack[sp + 1];
937 name = elementStack[sp + 2];
Jesse Wilson3b8cee42010-11-19 14:47:30 -0800938 } else if (!relaxed) {
939 throw new XmlPullParserException(
940 "expected: /" + elementStack[sp + 3] + " read: " + name, this, null);
Elliott Hughesb211e132009-11-09 16:06:42 -0800941 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800942 }
943
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700944 /**
945 * Returns the type of the next token.
946 */
Jesse Wilson7fac0472010-11-23 17:02:32 -0800947 private int peekType(boolean inDeclaration) throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700948 if (position >= limit && !fillBuffer(1)) {
949 return END_DOCUMENT;
950 }
951
Jesse Wilson7fac0472010-11-23 17:02:32 -0800952 switch (buffer[position]) {
953 case '&':
954 return ENTITY_REF; // &
955 case '<':
956 if (position + 3 >= limit && !fillBuffer(4)) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700957 throw new XmlPullParserException("Dangling <", this, null);
958 }
959
Jesse Wilson7fac0472010-11-23 17:02:32 -0800960 switch (buffer[position + 1]) {
961 case '/':
962 return END_TAG; // </
963 case '?':
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700964 // we're looking for "<?xml " with case insensitivity
965 if ((position + 5 < limit || fillBuffer(6))
966 && (buffer[position + 2] == 'x' || buffer[position + 2] == 'X')
967 && (buffer[position + 3] == 'm' || buffer[position + 3] == 'M')
968 && (buffer[position + 4] == 'l' || buffer[position + 4] == 'L')
969 && (buffer[position + 5] == ' ')) {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800970 return XML_DECLARATION; // <?xml
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700971 } else {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800972 return PROCESSING_INSTRUCTION; // <?
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800973 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800974 case '!':
975 switch (buffer[position + 2]) {
976 case 'D':
977 return DOCDECL; // <!D
978 case '[':
979 return CDSECT; // <![
980 case '-':
981 return COMMENT; // <!-
982 case 'E':
983 switch (buffer[position + 3]) {
984 case 'L':
985 return ELEMENTDECL; // <!EL
986 case 'N':
987 return ENTITYDECL; // <!EN
988 default:
989 throw new XmlPullParserException("Unexpected <!", this, null);
990 }
991 case 'A':
992 return ATTLISTDECL; // <!A
993 case 'N':
994 return NOTATIONDECL; // <!N
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700995 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800996 default:
997 return START_TAG; // <
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700998 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800999 case '%':
1000 return inDeclaration ? PARAMETER_ENTITY_REF : TEXT;
1001 default:
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001002 return TEXT;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001003 }
1004 }
1005
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001006 /**
1007 * Sets name and attributes
1008 */
Jesse Wilson086fd022010-11-29 16:01:57 -08001009 private void parseStartTag(boolean xmldecl, boolean throwOnResolveFailure)
1010 throws IOException, XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001011 if (!xmldecl) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001012 read('<');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001013 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001014 name = readName();
1015 attributeCount = 0;
1016
1017 while (true) {
1018 skip();
1019
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001020 if (position >= limit && !fillBuffer(1)) {
1021 checkRelaxed(UNEXPECTED_EOF);
1022 return;
1023 }
1024
1025 int c = buffer[position];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001026
1027 if (xmldecl) {
1028 if (c == '?') {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001029 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001030 read('>');
1031 return;
1032 }
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001033 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001034 if (c == '/') {
1035 degenerated = true;
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001036 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001037 skip();
1038 read('>');
1039 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001040 } else if (c == '>') {
1041 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001042 break;
1043 }
1044 }
1045
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001046 String attrName = readName();
1047
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001048 int i = (attributeCount++) * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001049 attributes = ensureCapacity(attributes, i + 4);
Jesse Wilson76c85882010-11-24 18:26:03 -08001050 attributes[i] = "";
1051 attributes[i + 1] = null;
1052 attributes[i + 2] = attrName;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001053
1054 skip();
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001055 if (position >= limit && !fillBuffer(1)) {
1056 checkRelaxed(UNEXPECTED_EOF);
1057 return;
1058 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001059
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001060 if (buffer[position] == '=') {
1061 position++;
1062
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001063 skip();
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001064 if (position >= limit && !fillBuffer(1)) {
1065 checkRelaxed(UNEXPECTED_EOF);
1066 return;
1067 }
1068 char delimiter = buffer[position];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001069
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001070 if (delimiter == '\'' || delimiter == '"') {
1071 position++;
1072 } else if (relaxed) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001073 delimiter = ' ';
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001074 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001075 throw new XmlPullParserException("attr value delimiter missing!", this, null);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001076 }
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001077
Jesse Wilson086fd022010-11-29 16:01:57 -08001078 attributes[i + 3] = readValue(delimiter, true, throwOnResolveFailure,
1079 ValueContext.ATTRIBUTE);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001080
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001081 if (delimiter != ' ') {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001082 position++; // end quote
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001083 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001084 } else if (relaxed) {
Jesse Wilson76c85882010-11-24 18:26:03 -08001085 attributes[i + 3] = attrName;
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001086 } else {
1087 checkRelaxed("Attr.value missing f. " + attrName);
Jesse Wilson76c85882010-11-24 18:26:03 -08001088 attributes[i + 3] = attrName;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001089 }
1090 }
1091
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001092 int sp = depth++ * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001093 elementStack = ensureCapacity(elementStack, sp + 4);
1094 elementStack[sp + 3] = name;
1095
1096 if (depth >= nspCounts.length) {
1097 int[] bigger = new int[depth + 4];
1098 System.arraycopy(nspCounts, 0, bigger, 0, nspCounts.length);
1099 nspCounts = bigger;
1100 }
1101
1102 nspCounts[depth] = nspCounts[depth - 1];
1103
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001104 if (processNsp) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001105 adjustNsp();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001106 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001107 namespace = "";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001108 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001109
Jesse Wilson76c85882010-11-24 18:26:03 -08001110 // For consistency with Expat, add default attributes after fixing namespaces.
1111 if (defaultAttributes != null) {
1112 Map<String, String> elementDefaultAttributes = defaultAttributes.get(name);
1113 if (elementDefaultAttributes != null) {
1114 for (Map.Entry<String, String> entry : elementDefaultAttributes.entrySet()) {
1115 if (getAttributeValue(null, entry.getKey()) != null) {
1116 continue; // an explicit value overrides the default
1117 }
1118
1119 int i = (attributeCount++) * 4;
1120 attributes = ensureCapacity(attributes, i + 4);
1121 attributes[i] = "";
1122 attributes[i + 1] = null;
1123 attributes[i + 2] = entry.getKey();
1124 attributes[i + 3] = entry.getValue();
1125 }
1126 }
1127 }
1128
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001129 elementStack[sp] = namespace;
1130 elementStack[sp + 1] = prefix;
1131 elementStack[sp + 2] = name;
1132 }
1133
Elliott Hughesf33eae72010-05-13 12:36:25 -07001134 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001135 * Reads an entity reference from the buffer, resolves it, and writes the
1136 * resolved entity to {@code out}. If the entity cannot be read or resolved,
1137 * {@code out} will contain the partial entity reference.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001138 */
Jesse Wilson086fd022010-11-29 16:01:57 -08001139 private void readEntity(StringBuilder out, boolean isEntityToken, boolean throwOnResolveFailure,
1140 ValueContext valueContext) throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001141 int start = out.length();
Elliott Hughesf33eae72010-05-13 12:36:25 -07001142
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001143 if (buffer[position++] != '&') {
1144 throw new AssertionError();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001145 }
1146
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001147 out.append('&');
1148
1149 while (true) {
1150 int c = peekCharacter();
1151
1152 if (c == ';') {
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001153 out.append(';');
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001154 position++;
1155 break;
1156
1157 } else if (c >= 128
1158 || (c >= '0' && c <= '9')
1159 || (c >= 'a' && c <= 'z')
1160 || (c >= 'A' && c <= 'Z')
1161 || c == '_'
1162 || c == '-'
1163 || c == '#') {
1164 position++;
1165 out.append((char) c);
1166
1167 } else if (relaxed) {
1168 // intentionally leave the partial reference in 'out'
1169 return;
1170
1171 } else {
1172 throw new XmlPullParserException("unterminated entity ref", this, null);
1173 }
1174 }
1175
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001176 String code = out.substring(start + 1, out.length() - 1);
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001177
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -08001178 if (isEntityToken) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001179 name = code;
1180 }
1181
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001182 if (code.startsWith("#")) {
1183 try {
1184 int c = code.startsWith("#x")
1185 ? Integer.parseInt(code.substring(2), 16)
1186 : Integer.parseInt(code.substring(1));
1187 out.delete(start, out.length());
1188 out.appendCodePoint(c);
1189 unresolved = false;
Jesse Wilson76c85882010-11-24 18:26:03 -08001190 return;
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001191 } catch (NumberFormatException notANumber) {
1192 throw new XmlPullParserException("Invalid character reference: &" + code);
1193 } catch (IllegalArgumentException invalidCodePoint) {
1194 throw new XmlPullParserException("Invalid character reference: &" + code);
1195 }
Jesse Wilson76c85882010-11-24 18:26:03 -08001196 }
1197
1198 if (valueContext == ValueContext.ENTITY_DECLARATION) {
1199 // keep the unresolved &code; in the text to resolve later
1200 return;
1201 }
1202
1203 String defaultEntity = DEFAULT_ENTITIES.get(code);
1204 if (defaultEntity != null) {
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001205 out.delete(start, out.length());
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001206 unresolved = false;
Jesse Wilson76c85882010-11-24 18:26:03 -08001207 out.append(defaultEntity);
1208 return;
1209 }
1210
1211 char[] resolved;
1212 if (documentEntities != null && (resolved = documentEntities.get(code)) != null) {
1213 out.delete(start, out.length());
1214 unresolved = false;
1215 if (processDocDecl) {
1216 pushContentSource(resolved); // parse the entity as XML
1217 } else {
1218 out.append(resolved); // include the entity value as text
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001219 }
Jesse Wilson76c85882010-11-24 18:26:03 -08001220 return;
1221 }
1222
1223 // keep the unresolved entity "&code;" in the text for relaxed clients
1224 unresolved = true;
Jesse Wilson086fd022010-11-29 16:01:57 -08001225 if (throwOnResolveFailure) {
Jesse Wilson76c85882010-11-24 18:26:03 -08001226 checkRelaxed("unresolved: &" + code + ";");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001227 }
1228 }
1229
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001230 /**
Jesse Wilson7fac0472010-11-23 17:02:32 -08001231 * Where a value is found impacts how that value is interpreted. For
1232 * example, in attributes, "\n" must be replaced with a space character. In
1233 * text, "]]>" is forbidden. In entity declarations, named references are
1234 * not resolved.
1235 */
1236 enum ValueContext {
1237 ATTRIBUTE,
1238 TEXT,
1239 ENTITY_DECLARATION
1240 }
1241
1242 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001243 * Returns the current text or attribute value. This also has the side
1244 * effect of setting isWhitespace to false if a non-whitespace character is
1245 * encountered.
1246 *
Jesse Wilson7fac0472010-11-23 17:02:32 -08001247 * @param delimiter {@code <} for text, {@code "} and {@code '} for quoted
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001248 * attributes, or a space for unquoted attributes.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001249 */
Jesse Wilson086fd022010-11-29 16:01:57 -08001250 private String readValue(char delimiter, boolean resolveEntities, boolean throwOnResolveFailure,
Jesse Wilson7fac0472010-11-23 17:02:32 -08001251 ValueContext valueContext) throws IOException, XmlPullParserException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001252
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001253 /*
1254 * This method returns all of the characters from the current position
1255 * through to an appropriate delimiter.
1256 *
1257 * If we're lucky (which we usually are), we'll return a single slice of
1258 * the buffer. This fast path avoids allocating a string builder.
1259 *
1260 * There are 5 unlucky characters we could encounter:
1261 * - "&": entities must be resolved.
1262 * - "<": this isn't permitted in attributes unless relaxed.
1263 * - "]": this requires a lookahead to defend against the forbidden
1264 * CDATA section delimiter "]]>".
1265 * - "\r": If a "\r" is followed by a "\n", we discard the "\r". If it
1266 * isn't followed by "\n", we replace "\r" with either a "\n"
1267 * in text nodes or a space in attribute values.
1268 * - "\n": In attribute values, "\n" must be replaced with a space.
1269 *
1270 * We could also get unlucky by needing to refill the buffer midway
1271 * through the text.
1272 */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001273
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001274 int start = position;
1275 StringBuilder result = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001276
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001277 // if a text section was already started, prefix the start
Jesse Wilson7fac0472010-11-23 17:02:32 -08001278 if (valueContext == ValueContext.TEXT && text != null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001279 result = new StringBuilder();
1280 result.append(text);
1281 }
1282
1283 while (true) {
1284
1285 /*
1286 * Make sure we have at least a single character to read from the
1287 * buffer. This mutates the buffer, so save the partial result
1288 * to the slow path string builder first.
1289 */
1290 if (position >= limit) {
1291 if (start < position) {
1292 if (result == null) {
1293 result = new StringBuilder();
1294 }
1295 result.append(buffer, start, position - start);
1296 }
1297 if (!fillBuffer(1)) {
1298 return result != null ? result.toString() : "";
1299 }
1300 start = position;
1301 }
1302
1303 char c = buffer[position];
1304
1305 if (c == delimiter
1306 || (delimiter == ' ' && (c <= ' ' || c == '>'))
1307 || c == '&' && !resolveEntities) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001308 break;
1309 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001310
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001311 if (c != '\r'
Jesse Wilson7fac0472010-11-23 17:02:32 -08001312 && (c != '\n' || valueContext != ValueContext.ATTRIBUTE)
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001313 && c != '&'
1314 && c != '<'
Jesse Wilson7fac0472010-11-23 17:02:32 -08001315 && (c != ']' || valueContext != ValueContext.TEXT)) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001316 isWhitespace &= (c <= ' ');
1317 position++;
1318 continue;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001319 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001320
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001321 /*
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001322 * We've encountered an unlucky character! Convert from fast
1323 * path to slow path if we haven't done so already.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001324 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001325 if (result == null) {
1326 result = new StringBuilder();
Elliott Hughes6bcf32a2009-11-16 21:23:11 -08001327 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001328 result.append(buffer, start, position - start);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001329
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001330 if (c == '\r') {
1331 if ((position + 1 < limit || fillBuffer(2)) && buffer[position + 1] == '\n') {
1332 position++;
1333 }
Jesse Wilson7fac0472010-11-23 17:02:32 -08001334 c = (valueContext == ValueContext.ATTRIBUTE) ? ' ' : '\n';
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001335
1336 } else if (c == '\n') {
1337 c = ' ';
1338
1339 } else if (c == '&') {
1340 isWhitespace = false; // TODO: what if the entity resolves to whitespace?
Jesse Wilson086fd022010-11-29 16:01:57 -08001341 readEntity(result, false, throwOnResolveFailure, valueContext);
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001342 start = position;
1343 continue;
1344
1345 } else if (c == '<') {
Jesse Wilson7fac0472010-11-23 17:02:32 -08001346 if (valueContext == ValueContext.ATTRIBUTE) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001347 checkRelaxed("Illegal: \"<\" inside attribute value");
1348 }
1349 isWhitespace = false;
1350
1351 } else if (c == ']') {
1352 if ((position + 2 < limit || fillBuffer(3))
1353 && buffer[position + 1] == ']' && buffer[position + 2] == '>') {
1354 checkRelaxed("Illegal: \"]]>\" outside CDATA section");
1355 }
1356 isWhitespace = false;
1357
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001358 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001359 throw new AssertionError();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001360 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001361
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001362 position++;
1363 result.append(c);
1364 start = position;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001365 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001366
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001367 if (result == null) {
Jesse Wilsona78c2aa2010-11-14 12:01:08 -08001368 return stringPool.get(buffer, start, position - start);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001369 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001370 result.append(buffer, start, position - start);
1371 return result.toString();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001372 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001373 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001374
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001375 private void read(char expected) throws IOException, XmlPullParserException {
1376 int c = peekCharacter();
1377 if (c != expected) {
1378 checkRelaxed("expected: '" + expected + "' actual: '" + ((char) c) + "'");
1379 }
1380 position++;
1381 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001382
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001383 private void read(char[] chars) throws IOException, XmlPullParserException {
1384 if (position + chars.length >= limit && !fillBuffer(chars.length)) {
1385 checkRelaxed("expected: '" + new String(chars) + "' but was EOF");
1386 return;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001387 }
1388
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001389 // TODO: replace with Arrays.equals(buffer, position, delimiter, 0, delimiter.length)
1390 // when the VM has better method inlining
1391 for (int i = 0; i < chars.length; i++) {
1392 if (buffer[position + i] != chars[i]) {
1393 checkRelaxed("expected: \"" + new String(chars) + "\" but was \""
1394 + new String(buffer, position, chars.length) + "...\"");
1395 }
1396 }
1397
1398 position += chars.length;
1399 }
1400
1401 private int peekCharacter() throws IOException, XmlPullParserException {
1402 if (position < limit || fillBuffer(1)) {
1403 return buffer[position];
1404 }
1405 return -1;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001406 }
1407
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001408 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001409 * Returns true once {@code limit - position >= minimum}. If the data is
1410 * exhausted before that many characters are available, this returns
1411 * false.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001412 */
Jesse Wilson76c85882010-11-24 18:26:03 -08001413 private boolean fillBuffer(int minimum) throws IOException, XmlPullParserException {
1414 // If we've exhausted the current content source, remove it
1415 while (nextContentSource != null) {
1416 if (position < limit) {
1417 throw new XmlPullParserException("Unbalanced entity!", this, null);
1418 }
1419 popContentSource();
1420 if (limit - position >= minimum) {
1421 return true;
1422 }
1423 }
1424
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001425 // Before clobbering the old characters, update where buffer starts
1426 for (int i = 0; i < position; i++) {
1427 if (buffer[i] == '\n') {
1428 bufferStartLine++;
1429 bufferStartColumn = 0;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001430 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001431 bufferStartColumn++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001432 }
1433 }
1434
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001435 if (limit != position) {
1436 limit -= position;
1437 System.arraycopy(buffer, position, buffer, 0, limit);
1438 } else {
1439 limit = 0;
1440 }
1441
1442 position = 0;
1443 int total;
1444 while ((total = reader.read(buffer, limit, buffer.length - limit)) != -1) {
1445 limit += total;
1446 if (limit >= minimum) {
1447 return true;
1448 }
1449 }
1450 return false;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001451 }
1452
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001453 /**
1454 * Returns an element or attribute name. This is always non-empty for
1455 * non-relaxed parsers.
1456 */
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001457 private String readName() throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001458 if (position >= limit && !fillBuffer(1)) {
1459 checkRelaxed("name expected");
1460 return "";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001461 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001462
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001463 int start = position;
1464 StringBuilder result = null;
1465
1466 // read the first character
1467 char c = buffer[position];
1468 if ((c >= 'a' && c <= 'z')
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001469 || (c >= 'A' && c <= 'Z')
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001470 || c == '_'
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001471 || c == ':'
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001472 || c >= '\u00c0' // TODO: check the XML spec
1473 || relaxed) {
1474 position++;
1475 } else {
1476 checkRelaxed("name expected");
1477 return "";
1478 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001479
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001480 while (true) {
1481 /*
1482 * Make sure we have at least a single character to read from the
1483 * buffer. This mutates the buffer, so save the partial result
1484 * to the slow path string builder first.
1485 */
1486 if (position >= limit) {
1487 if (result == null) {
1488 result = new StringBuilder();
1489 }
1490 result.append(buffer, start, position - start);
1491 if (!fillBuffer(1)) {
1492 return result.toString();
1493 }
1494 start = position;
1495 }
1496
1497 // read another character
1498 c = buffer[position];
1499 if ((c >= 'a' && c <= 'z')
1500 || (c >= 'A' && c <= 'Z')
1501 || (c >= '0' && c <= '9')
1502 || c == '_'
1503 || c == '-'
1504 || c == ':'
1505 || c == '.'
1506 || c >= '\u00b7') { // TODO: check the XML spec
1507 position++;
1508 continue;
1509 }
1510
1511 // we encountered a non-name character. done!
1512 if (result == null) {
Jesse Wilsona78c2aa2010-11-14 12:01:08 -08001513 return stringPool.get(buffer, start, position - start);
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001514 } else {
1515 result.append(buffer, start, position - start);
1516 return result.toString();
1517 }
1518 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001519 }
1520
Jesse Wilson76c85882010-11-24 18:26:03 -08001521 private void skip() throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001522 while (position < limit || fillBuffer(1)) {
1523 int c = buffer[position];
1524 if (c > ' ') {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001525 break;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001526 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001527 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001528 }
1529 }
1530
1531 // public part starts here...
1532
1533 public void setInput(Reader reader) throws XmlPullParserException {
1534 this.reader = reader;
1535
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001536 type = START_DOCUMENT;
1537 name = null;
1538 namespace = null;
1539 degenerated = false;
1540 attributeCount = -1;
1541 encoding = null;
1542 version = null;
1543 standalone = null;
1544
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001545 if (reader == null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001546 return;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001547 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001548
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001549 position = 0;
1550 limit = 0;
1551 bufferStartLine = 0;
1552 bufferStartColumn = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001553 depth = 0;
Jesse Wilson76c85882010-11-24 18:26:03 -08001554 documentEntities = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001555 }
1556
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001557 public void setInput(InputStream is, String _enc) throws XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001558 position = 0;
1559 limit = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001560 String enc = _enc;
1561
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001562 if (is == null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001563 throw new IllegalArgumentException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001564 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001565
1566 try {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001567 if (enc == null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001568 // read the four bytes looking for an indication of the encoding in use
1569 int firstFourBytes = 0;
1570 while (limit < 4) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001571 int i = is.read();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001572 if (i == -1) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001573 break;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001574 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001575 firstFourBytes = (firstFourBytes << 8) | i;
1576 buffer[limit++] = (char) i;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001577 }
1578
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001579 if (limit == 4) {
1580 switch (firstFourBytes) {
1581 case 0x00000FEFF: // UTF-32BE BOM
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001582 enc = "UTF-32BE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001583 limit = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001584 break;
1585
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001586 case 0x0FFFE0000: // UTF-32LE BOM
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001587 enc = "UTF-32LE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001588 limit = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001589 break;
1590
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001591 case 0x0000003c: // '>' in UTF-32BE
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001592 enc = "UTF-32BE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001593 buffer[0] = '<';
1594 limit = 1;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001595 break;
1596
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001597 case 0x03c000000: // '<' in UTF-32LE
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001598 enc = "UTF-32LE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001599 buffer[0] = '<';
1600 limit = 1;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001601 break;
1602
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001603 case 0x0003c003f: // "<?" in UTF-16BE
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001604 enc = "UTF-16BE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001605 buffer[0] = '<';
1606 buffer[1] = '?';
1607 limit = 2;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001608 break;
1609
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001610 case 0x03c003f00: // "<?" in UTF-16LE
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001611 enc = "UTF-16LE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001612 buffer[0] = '<';
1613 buffer[1] = '?';
1614 limit = 2;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001615 break;
1616
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001617 case 0x03c3f786d: // "<?xm" in ASCII etc.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001618 while (true) {
1619 int i = is.read();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001620 if (i == -1) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001621 break;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001622 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001623 buffer[limit++] = (char) i;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001624 if (i == '>') {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001625 String s = new String(buffer, 0, limit);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001626 int i0 = s.indexOf("encoding");
1627 if (i0 != -1) {
1628 while (s.charAt(i0) != '"'
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001629 && s.charAt(i0) != '\'') {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001630 i0++;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001631 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001632 char deli = s.charAt(i0++);
1633 int i1 = s.indexOf(deli, i0);
1634 enc = s.substring(i0, i1);
1635 }
1636 break;
1637 }
1638 }
1639
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001640 default:
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001641 // handle a byte order mark followed by something other than <?
1642 if ((firstFourBytes & 0x0ffff0000) == 0x0FEFF0000) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001643 enc = "UTF-16BE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001644 buffer[0] = (char) ((buffer[2] << 8) | buffer[3]);
1645 limit = 1;
1646 } else if ((firstFourBytes & 0x0ffff0000) == 0x0fffe0000) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001647 enc = "UTF-16LE";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001648 buffer[0] = (char) ((buffer[3] << 8) | buffer[2]);
1649 limit = 1;
1650 } else if ((firstFourBytes & 0x0ffffff00) == 0x0EFBBBF00) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001651 enc = "UTF-8";
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001652 buffer[0] = buffer[3];
1653 limit = 1;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001654 }
1655 }
1656 }
1657 }
1658
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001659 if (enc == null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001660 enc = "UTF-8";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001661 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001662
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001663 int sc = limit;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001664 setInput(new InputStreamReader(is, enc));
1665 encoding = _enc;
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001666 limit = sc;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001667 } catch (Exception e) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001668 throw new XmlPullParserException("Invalid stream or encoding: " + e, this, e);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001669 }
1670 }
1671
Jesse Wilson086fd022010-11-29 16:01:57 -08001672 public void close() throws IOException {
1673 if (reader != null) {
1674 reader.close();
1675 }
1676 }
1677
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001678 public boolean getFeature(String feature) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001679 if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001680 return processNsp;
Jesse Wilson76c85882010-11-24 18:26:03 -08001681 } else if (FEATURE_RELAXED.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001682 return relaxed;
Jesse Wilson76c85882010-11-24 18:26:03 -08001683 } else if (FEATURE_PROCESS_DOCDECL.equals(feature)) {
1684 return processDocDecl;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001685 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001686 return false;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001687 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001688 }
1689
1690 public String getInputEncoding() {
1691 return encoding;
1692 }
1693
1694 public void defineEntityReplacementText(String entity, String value)
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001695 throws XmlPullParserException {
Jesse Wilson76c85882010-11-24 18:26:03 -08001696 if (processDocDecl) {
1697 throw new IllegalStateException(
1698 "Entity replacement text may not be defined with DOCTYPE processing enabled.");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001699 }
Jesse Wilson76c85882010-11-24 18:26:03 -08001700 if (reader == null) {
1701 throw new IllegalStateException(
1702 "Entity replacement text must be defined after setInput()");
1703 }
1704 if (documentEntities == null) {
1705 documentEntities = new HashMap<String, char[]>();
1706 }
1707 documentEntities.put(entity, value.toCharArray());
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001708 }
1709
1710 public Object getProperty(String property) {
Jesse Wilson76c85882010-11-24 18:26:03 -08001711 if (property.equals(PROPERTY_XMLDECL_VERSION)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001712 return version;
Jesse Wilson76c85882010-11-24 18:26:03 -08001713 } else if (property.equals(PROPERTY_XMLDECL_STANDALONE)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001714 return standalone;
Jesse Wilson76c85882010-11-24 18:26:03 -08001715 } else if (property.equals(PROPERTY_LOCATION)) {
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001716 return location != null ? location : reader.toString();
Jesse Wilson76c85882010-11-24 18:26:03 -08001717 } else {
1718 return null;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001719 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001720 }
1721
Jesse Wilson086fd022010-11-29 16:01:57 -08001722 /**
1723 * Returns the root element's name if it was declared in the DTD. This
1724 * equals the first tag's name for valid documents.
1725 */
1726 public String getRootElementName() {
1727 return rootElementName;
1728 }
1729
1730 /**
1731 * Returns the document's system ID if it was declared. This is typically a
1732 * string like {@code http://www.w3.org/TR/html4/strict.dtd}.
1733 */
1734 public String getSystemId() {
1735 return systemId;
1736 }
1737
1738 /**
1739 * Returns the document's public ID if it was declared. This is typically a
1740 * string like {@code -//W3C//DTD HTML 4.01//EN}.
1741 */
1742 public String getPublicId() {
1743 return publicId;
1744 }
1745
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001746 public int getNamespaceCount(int depth) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001747 if (depth > this.depth) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001748 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001749 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001750 return nspCounts[depth];
1751 }
1752
1753 public String getNamespacePrefix(int pos) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001754 return nspStack[pos * 2];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001755 }
1756
1757 public String getNamespaceUri(int pos) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001758 return nspStack[(pos * 2) + 1];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001759 }
1760
1761 public String getNamespace(String prefix) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001762 if ("xml".equals(prefix)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001763 return "http://www.w3.org/XML/1998/namespace";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001764 }
1765 if ("xmlns".equals(prefix)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001766 return "http://www.w3.org/2000/xmlns/";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001767 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001768
1769 for (int i = (getNamespaceCount(depth) << 1) - 2; i >= 0; i -= 2) {
1770 if (prefix == null) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001771 if (nspStack[i] == null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001772 return nspStack[i + 1];
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001773 }
1774 } else if (prefix.equals(nspStack[i])) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001775 return nspStack[i + 1];
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001776 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001777 }
1778 return null;
1779 }
1780
1781 public int getDepth() {
1782 return depth;
1783 }
1784
1785 public String getPositionDescription() {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001786 StringBuilder buf = new StringBuilder(type < TYPES.length ? TYPES[type] : "unknown");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001787 buf.append(' ');
1788
1789 if (type == START_TAG || type == END_TAG) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001790 if (degenerated) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001791 buf.append("(empty) ");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001792 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001793 buf.append('<');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001794 if (type == END_TAG) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001795 buf.append('/');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001796 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001797
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001798 if (prefix != null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001799 buf.append("{" + namespace + "}" + prefix + ":");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001800 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001801 buf.append(name);
1802
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001803 int cnt = attributeCount * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001804 for (int i = 0; i < cnt; i += 4) {
1805 buf.append(' ');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001806 if (attributes[i + 1] != null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001807 buf.append("{" + attributes[i] + "}" + attributes[i + 1] + ":");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001808 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001809 buf.append(attributes[i + 2] + "='" + attributes[i + 3] + "'");
1810 }
1811
1812 buf.append('>');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001813 } else if (type == IGNORABLE_WHITESPACE) {
1814 ;
1815 } else if (type != TEXT) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001816 buf.append(getText());
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001817 } else if (isWhitespace) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001818 buf.append("(whitespace)");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001819 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001820 String text = getText();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001821 if (text.length() > 16) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001822 text = text.substring(0, 16) + "...";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001823 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001824 buf.append(text);
1825 }
1826
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001827 buf.append("@" + getLineNumber() + ":" + getColumnNumber());
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001828 if (location != null) {
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001829 buf.append(" in ");
1830 buf.append(location);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001831 } else if (reader != null) {
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001832 buf.append(" in ");
1833 buf.append(reader.toString());
1834 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001835 return buf.toString();
1836 }
1837
1838 public int getLineNumber() {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001839 int result = bufferStartLine;
1840 for (int i = 0; i < position; i++) {
1841 if (buffer[i] == '\n') {
1842 result++;
1843 }
1844 }
1845 return result + 1; // the first line is '1'
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001846 }
1847
1848 public int getColumnNumber() {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001849 int result = bufferStartColumn;
1850 for (int i = 0; i < position; i++) {
1851 if (buffer[i] == '\n') {
1852 result = 0;
1853 } else {
1854 result++;
1855 }
1856 }
1857 return result + 1; // the first column is '1'
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001858 }
1859
1860 public boolean isWhitespace() throws XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001861 if (type != TEXT && type != IGNORABLE_WHITESPACE && type != CDSECT) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001862 throw new XmlPullParserException(ILLEGAL_TYPE, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001863 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001864 return isWhitespace;
1865 }
1866
1867 public String getText() {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001868 if (type < TEXT || (type == ENTITY_REF && unresolved)) {
1869 return null;
1870 } else if (text == null) {
1871 return "";
1872 } else {
1873 return text;
1874 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001875 }
1876
1877 public char[] getTextCharacters(int[] poslen) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001878 String text = getText();
1879 if (text == null) {
1880 poslen[0] = -1;
1881 poslen[1] = -1;
1882 return null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001883 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001884 char[] result = text.toCharArray();
1885 poslen[0] = 0;
1886 poslen[1] = result.length;
1887 return result;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001888 }
1889
1890 public String getNamespace() {
1891 return namespace;
1892 }
1893
1894 public String getName() {
1895 return name;
1896 }
1897
1898 public String getPrefix() {
1899 return prefix;
1900 }
1901
1902 public boolean isEmptyElementTag() throws XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001903 if (type != START_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001904 throw new XmlPullParserException(ILLEGAL_TYPE, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001905 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001906 return degenerated;
1907 }
1908
1909 public int getAttributeCount() {
1910 return attributeCount;
1911 }
1912
1913 public String getAttributeType(int index) {
1914 return "CDATA";
1915 }
1916
1917 public boolean isAttributeDefault(int index) {
1918 return false;
1919 }
1920
1921 public String getAttributeNamespace(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001922 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001923 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001924 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001925 return attributes[index * 4];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001926 }
1927
1928 public String getAttributeName(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001929 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001930 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001931 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001932 return attributes[(index * 4) + 2];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001933 }
1934
1935 public String getAttributePrefix(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001936 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001937 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001938 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001939 return attributes[(index * 4) + 1];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001940 }
1941
1942 public String getAttributeValue(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001943 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001944 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001945 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001946 return attributes[(index * 4) + 3];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001947 }
1948
1949 public String getAttributeValue(String namespace, String name) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001950 for (int i = (attributeCount * 4) - 4; i >= 0; i -= 4) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001951 if (attributes[i + 2].equals(name)
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001952 && (namespace == null || attributes[i].equals(namespace))) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001953 return attributes[i + 3];
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001954 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001955 }
1956
1957 return null;
1958 }
1959
1960 public int getEventType() throws XmlPullParserException {
1961 return type;
1962 }
1963
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001964 // utility methods to make XML parsing easier ...
1965
1966 public int nextTag() throws XmlPullParserException, IOException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001967 next();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001968 if (type == TEXT && isWhitespace) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001969 next();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001970 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001971
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001972 if (type != END_TAG && type != START_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001973 throw new XmlPullParserException("unexpected type", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001974 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001975
1976 return type;
1977 }
1978
1979 public void require(int type, String namespace, String name)
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001980 throws XmlPullParserException, IOException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001981
1982 if (type != this.type
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001983 || (namespace != null && !namespace.equals(getNamespace()))
1984 || (name != null && !name.equals(getName()))) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001985 throw new XmlPullParserException(
1986 "expected: " + TYPES[type] + " {" + namespace + "}" + name, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001987 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001988 }
1989
1990 public String nextText() throws XmlPullParserException, IOException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001991 if (type != START_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001992 throw new XmlPullParserException("precondition: START_TAG", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001993 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001994
1995 next();
1996
1997 String result;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001998 if (type == TEXT) {
1999 result = getText();
2000 next();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002001 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002002 result = "";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002003 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002004
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002005 if (type != END_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002006 throw new XmlPullParserException("END_TAG expected", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002007 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002008
2009 return result;
2010 }
2011
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002012 public void setFeature(String feature, boolean value) throws XmlPullParserException {
2013 if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002014 processNsp = value;
Jesse Wilson76c85882010-11-24 18:26:03 -08002015 } else if (XmlPullParser.FEATURE_PROCESS_DOCDECL.equals(feature)) {
2016 processDocDecl = value;
2017 } else if (FEATURE_RELAXED.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002018 relaxed = value;
Elliott Hughesd21d78f2010-05-13 11:32:57 -07002019 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002020 throw new XmlPullParserException("unsupported feature: " + feature, this, null);
Elliott Hughesd21d78f2010-05-13 11:32:57 -07002021 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002022 }
2023
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002024 public void setProperty(String property, Object value) throws XmlPullParserException {
Jesse Wilson76c85882010-11-24 18:26:03 -08002025 if (property.equals(PROPERTY_LOCATION)) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002026 location = String.valueOf(value);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002027 } else {
2028 throw new XmlPullParserException("unsupported property: " + property);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002029 }
2030 }
Jesse Wilson76c85882010-11-24 18:26:03 -08002031
2032 /**
2033 * A chain of buffers containing XML content. Each content source contains
2034 * the parser's primary read buffer or the characters of entities actively
2035 * being parsed.
2036 *
2037 * <p>For example, note the buffers needed to parse this document:
2038 * <pre> {@code
2039 * <!DOCTYPE foo [
2040 * <!ENTITY baz "ghi">
2041 * <!ENTITY bar "def &baz; jkl">
2042 * ]>
2043 * <foo>abc &bar; mno</foo>
2044 * }</pre>
2045 *
2046 * <p>Things get interesting when the bar entity is encountered. At that
2047 * point two buffers are active:
2048 * <ol>
2049 * <li>The value for the bar entity, containing {@code "def &baz; jkl"}
2050 * <li>The parser's primary read buffer, containing {@code " mno</foo>"}
2051 * </ol>
2052 * <p>The parser will return the characters {@code "def "} from the bar
2053 * entity's buffer, and then it will encounter the baz entity. To handle
2054 * that, three buffers will be active:
2055 * <ol>
2056 * <li>The value for the baz entity, containing {@code "ghi"}
2057 * <li>The remaining value for the bar entity, containing {@code " jkl"}
2058 * <li>The parser's primary read buffer, containing {@code " mno</foo>"}
2059 * </ol>
2060 * <p>The parser will then return the characters {@code ghi jkl mno} in that
2061 * sequence by reading each buffer in sequence.
2062 */
2063 static class ContentSource {
2064 private final ContentSource next;
2065 private final char[] buffer;
2066 private final int position;
2067 private final int limit;
2068 ContentSource(ContentSource next, char[] buffer, int position, int limit) {
2069 this.next = next;
2070 this.buffer = buffer;
2071 this.position = position;
2072 this.limit = limit;
2073 }
2074 }
2075
2076 /**
2077 * Prepends the characters of {@code newBuffer} to be read before the
2078 * current buffer.
2079 */
2080 private void pushContentSource(char[] newBuffer) {
2081 nextContentSource = new ContentSource(nextContentSource, buffer, position, limit);
2082 buffer = newBuffer;
2083 position = 0;
2084 limit = newBuffer.length;
2085 }
2086
2087 /**
2088 * Replaces the current exhausted buffer with the next buffer in the chain.
2089 */
2090 private void popContentSource() {
2091 buffer = nextContentSource.buffer;
2092 position = nextContentSource.position;
2093 limit = nextContentSource.limit;
2094 nextContentSource = nextContentSource.next;
2095 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002096}