blob: 2be8d36235ef728d3132866461be1281ea8fa490 [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
Elliott Hughes6aa068b2011-02-17 18:00:29 -080041 private static final String PROPERTY_XMLDECL_VERSION
Jesse Wilson76c85882010-11-24 18:26:03 -080042 = "http://xmlpull.org/v1/doc/properties.html#xmldecl-version";
Elliott Hughes6aa068b2011-02-17 18:00:29 -080043 private static final String PROPERTY_XMLDECL_STANDALONE
Jesse Wilson76c85882010-11-24 18:26:03 -080044 = "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone";
Elliott Hughes6aa068b2011-02-17 18:00:29 -080045 private static final String PROPERTY_LOCATION = "http://xmlpull.org/v1/doc/properties.html#location";
Jesse Wilson76c85882010-11-24 18:26:03 -080046 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 /**
Jesse Wilson38e84b82010-12-20 11:51:41 -0800113 * If non-null, the contents of the read buffer must be copied into this
114 * string builder before the read buffer is overwritten. This is used to
115 * capture the raw DTD text while parsing the DTD.
116 */
117 private StringBuilder bufferCapture;
118
119 /**
Jesse Wilson76c85882010-11-24 18:26:03 -0800120 * Entities defined in or for this document. This map is created lazily.
121 */
122 private Map<String, char[]> documentEntities;
123
124 /**
125 * Default attributes in this document. The outer map's key is the element
126 * name; the inner map's key is the attribute name. Both keys should be
127 * without namespace adjustments. This map is created lazily.
128 */
129 private Map<String, Map<String, String>> defaultAttributes;
130
131
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800132 private int depth;
133 private String[] elementStack = new String[16];
134 private String[] nspStack = new String[8];
135 private int[] nspCounts = new int[4];
136
137 // source
138
139 private Reader reader;
140 private String encoding;
Jesse Wilson76c85882010-11-24 18:26:03 -0800141 private ContentSource nextContentSource;
142 private char[] buffer = new char[8192];
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700143 private int position = 0;
144 private int limit = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800145
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700146 /*
147 * Track the number of newlines and columns preceding the current buffer. To
148 * compute the line and column of a position in the buffer, compute the line
149 * and column in the buffer and add the preceding values.
150 */
151 private int bufferStartLine;
152 private int bufferStartColumn;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800153
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700154 // the current token
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800155
156 private int type;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800157 private boolean isWhitespace;
158 private String namespace;
159 private String prefix;
160 private String name;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700161 private String text;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800162
163 private boolean degenerated;
164 private int attributeCount;
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800165
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700166 /*
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800167 * The current element's attributes arranged in groups of 4:
168 * i + 0 = attribute namespace URI
169 * i + 1 = attribute namespace prefix
170 * i + 2 = attribute qualified name (may contain ":", as in "html:h1")
171 * i + 3 = attribute value
172 */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800173 private String[] attributes = new String[16];
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700174
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800175 private String error;
176
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800177 private boolean unresolved;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800178
Jesse Wilsona78c2aa2010-11-14 12:01:08 -0800179 public final StringPool stringPool = new StringPool();
180
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800181 /**
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700182 * Retains namespace attributes like {@code xmlns="http://foo"} or {@code xmlns:foo="http:foo"}
183 * in pulled elements. Most applications will only be interested in the effective namespaces of
184 * their elements, so these attributes aren't useful. But for structure preserving wrappers like
185 * DOM, it is necessary to keep the namespace data around.
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800186 */
187 public void keepNamespaceAttributes() {
188 this.keepNamespaceAttributes = true;
189 }
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800190
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700191 private boolean adjustNsp() throws XmlPullParserException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800192 boolean any = false;
193
194 for (int i = 0; i < attributeCount << 2; i += 4) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800195 String attrName = attributes[i + 2];
196 int cut = attrName.indexOf(':');
197 String prefix;
198
199 if (cut != -1) {
200 prefix = attrName.substring(0, cut);
201 attrName = attrName.substring(cut + 1);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700202 } else if (attrName.equals("xmlns")) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800203 prefix = attrName;
204 attrName = null;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700205 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800206 continue;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700207 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800208
209 if (!prefix.equals("xmlns")) {
210 any = true;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700211 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800212 int j = (nspCounts[depth]++) << 1;
213
214 nspStack = ensureCapacity(nspStack, j + 2);
215 nspStack[j] = attrName;
216 nspStack[j + 1] = attributes[i + 3];
217
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700218 if (attrName != null && attributes[i + 3].isEmpty()) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700219 checkRelaxed("illegal empty namespace");
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700220 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800221
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800222 if (keepNamespaceAttributes) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700223 // explicitly set the namespace for unprefixed attributes
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800224 // such as xmlns="http://foo"
225 attributes[i] = "http://www.w3.org/2000/xmlns/";
226 any = true;
227 } else {
228 System.arraycopy(
229 attributes,
230 i + 4,
231 attributes,
232 i,
233 ((--attributeCount) << 2) - i);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800234
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800235 i -= 4;
236 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800237 }
238 }
239
240 if (any) {
241 for (int i = (attributeCount << 2) - 4; i >= 0; i -= 4) {
242
243 String attrName = attributes[i + 2];
244 int cut = attrName.indexOf(':');
245
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700246 if (cut == 0 && !relaxed) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800247 throw new RuntimeException(
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700248 "illegal attribute name: " + attrName + " at " + this);
249 } else if (cut != -1) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800250 String attrPrefix = attrName.substring(0, cut);
251
252 attrName = attrName.substring(cut + 1);
253
254 String attrNs = getNamespace(attrPrefix);
255
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700256 if (attrNs == null && !relaxed) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800257 throw new RuntimeException(
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700258 "Undefined Prefix: " + attrPrefix + " in " + this);
259 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800260
261 attributes[i] = attrNs;
262 attributes[i + 1] = attrPrefix;
263 attributes[i + 2] = attrName;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800264 }
265 }
266 }
267
268 int cut = name.indexOf(':');
269
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700270 if (cut == 0) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700271 checkRelaxed("illegal tag name: " + name);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700272 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800273
274 if (cut != -1) {
275 prefix = name.substring(0, cut);
276 name = name.substring(cut + 1);
277 }
278
279 this.namespace = getNamespace(prefix);
280
281 if (this.namespace == null) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700282 if (prefix != null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700283 checkRelaxed("undefined prefix: " + prefix);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700284 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800285 this.namespace = NO_NAMESPACE;
286 }
287
288 return any;
289 }
290
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700291 private String[] ensureCapacity(String[] arr, int required) {
292 if (arr.length >= required) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800293 return arr;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700294 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800295 String[] bigger = new String[required + 16];
296 System.arraycopy(arr, 0, bigger, 0, arr.length);
297 return bigger;
298 }
299
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700300 private void checkRelaxed(String errorMessage) throws XmlPullParserException {
301 if (!relaxed) {
302 throw new XmlPullParserException(errorMessage, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700303 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700304 if (error == null) {
305 error = "Error: " + errorMessage;
306 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800307 }
308
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800309 public int next() throws XmlPullParserException, IOException {
310 return next(false);
311 }
312
313 public int nextToken() throws XmlPullParserException, IOException {
314 return next(true);
315 }
316
317 private int next(boolean justOneToken) throws IOException, XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700318 if (reader == null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700319 throw new XmlPullParserException("setInput() must be called first.", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700320 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800321
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700322 if (type == END_TAG) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800323 depth--;
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700324 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800325
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800326 // degenerated needs to be handled before error because of possible
327 // processor expectations(!)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800328
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800329 if (degenerated) {
330 degenerated = false;
331 type = END_TAG;
332 return type;
333 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800334
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800335 if (error != null) {
336 if (justOneToken) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700337 text = error;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800338 type = COMMENT;
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800339 error = null;
340 return type;
341 } else {
342 error = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800343 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800344 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800345
Jesse Wilson7fac0472010-11-23 17:02:32 -0800346 type = peekType(false);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800347
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800348 if (type == XML_DECLARATION) {
349 readXmlDeclaration();
Jesse Wilson7fac0472010-11-23 17:02:32 -0800350 type = peekType(false);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800351 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800352
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800353 text = null;
354 isWhitespace = true;
355 prefix = null;
356 name = null;
357 namespace = null;
358 attributeCount = -1;
Jesse Wilson086fd022010-11-29 16:01:57 -0800359 boolean throwOnResolveFailure = !justOneToken;
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800360
361 while (true) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800362 switch (type) {
363
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800364 /*
365 * Return immediately after encountering a start tag, end tag, or
366 * the end of the document.
367 */
368 case START_TAG:
Jesse Wilson086fd022010-11-29 16:01:57 -0800369 parseStartTag(false, throwOnResolveFailure);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800370 return type;
371 case END_TAG:
372 readEndTag();
373 return type;
374 case END_DOCUMENT:
375 return type;
376
377 /*
378 * Return after any text token when we're looking for a single
379 * token. Otherwise concatenate all text between tags.
380 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700381 case ENTITY_REF:
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800382 if (justOneToken) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700383 StringBuilder entityTextBuilder = new StringBuilder();
Jesse Wilson086fd022010-11-29 16:01:57 -0800384 readEntity(entityTextBuilder, true, throwOnResolveFailure, ValueContext.TEXT);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700385 text = entityTextBuilder.toString();
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800386 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700387 }
388 // fall-through
389 case TEXT:
Jesse Wilson086fd022010-11-29 16:01:57 -0800390 text = readValue('<', !justOneToken, throwOnResolveFailure, ValueContext.TEXT);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700391 if (depth == 0 && isWhitespace) {
392 type = IGNORABLE_WHITESPACE;
393 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800394 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700395 case CDSECT:
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700396 read(START_CDATA);
397 text = readUntil(END_CDATA, true);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800398 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700399
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800400 /*
401 * Comments, processing instructions and declarations are returned
402 * when we're looking for a single token. Otherwise they're skipped.
403 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700404 case COMMENT:
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800405 String commentText = readComment(justOneToken);
406 if (justOneToken) {
407 text = commentText;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700408 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800409 break;
410 case PROCESSING_INSTRUCTION:
411 read(START_PROCESSING_INSTRUCTION);
412 String processingInstruction = readUntil(END_PROCESSING_INSTRUCTION, justOneToken);
413 if (justOneToken) {
414 text = processingInstruction;
415 }
416 break;
417 case DOCDECL:
Jesse Wilson38e84b82010-12-20 11:51:41 -0800418 readDoctype(justOneToken);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800419 break;
Jesse Wilson7fac0472010-11-23 17:02:32 -0800420
421 default:
422 throw new XmlPullParserException("Unexpected token", this, null);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800423 }
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800424
Jesse Wilson995a3842011-02-14 13:04:50 -0800425 if (depth == 0 && (type == ENTITY_REF || type == TEXT || type == CDSECT)) {
426 throw new XmlPullParserException("Unexpected token", this, null);
427 }
428
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800429 if (justOneToken) {
430 return type;
431 }
432
433 if (type == IGNORABLE_WHITESPACE) {
434 text = null;
435 }
436
437 /*
438 * We've read all that we can of a non-empty text block. Always
439 * report this as text, even if it was a CDATA block or entity
440 * reference.
441 */
Jesse Wilson7fac0472010-11-23 17:02:32 -0800442 int peek = peekType(false);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800443 if (text != null && !text.isEmpty() && peek < TEXT) {
444 type = TEXT;
445 return type;
446 }
447
448 type = peek;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800449 }
450 }
451
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700452 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700453 * Reads text until the specified delimiter is encountered. Consumes the
454 * text and the delimiter.
455 *
456 * @param returnText true to return the read text excluding the delimiter;
457 * false to return null.
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700458 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700459 private String readUntil(char[] delimiter, boolean returnText)
460 throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700461 int start = position;
462 StringBuilder result = null;
463
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800464 if (returnText && text != null) {
465 result = new StringBuilder();
466 result.append(text);
467 }
468
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700469 search:
470 while (true) {
471 if (position + delimiter.length >= limit) {
472 if (start < position && returnText) {
473 if (result == null) {
474 result = new StringBuilder();
475 }
476 result.append(buffer, start, position - start);
477 }
478 if (!fillBuffer(delimiter.length)) {
479 checkRelaxed(UNEXPECTED_EOF);
480 type = COMMENT;
481 return null;
482 }
483 start = position;
484 }
485
486 // TODO: replace with Arrays.equals(buffer, position, delimiter, 0, delimiter.length)
487 // when the VM has better method inlining
488 for (int i = 0; i < delimiter.length; i++) {
489 if (buffer[position + i] != delimiter[i]) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700490 position++;
491 continue search;
492 }
493 }
494
495 break;
496 }
497
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700498 int end = position;
499 position += delimiter.length;
500
501 if (!returnText) {
502 return null;
503 } else if (result == null) {
Jesse Wilsona78c2aa2010-11-14 12:01:08 -0800504 return stringPool.get(buffer, start, end - start);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700505 } else {
506 result.append(buffer, start, end - start);
507 return result.toString();
508 }
509 }
510
511 /**
512 * Returns true if an XML declaration was read.
513 */
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800514 private void readXmlDeclaration() throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700515 if (bufferStartLine != 0 || bufferStartColumn != 0 || position != 0) {
516 checkRelaxed("processing instructions must not start with xml");
517 }
518
519 read(START_PROCESSING_INSTRUCTION);
Jesse Wilson086fd022010-11-29 16:01:57 -0800520 parseStartTag(true, true);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700521
522 if (attributeCount < 1 || !"version".equals(attributes[2])) {
523 checkRelaxed("version expected");
524 }
525
526 version = attributes[3];
527
528 int pos = 1;
529
530 if (pos < attributeCount && "encoding".equals(attributes[2 + 4])) {
531 encoding = attributes[3 + 4];
532 pos++;
533 }
534
535 if (pos < attributeCount && "standalone".equals(attributes[4 * pos + 2])) {
536 String st = attributes[3 + 4 * pos];
537 if ("yes".equals(st)) {
538 standalone = Boolean.TRUE;
539 } else if ("no".equals(st)) {
540 standalone = Boolean.FALSE;
541 } else {
542 checkRelaxed("illegal standalone value: " + st);
543 }
544 pos++;
545 }
546
547 if (pos != attributeCount) {
548 checkRelaxed("unexpected attributes in XML declaration");
549 }
550
551 isWhitespace = true;
552 text = null;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700553 }
554
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800555 private String readComment(boolean returnText) throws IOException, XmlPullParserException {
556 read(START_COMMENT);
557
558 if (relaxed) {
559 return readUntil(END_COMMENT, returnText);
560 }
561
562 String commentText = readUntil(COMMENT_DOUBLE_DASH, returnText);
563 if (peekCharacter() != '>') {
564 throw new XmlPullParserException("Comments may not contain --", this, null);
565 }
566 position++;
567 return commentText;
568 }
569
Jesse Wilson7fac0472010-11-23 17:02:32 -0800570 /**
571 * Read the document's DTD. Although this parser is non-validating, the DTD
572 * must be parsed to capture entity values and default attribute values.
573 */
Jesse Wilson38e84b82010-12-20 11:51:41 -0800574 private void readDoctype(boolean saveDtdText) throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700575 read(START_DOCTYPE);
Jesse Wilson38e84b82010-12-20 11:51:41 -0800576
577 int startPosition = -1;
578 if (saveDtdText) {
579 bufferCapture = new StringBuilder();
580 startPosition = position;
Jesse Wilson7fac0472010-11-23 17:02:32 -0800581 }
Jesse Wilson38e84b82010-12-20 11:51:41 -0800582 try {
583 skip();
584 rootElementName = readName();
585 readExternalId(true, true);
586 skip();
587 if (peekCharacter() == '[') {
588 readInternalSubset();
589 }
590 skip();
591 } finally {
592 if (saveDtdText) {
593 bufferCapture.append(buffer, 0, position);
594 bufferCapture.delete(0, startPosition);
595 text = bufferCapture.toString();
596 bufferCapture = null;
597 }
598 }
599
Jesse Wilson7fac0472010-11-23 17:02:32 -0800600 read('>');
601 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700602
Jesse Wilson7fac0472010-11-23 17:02:32 -0800603 /**
604 * Reads an external ID of one of these two forms:
605 * SYSTEM "quoted system name"
606 * PUBLIC "quoted public id" "quoted system name"
607 *
608 * If the system name is not required, this also supports lone public IDs of
609 * this form:
610 * PUBLIC "quoted public id"
611 *
612 * Returns true if any ID was read.
613 */
Jesse Wilson086fd022010-11-29 16:01:57 -0800614 private boolean readExternalId(boolean requireSystemName, boolean assignFields)
Jesse Wilson7fac0472010-11-23 17:02:32 -0800615 throws IOException, XmlPullParserException {
616 skip();
617 int c = peekCharacter();
618
619 if (c == 'S') {
620 read(SYSTEM);
621 } else if (c == 'P') {
622 read(PUBLIC);
623 skip();
Jesse Wilson086fd022010-11-29 16:01:57 -0800624 if (assignFields) {
625 publicId = readQuotedId(true);
626 } else {
627 readQuotedId(false);
628 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800629 } else {
630 return false;
631 }
632
633 skip();
634
635 if (!requireSystemName) {
636 int delimiter = peekCharacter();
637 if (delimiter != '"' && delimiter != '\'') {
638 return true; // no system name!
639 }
640 }
641
Jesse Wilson086fd022010-11-29 16:01:57 -0800642 if (assignFields) {
643 systemId = readQuotedId(true);
644 } else {
645 readQuotedId(false);
646 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800647 return true;
648 }
649
Jesse Wilson086fd022010-11-29 16:01:57 -0800650 private static final char[] SINGLE_QUOTE = new char[] { '\'' };
651 private static final char[] DOUBLE_QUOTE = new char[] { '"' };
652
Jesse Wilson7fac0472010-11-23 17:02:32 -0800653 /**
654 * Reads a quoted string, performing no entity escaping of the contents.
655 */
Jesse Wilson086fd022010-11-29 16:01:57 -0800656 private String readQuotedId(boolean returnText) throws IOException, XmlPullParserException {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800657 int quote = peekCharacter();
Jesse Wilson086fd022010-11-29 16:01:57 -0800658 char[] delimiter;
659 if (quote == '"') {
660 delimiter = DOUBLE_QUOTE;
661 } else if (quote == '\'') {
662 delimiter = SINGLE_QUOTE;
663 } else {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800664 throw new XmlPullParserException("Expected a quoted string", this, null);
665 }
666 position++;
Jesse Wilson086fd022010-11-29 16:01:57 -0800667 return readUntil(delimiter, returnText);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800668 }
669
670 private void readInternalSubset() throws IOException, XmlPullParserException {
671 read('[');
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800672
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800673 while (true) {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800674 skip();
675 if (peekCharacter() == ']') {
676 position++;
677 return;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800678 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700679
Jesse Wilson7fac0472010-11-23 17:02:32 -0800680 int declarationType = peekType(true);
681 switch (declarationType) {
682 case ELEMENTDECL:
683 readElementDeclaration();
684 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700685
Jesse Wilson7fac0472010-11-23 17:02:32 -0800686 case ATTLISTDECL:
687 readAttributeListDeclaration();
688 break;
689
690 case ENTITYDECL:
691 readEntityDeclaration();
692 break;
693
694 case NOTATIONDECL:
695 readNotationDeclaration();
696 break;
697
698 case PROCESSING_INSTRUCTION:
699 read(START_PROCESSING_INSTRUCTION);
700 readUntil(END_PROCESSING_INSTRUCTION, false);
701 break;
702
703 case COMMENT:
704 readComment(false);
705 break;
706
707 case PARAMETER_ENTITY_REF:
708 throw new XmlPullParserException(
709 "Parameter entity references are not supported", this, null);
710
711 default:
712 throw new XmlPullParserException("Unexpected token", this, null);
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700713 }
714 }
Jesse Wilson7fac0472010-11-23 17:02:32 -0800715 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700716
Jesse Wilson7fac0472010-11-23 17:02:32 -0800717 /**
718 * Read an element declaration. This contains a name and a content spec.
719 * <!ELEMENT foo EMPTY >
720 * <!ELEMENT foo (bar?,(baz|quux)) >
721 * <!ELEMENT foo (#PCDATA|bar)* >
722 */
723 private void readElementDeclaration() throws IOException, XmlPullParserException {
724 read(START_ELEMENT);
725 skip();
726 readName();
727 readContentSpec();
728 skip();
729 read('>');
730 }
731
732 /**
733 * Read an element content spec. This is a regular expression-like pattern
734 * of names or other content specs. The following operators are supported:
735 * sequence: (a,b,c)
736 * choice: (a|b|c)
737 * optional: a?
738 * one or more: a+
739 * any number: a*
740 *
741 * The special name '#PCDATA' is permitted but only if it is the first
742 * element of the first group:
743 * (#PCDATA|a|b)
744 *
745 * The top-level element must be either a choice, a sequence, or one of the
746 * special names EMPTY and ANY.
747 */
748 private void readContentSpec() throws IOException, XmlPullParserException {
749 // this implementation is very lenient; it scans for balanced parens only
750 skip();
751 int c = peekCharacter();
752 if (c == '(') {
753 int depth = 0;
754 do {
755 if (c == '(') {
756 depth++;
757 } else if (c == ')') {
758 depth--;
759 }
760 position++;
761 c = peekCharacter();
762 } while (depth > 0);
763
764 if (c == '*' || c == '?' || c == '+') {
765 position++;
766 }
767 } else if (c == EMPTY[0]) {
768 read(EMPTY);
769 } else if (c == ANY[0]) {
770 read(ANY);
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -0800771 } else {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800772 throw new XmlPullParserException("Expected element content spec", this, null);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800773 }
774 }
775
Jesse Wilson7fac0472010-11-23 17:02:32 -0800776 /**
777 * Reads an attribute list declaration such as the following:
778 * <!ATTLIST foo
779 * bar CDATA #IMPLIED
780 * quux (a|b|c) "c"
781 * baz NOTATION (a|b|c) #FIXED "c">
782 *
783 * Each attribute has a name, type and default.
784 *
785 * Types are one of the built-in types (CDATA, ID, IDREF, IDREFS, ENTITY,
786 * ENTITIES, NMTOKEN, or NMTOKENS), an enumerated type "(list|of|options)"
787 * or NOTATION followed by an enumerated type.
788 *
789 * The default is either #REQUIRED, #IMPLIED, #FIXED, a quoted value, or
790 * #FIXED with a quoted value.
791 */
792 private void readAttributeListDeclaration() throws IOException, XmlPullParserException {
793 read(START_ATTLIST);
794 skip();
795 String elementName = readName();
796
797 while (true) {
798 skip();
799 int c = peekCharacter();
800 if (c == '>') {
801 position++;
802 return;
803 }
804
805 // attribute name
806 String attributeName = readName();
807
808 // attribute type
809 skip();
810 if (position + 1 >= limit && !fillBuffer(2)) {
811 throw new XmlPullParserException("Malformed attribute list", this, null);
812 }
813 if (buffer[position] == NOTATION[0] && buffer[position + 1] == NOTATION[1]) {
814 read(NOTATION);
815 skip();
816 }
817 c = peekCharacter();
818 if (c == '(') {
819 position++;
820 while (true) {
821 skip();
822 readName();
823 skip();
824 c = peekCharacter();
825 if (c == ')') {
826 position++;
827 break;
828 } else if (c == '|') {
829 position++;
830 } else {
831 throw new XmlPullParserException("Malformed attribute type", this, null);
832 }
833 }
834 } else {
835 readName();
836 }
837
838 // default value
839 skip();
840 c = peekCharacter();
841 if (c == '#') {
842 position++;
843 c = peekCharacter();
844 if (c == 'R') {
845 read(REQUIRED);
846 } else if (c == 'I') {
847 read(IMPLIED);
848 } else if (c == 'F') {
849 read(FIXED);
850 } else {
851 throw new XmlPullParserException("Malformed attribute type", this, null);
852 }
853 skip();
854 c = peekCharacter();
855 }
856 if (c == '"' || c == '\'') {
857 position++;
858 // TODO: does this do escaping correctly?
Jesse Wilson086fd022010-11-29 16:01:57 -0800859 String value = readValue((char) c, true, true, ValueContext.ATTRIBUTE);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800860 position++;
861 defineAttributeDefault(elementName, attributeName, value);
862 }
863 }
864 }
865
866 private void defineAttributeDefault(String elementName, String attributeName, String value) {
Jesse Wilson76c85882010-11-24 18:26:03 -0800867 if (defaultAttributes == null) {
868 defaultAttributes = new HashMap<String, Map<String, String>>();
869 }
870 Map<String, String> elementAttributes = defaultAttributes.get(elementName);
871 if (elementAttributes == null) {
872 elementAttributes = new HashMap<String, String>();
873 defaultAttributes.put(elementName, elementAttributes);
874 }
875 elementAttributes.put(attributeName, value);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800876 }
877
878 /**
879 * Read an entity declaration. The value of internal entities are inline:
880 * <!ENTITY foo "bar">
881 *
882 * The values of external entities must be retrieved by URL or path:
883 * <!ENTITY foo SYSTEM "http://host/file">
884 * <!ENTITY foo PUBLIC "-//Android//Foo//EN" "http://host/file">
885 * <!ENTITY foo SYSTEM "../file.png" NDATA png>
886 *
887 * Entities may be general or parameterized. Parameterized entities are
888 * marked by a percent sign. Such entities may only be used in the DTD:
889 * <!ENTITY % foo "bar">
890 */
891 private void readEntityDeclaration() throws IOException, XmlPullParserException {
892 read(START_ENTITY);
893 boolean generalEntity = true;
894
895 skip();
896 if (peekCharacter() == '%') {
897 generalEntity = false;
898 position++;
899 skip();
900 }
901
902 String name = readName();
903
904 skip();
905 int quote = peekCharacter();
Jesse Wilson77353372010-11-30 14:46:05 -0800906 String entityValue;
Jesse Wilson7fac0472010-11-23 17:02:32 -0800907 if (quote == '"' || quote == '\'') {
908 position++;
Jesse Wilson77353372010-11-30 14:46:05 -0800909 entityValue = readValue((char) quote, true, false, ValueContext.ENTITY_DECLARATION);
Jesse Wilson7fac0472010-11-23 17:02:32 -0800910 position++;
Jesse Wilson086fd022010-11-29 16:01:57 -0800911 } else if (readExternalId(true, false)) {
Jesse Wilson77353372010-11-30 14:46:05 -0800912 /*
913 * Map external entities to the empty string. This is dishonest,
914 * but it's consistent with Android's Expat pull parser.
915 */
916 entityValue = "";
Jesse Wilson7fac0472010-11-23 17:02:32 -0800917 skip();
918 if (peekCharacter() == NDATA[0]) {
919 read(NDATA);
920 skip();
921 readName();
922 }
923 } else {
924 throw new XmlPullParserException("Expected entity value or external ID", this, null);
925 }
926
Jesse Wilson77353372010-11-30 14:46:05 -0800927 if (generalEntity && processDocDecl) {
928 if (documentEntities == null) {
929 documentEntities = new HashMap<String, char[]>();
930 }
931 documentEntities.put(name, entityValue.toCharArray());
932 }
933
Jesse Wilson7fac0472010-11-23 17:02:32 -0800934 skip();
935 read('>');
936 }
937
938 private void readNotationDeclaration() throws IOException, XmlPullParserException {
939 read(START_NOTATION);
940 skip();
941 readName();
Jesse Wilson086fd022010-11-29 16:01:57 -0800942 if (!readExternalId(false, false)) {
Jesse Wilson7fac0472010-11-23 17:02:32 -0800943 throw new XmlPullParserException(
944 "Expected external ID or public ID for notation", this, null);
945 }
946 skip();
947 read('>');
948 }
949
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700950 private void readEndTag() throws IOException, XmlPullParserException {
951 read('<');
952 read('/');
953 name = readName(); // TODO: pass the expected name in as a hint?
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800954 skip();
955 read('>');
956
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700957 int sp = (depth - 1) * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800958
959 if (depth == 0) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700960 checkRelaxed("read end tag " + name + " with no tags open");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800961 type = COMMENT;
962 return;
963 }
964
Jesse Wilson3b8cee42010-11-19 14:47:30 -0800965 if (name.equals(elementStack[sp + 3])) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -0700966 namespace = elementStack[sp];
967 prefix = elementStack[sp + 1];
968 name = elementStack[sp + 2];
Jesse Wilson3b8cee42010-11-19 14:47:30 -0800969 } else if (!relaxed) {
970 throw new XmlPullParserException(
971 "expected: /" + elementStack[sp + 3] + " read: " + name, this, null);
Elliott Hughesb211e132009-11-09 16:06:42 -0800972 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800973 }
974
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700975 /**
976 * Returns the type of the next token.
977 */
Jesse Wilson7fac0472010-11-23 17:02:32 -0800978 private int peekType(boolean inDeclaration) throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700979 if (position >= limit && !fillBuffer(1)) {
980 return END_DOCUMENT;
981 }
982
Jesse Wilson7fac0472010-11-23 17:02:32 -0800983 switch (buffer[position]) {
984 case '&':
985 return ENTITY_REF; // &
986 case '<':
987 if (position + 3 >= limit && !fillBuffer(4)) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700988 throw new XmlPullParserException("Dangling <", this, null);
989 }
990
Jesse Wilson7fac0472010-11-23 17:02:32 -0800991 switch (buffer[position + 1]) {
992 case '/':
993 return END_TAG; // </
994 case '?':
Jesse Wilsonfda724d2010-11-04 18:32:03 -0700995 // we're looking for "<?xml " with case insensitivity
996 if ((position + 5 < limit || fillBuffer(6))
997 && (buffer[position + 2] == 'x' || buffer[position + 2] == 'X')
998 && (buffer[position + 3] == 'm' || buffer[position + 3] == 'M')
999 && (buffer[position + 4] == 'l' || buffer[position + 4] == 'L')
1000 && (buffer[position + 5] == ' ')) {
Jesse Wilson7fac0472010-11-23 17:02:32 -08001001 return XML_DECLARATION; // <?xml
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001002 } else {
Jesse Wilson7fac0472010-11-23 17:02:32 -08001003 return PROCESSING_INSTRUCTION; // <?
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001004 }
Jesse Wilson7fac0472010-11-23 17:02:32 -08001005 case '!':
1006 switch (buffer[position + 2]) {
1007 case 'D':
1008 return DOCDECL; // <!D
1009 case '[':
1010 return CDSECT; // <![
1011 case '-':
1012 return COMMENT; // <!-
1013 case 'E':
1014 switch (buffer[position + 3]) {
1015 case 'L':
1016 return ELEMENTDECL; // <!EL
1017 case 'N':
1018 return ENTITYDECL; // <!EN
Jesse Wilson7fac0472010-11-23 17:02:32 -08001019 }
Elliott Hughes866e7ae2010-12-08 19:19:13 -08001020 break;
Jesse Wilson7fac0472010-11-23 17:02:32 -08001021 case 'A':
1022 return ATTLISTDECL; // <!A
1023 case 'N':
1024 return NOTATIONDECL; // <!N
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001025 }
Elliott Hughes866e7ae2010-12-08 19:19:13 -08001026 throw new XmlPullParserException("Unexpected <!", this, null);
Jesse Wilson7fac0472010-11-23 17:02:32 -08001027 default:
1028 return START_TAG; // <
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001029 }
Jesse Wilson7fac0472010-11-23 17:02:32 -08001030 case '%':
1031 return inDeclaration ? PARAMETER_ENTITY_REF : TEXT;
1032 default:
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001033 return TEXT;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001034 }
1035 }
1036
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001037 /**
1038 * Sets name and attributes
1039 */
Jesse Wilson086fd022010-11-29 16:01:57 -08001040 private void parseStartTag(boolean xmldecl, boolean throwOnResolveFailure)
1041 throws IOException, XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001042 if (!xmldecl) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001043 read('<');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001044 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001045 name = readName();
1046 attributeCount = 0;
1047
1048 while (true) {
1049 skip();
1050
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001051 if (position >= limit && !fillBuffer(1)) {
1052 checkRelaxed(UNEXPECTED_EOF);
1053 return;
1054 }
1055
1056 int c = buffer[position];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001057
1058 if (xmldecl) {
1059 if (c == '?') {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001060 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001061 read('>');
1062 return;
1063 }
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001064 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001065 if (c == '/') {
1066 degenerated = true;
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001067 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001068 skip();
1069 read('>');
1070 break;
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001071 } else if (c == '>') {
1072 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001073 break;
1074 }
1075 }
1076
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001077 String attrName = readName();
1078
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001079 int i = (attributeCount++) * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001080 attributes = ensureCapacity(attributes, i + 4);
Jesse Wilson76c85882010-11-24 18:26:03 -08001081 attributes[i] = "";
1082 attributes[i + 1] = null;
1083 attributes[i + 2] = attrName;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001084
1085 skip();
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001086 if (position >= limit && !fillBuffer(1)) {
1087 checkRelaxed(UNEXPECTED_EOF);
1088 return;
1089 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001090
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001091 if (buffer[position] == '=') {
1092 position++;
1093
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001094 skip();
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001095 if (position >= limit && !fillBuffer(1)) {
1096 checkRelaxed(UNEXPECTED_EOF);
1097 return;
1098 }
1099 char delimiter = buffer[position];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001100
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001101 if (delimiter == '\'' || delimiter == '"') {
1102 position++;
1103 } else if (relaxed) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001104 delimiter = ' ';
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001105 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001106 throw new XmlPullParserException("attr value delimiter missing!", this, null);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001107 }
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001108
Jesse Wilson086fd022010-11-29 16:01:57 -08001109 attributes[i + 3] = readValue(delimiter, true, throwOnResolveFailure,
1110 ValueContext.ATTRIBUTE);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001111
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001112 if (delimiter != ' ') {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001113 position++; // end quote
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001114 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001115 } else if (relaxed) {
Jesse Wilson76c85882010-11-24 18:26:03 -08001116 attributes[i + 3] = attrName;
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001117 } else {
1118 checkRelaxed("Attr.value missing f. " + attrName);
Jesse Wilson76c85882010-11-24 18:26:03 -08001119 attributes[i + 3] = attrName;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001120 }
1121 }
1122
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001123 int sp = depth++ * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001124 elementStack = ensureCapacity(elementStack, sp + 4);
1125 elementStack[sp + 3] = name;
1126
1127 if (depth >= nspCounts.length) {
1128 int[] bigger = new int[depth + 4];
1129 System.arraycopy(nspCounts, 0, bigger, 0, nspCounts.length);
1130 nspCounts = bigger;
1131 }
1132
1133 nspCounts[depth] = nspCounts[depth - 1];
1134
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001135 if (processNsp) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001136 adjustNsp();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001137 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001138 namespace = "";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001139 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001140
Jesse Wilson76c85882010-11-24 18:26:03 -08001141 // For consistency with Expat, add default attributes after fixing namespaces.
1142 if (defaultAttributes != null) {
1143 Map<String, String> elementDefaultAttributes = defaultAttributes.get(name);
1144 if (elementDefaultAttributes != null) {
1145 for (Map.Entry<String, String> entry : elementDefaultAttributes.entrySet()) {
1146 if (getAttributeValue(null, entry.getKey()) != null) {
1147 continue; // an explicit value overrides the default
1148 }
1149
1150 int i = (attributeCount++) * 4;
1151 attributes = ensureCapacity(attributes, i + 4);
1152 attributes[i] = "";
1153 attributes[i + 1] = null;
1154 attributes[i + 2] = entry.getKey();
1155 attributes[i + 3] = entry.getValue();
1156 }
1157 }
1158 }
1159
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001160 elementStack[sp] = namespace;
1161 elementStack[sp + 1] = prefix;
1162 elementStack[sp + 2] = name;
1163 }
1164
Elliott Hughesf33eae72010-05-13 12:36:25 -07001165 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001166 * Reads an entity reference from the buffer, resolves it, and writes the
1167 * resolved entity to {@code out}. If the entity cannot be read or resolved,
1168 * {@code out} will contain the partial entity reference.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001169 */
Jesse Wilson086fd022010-11-29 16:01:57 -08001170 private void readEntity(StringBuilder out, boolean isEntityToken, boolean throwOnResolveFailure,
1171 ValueContext valueContext) throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001172 int start = out.length();
Elliott Hughesf33eae72010-05-13 12:36:25 -07001173
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001174 if (buffer[position++] != '&') {
1175 throw new AssertionError();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001176 }
1177
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001178 out.append('&');
1179
1180 while (true) {
1181 int c = peekCharacter();
1182
1183 if (c == ';') {
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001184 out.append(';');
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001185 position++;
1186 break;
1187
1188 } else if (c >= 128
1189 || (c >= '0' && c <= '9')
1190 || (c >= 'a' && c <= 'z')
1191 || (c >= 'A' && c <= 'Z')
1192 || c == '_'
1193 || c == '-'
1194 || c == '#') {
1195 position++;
1196 out.append((char) c);
1197
1198 } else if (relaxed) {
1199 // intentionally leave the partial reference in 'out'
1200 return;
1201
1202 } else {
1203 throw new XmlPullParserException("unterminated entity ref", this, null);
1204 }
1205 }
1206
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001207 String code = out.substring(start + 1, out.length() - 1);
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001208
Jesse Wilsonbbf35ec2010-11-20 00:44:27 -08001209 if (isEntityToken) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001210 name = code;
1211 }
1212
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001213 if (code.startsWith("#")) {
1214 try {
1215 int c = code.startsWith("#x")
1216 ? Integer.parseInt(code.substring(2), 16)
1217 : Integer.parseInt(code.substring(1));
1218 out.delete(start, out.length());
1219 out.appendCodePoint(c);
1220 unresolved = false;
Jesse Wilson76c85882010-11-24 18:26:03 -08001221 return;
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001222 } catch (NumberFormatException notANumber) {
1223 throw new XmlPullParserException("Invalid character reference: &" + code);
1224 } catch (IllegalArgumentException invalidCodePoint) {
1225 throw new XmlPullParserException("Invalid character reference: &" + code);
1226 }
Jesse Wilson76c85882010-11-24 18:26:03 -08001227 }
1228
1229 if (valueContext == ValueContext.ENTITY_DECLARATION) {
1230 // keep the unresolved &code; in the text to resolve later
1231 return;
1232 }
1233
1234 String defaultEntity = DEFAULT_ENTITIES.get(code);
1235 if (defaultEntity != null) {
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001236 out.delete(start, out.length());
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001237 unresolved = false;
Jesse Wilson76c85882010-11-24 18:26:03 -08001238 out.append(defaultEntity);
1239 return;
1240 }
1241
1242 char[] resolved;
1243 if (documentEntities != null && (resolved = documentEntities.get(code)) != null) {
1244 out.delete(start, out.length());
1245 unresolved = false;
1246 if (processDocDecl) {
1247 pushContentSource(resolved); // parse the entity as XML
1248 } else {
1249 out.append(resolved); // include the entity value as text
Jesse Wilson3b8cee42010-11-19 14:47:30 -08001250 }
Jesse Wilson76c85882010-11-24 18:26:03 -08001251 return;
1252 }
1253
Jesse Wilson77353372010-11-30 14:46:05 -08001254 /*
1255 * The parser skipped an external DTD, and now we've encountered an
1256 * unknown entity that could have been declared there. Map it to the
1257 * empty string. This is dishonest, but it's consistent with Android's
1258 * old ExpatPullParser.
1259 */
1260 if (systemId != null) {
1261 out.delete(start, out.length());
1262 return;
1263 }
1264
Jesse Wilson76c85882010-11-24 18:26:03 -08001265 // keep the unresolved entity "&code;" in the text for relaxed clients
1266 unresolved = true;
Jesse Wilson086fd022010-11-29 16:01:57 -08001267 if (throwOnResolveFailure) {
Jesse Wilson76c85882010-11-24 18:26:03 -08001268 checkRelaxed("unresolved: &" + code + ";");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001269 }
1270 }
1271
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001272 /**
Jesse Wilson7fac0472010-11-23 17:02:32 -08001273 * Where a value is found impacts how that value is interpreted. For
1274 * example, in attributes, "\n" must be replaced with a space character. In
1275 * text, "]]>" is forbidden. In entity declarations, named references are
1276 * not resolved.
1277 */
1278 enum ValueContext {
1279 ATTRIBUTE,
1280 TEXT,
1281 ENTITY_DECLARATION
1282 }
1283
1284 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001285 * Returns the current text or attribute value. This also has the side
1286 * effect of setting isWhitespace to false if a non-whitespace character is
1287 * encountered.
1288 *
Jesse Wilson7fac0472010-11-23 17:02:32 -08001289 * @param delimiter {@code <} for text, {@code "} and {@code '} for quoted
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001290 * attributes, or a space for unquoted attributes.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001291 */
Jesse Wilson086fd022010-11-29 16:01:57 -08001292 private String readValue(char delimiter, boolean resolveEntities, boolean throwOnResolveFailure,
Jesse Wilson7fac0472010-11-23 17:02:32 -08001293 ValueContext valueContext) throws IOException, XmlPullParserException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001294
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001295 /*
1296 * This method returns all of the characters from the current position
1297 * through to an appropriate delimiter.
1298 *
1299 * If we're lucky (which we usually are), we'll return a single slice of
1300 * the buffer. This fast path avoids allocating a string builder.
1301 *
Jesse Wilson77353372010-11-30 14:46:05 -08001302 * There are 6 unlucky characters we could encounter:
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001303 * - "&": entities must be resolved.
Jesse Wilson77353372010-11-30 14:46:05 -08001304 * - "%": parameter entities are unsupported in entity values.
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001305 * - "<": this isn't permitted in attributes unless relaxed.
1306 * - "]": this requires a lookahead to defend against the forbidden
1307 * CDATA section delimiter "]]>".
1308 * - "\r": If a "\r" is followed by a "\n", we discard the "\r". If it
1309 * isn't followed by "\n", we replace "\r" with either a "\n"
1310 * in text nodes or a space in attribute values.
1311 * - "\n": In attribute values, "\n" must be replaced with a space.
1312 *
1313 * We could also get unlucky by needing to refill the buffer midway
1314 * through the text.
1315 */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001316
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001317 int start = position;
1318 StringBuilder result = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001319
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001320 // if a text section was already started, prefix the start
Jesse Wilson7fac0472010-11-23 17:02:32 -08001321 if (valueContext == ValueContext.TEXT && text != null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001322 result = new StringBuilder();
1323 result.append(text);
1324 }
1325
1326 while (true) {
1327
1328 /*
1329 * Make sure we have at least a single character to read from the
1330 * buffer. This mutates the buffer, so save the partial result
1331 * to the slow path string builder first.
1332 */
1333 if (position >= limit) {
1334 if (start < position) {
1335 if (result == null) {
1336 result = new StringBuilder();
1337 }
1338 result.append(buffer, start, position - start);
1339 }
1340 if (!fillBuffer(1)) {
1341 return result != null ? result.toString() : "";
1342 }
1343 start = position;
1344 }
1345
1346 char c = buffer[position];
1347
1348 if (c == delimiter
1349 || (delimiter == ' ' && (c <= ' ' || c == '>'))
1350 || c == '&' && !resolveEntities) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001351 break;
1352 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001353
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001354 if (c != '\r'
Jesse Wilson7fac0472010-11-23 17:02:32 -08001355 && (c != '\n' || valueContext != ValueContext.ATTRIBUTE)
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001356 && c != '&'
1357 && c != '<'
Jesse Wilson77353372010-11-30 14:46:05 -08001358 && (c != ']' || valueContext != ValueContext.TEXT)
1359 && (c != '%' || valueContext != ValueContext.ENTITY_DECLARATION)) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001360 isWhitespace &= (c <= ' ');
1361 position++;
1362 continue;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001363 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001364
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001365 /*
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001366 * We've encountered an unlucky character! Convert from fast
1367 * path to slow path if we haven't done so already.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001368 */
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001369 if (result == null) {
1370 result = new StringBuilder();
Elliott Hughes6bcf32a2009-11-16 21:23:11 -08001371 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001372 result.append(buffer, start, position - start);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001373
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001374 if (c == '\r') {
1375 if ((position + 1 < limit || fillBuffer(2)) && buffer[position + 1] == '\n') {
1376 position++;
1377 }
Jesse Wilson7fac0472010-11-23 17:02:32 -08001378 c = (valueContext == ValueContext.ATTRIBUTE) ? ' ' : '\n';
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001379
1380 } else if (c == '\n') {
1381 c = ' ';
1382
1383 } else if (c == '&') {
1384 isWhitespace = false; // TODO: what if the entity resolves to whitespace?
Jesse Wilson086fd022010-11-29 16:01:57 -08001385 readEntity(result, false, throwOnResolveFailure, valueContext);
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001386 start = position;
1387 continue;
1388
1389 } else if (c == '<') {
Jesse Wilson7fac0472010-11-23 17:02:32 -08001390 if (valueContext == ValueContext.ATTRIBUTE) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001391 checkRelaxed("Illegal: \"<\" inside attribute value");
1392 }
1393 isWhitespace = false;
1394
1395 } else if (c == ']') {
1396 if ((position + 2 < limit || fillBuffer(3))
1397 && buffer[position + 1] == ']' && buffer[position + 2] == '>') {
1398 checkRelaxed("Illegal: \"]]>\" outside CDATA section");
1399 }
1400 isWhitespace = false;
1401
Jesse Wilson77353372010-11-30 14:46:05 -08001402 } else if (c == '%') {
1403 throw new XmlPullParserException("This parser doesn't support parameter entities",
1404 this, null);
1405
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001406 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001407 throw new AssertionError();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001408 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001409
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001410 position++;
1411 result.append(c);
1412 start = position;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001413 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001414
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001415 if (result == null) {
Jesse Wilsona78c2aa2010-11-14 12:01:08 -08001416 return stringPool.get(buffer, start, position - start);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001417 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001418 result.append(buffer, start, position - start);
1419 return result.toString();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001420 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001421 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001422
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001423 private void read(char expected) throws IOException, XmlPullParserException {
1424 int c = peekCharacter();
1425 if (c != expected) {
1426 checkRelaxed("expected: '" + expected + "' actual: '" + ((char) c) + "'");
1427 }
1428 position++;
1429 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001430
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001431 private void read(char[] chars) throws IOException, XmlPullParserException {
1432 if (position + chars.length >= limit && !fillBuffer(chars.length)) {
1433 checkRelaxed("expected: '" + new String(chars) + "' but was EOF");
1434 return;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001435 }
1436
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001437 // TODO: replace with Arrays.equals(buffer, position, delimiter, 0, delimiter.length)
1438 // when the VM has better method inlining
1439 for (int i = 0; i < chars.length; i++) {
1440 if (buffer[position + i] != chars[i]) {
1441 checkRelaxed("expected: \"" + new String(chars) + "\" but was \""
1442 + new String(buffer, position, chars.length) + "...\"");
1443 }
1444 }
1445
1446 position += chars.length;
1447 }
1448
1449 private int peekCharacter() throws IOException, XmlPullParserException {
1450 if (position < limit || fillBuffer(1)) {
1451 return buffer[position];
1452 }
1453 return -1;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001454 }
1455
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001456 /**
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001457 * Returns true once {@code limit - position >= minimum}. If the data is
1458 * exhausted before that many characters are available, this returns
1459 * false.
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001460 */
Jesse Wilson76c85882010-11-24 18:26:03 -08001461 private boolean fillBuffer(int minimum) throws IOException, XmlPullParserException {
1462 // If we've exhausted the current content source, remove it
1463 while (nextContentSource != null) {
1464 if (position < limit) {
1465 throw new XmlPullParserException("Unbalanced entity!", this, null);
1466 }
1467 popContentSource();
1468 if (limit - position >= minimum) {
1469 return true;
1470 }
1471 }
1472
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001473 // Before clobbering the old characters, update where buffer starts
1474 for (int i = 0; i < position; i++) {
1475 if (buffer[i] == '\n') {
1476 bufferStartLine++;
1477 bufferStartColumn = 0;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001478 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001479 bufferStartColumn++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001480 }
1481 }
1482
Jesse Wilson38e84b82010-12-20 11:51:41 -08001483 if (bufferCapture != null) {
1484 bufferCapture.append(buffer, 0, position);
1485 }
1486
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001487 if (limit != position) {
1488 limit -= position;
1489 System.arraycopy(buffer, position, buffer, 0, limit);
1490 } else {
1491 limit = 0;
1492 }
1493
1494 position = 0;
1495 int total;
1496 while ((total = reader.read(buffer, limit, buffer.length - limit)) != -1) {
1497 limit += total;
1498 if (limit >= minimum) {
1499 return true;
1500 }
1501 }
1502 return false;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001503 }
1504
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001505 /**
1506 * Returns an element or attribute name. This is always non-empty for
1507 * non-relaxed parsers.
1508 */
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001509 private String readName() throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001510 if (position >= limit && !fillBuffer(1)) {
1511 checkRelaxed("name expected");
1512 return "";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001513 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001514
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001515 int start = position;
1516 StringBuilder result = null;
1517
1518 // read the first character
1519 char c = buffer[position];
1520 if ((c >= 'a' && c <= 'z')
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001521 || (c >= 'A' && c <= 'Z')
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001522 || c == '_'
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001523 || c == ':'
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001524 || c >= '\u00c0' // TODO: check the XML spec
1525 || relaxed) {
1526 position++;
1527 } else {
1528 checkRelaxed("name expected");
1529 return "";
1530 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001531
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001532 while (true) {
1533 /*
1534 * Make sure we have at least a single character to read from the
1535 * buffer. This mutates the buffer, so save the partial result
1536 * to the slow path string builder first.
1537 */
1538 if (position >= limit) {
1539 if (result == null) {
1540 result = new StringBuilder();
1541 }
1542 result.append(buffer, start, position - start);
1543 if (!fillBuffer(1)) {
1544 return result.toString();
1545 }
1546 start = position;
1547 }
1548
1549 // read another character
1550 c = buffer[position];
1551 if ((c >= 'a' && c <= 'z')
1552 || (c >= 'A' && c <= 'Z')
1553 || (c >= '0' && c <= '9')
1554 || c == '_'
1555 || c == '-'
1556 || c == ':'
1557 || c == '.'
1558 || c >= '\u00b7') { // TODO: check the XML spec
1559 position++;
1560 continue;
1561 }
1562
1563 // we encountered a non-name character. done!
1564 if (result == null) {
Jesse Wilsona78c2aa2010-11-14 12:01:08 -08001565 return stringPool.get(buffer, start, position - start);
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001566 } else {
1567 result.append(buffer, start, position - start);
1568 return result.toString();
1569 }
1570 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001571 }
1572
Jesse Wilson76c85882010-11-24 18:26:03 -08001573 private void skip() throws IOException, XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001574 while (position < limit || fillBuffer(1)) {
1575 int c = buffer[position];
1576 if (c > ' ') {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001577 break;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001578 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001579 position++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001580 }
1581 }
1582
1583 // public part starts here...
1584
1585 public void setInput(Reader reader) throws XmlPullParserException {
1586 this.reader = reader;
1587
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001588 type = START_DOCUMENT;
1589 name = null;
1590 namespace = null;
1591 degenerated = false;
1592 attributeCount = -1;
1593 encoding = null;
1594 version = null;
1595 standalone = null;
1596
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001597 if (reader == null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001598 return;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001599 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001600
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001601 position = 0;
1602 limit = 0;
1603 bufferStartLine = 0;
1604 bufferStartColumn = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001605 depth = 0;
Jesse Wilson76c85882010-11-24 18:26:03 -08001606 documentEntities = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001607 }
1608
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001609 public void setInput(InputStream is, String charset) throws XmlPullParserException {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001610 position = 0;
1611 limit = 0;
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001612 boolean detectCharset = (charset == null);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001613
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001614 if (is == null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001615 throw new IllegalArgumentException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001616 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001617
1618 try {
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001619 if (detectCharset) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001620 // read the four bytes looking for an indication of the encoding in use
1621 int firstFourBytes = 0;
1622 while (limit < 4) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001623 int i = is.read();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001624 if (i == -1) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001625 break;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001626 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001627 firstFourBytes = (firstFourBytes << 8) | i;
1628 buffer[limit++] = (char) i;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001629 }
1630
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001631 if (limit == 4) {
1632 switch (firstFourBytes) {
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001633 case 0x00000FEFF: // UTF-32BE BOM
1634 charset = "UTF-32BE";
1635 limit = 0;
1636 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001637
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001638 case 0x0FFFE0000: // UTF-32LE BOM
1639 charset = "UTF-32LE";
1640 limit = 0;
1641 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001642
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001643 case 0x0000003c: // '<' in UTF-32BE
1644 charset = "UTF-32BE";
1645 buffer[0] = '<';
1646 limit = 1;
1647 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001648
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001649 case 0x03c000000: // '<' in UTF-32LE
1650 charset = "UTF-32LE";
1651 buffer[0] = '<';
1652 limit = 1;
1653 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001654
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001655 case 0x0003c003f: // "<?" in UTF-16BE
1656 charset = "UTF-16BE";
1657 buffer[0] = '<';
1658 buffer[1] = '?';
1659 limit = 2;
1660 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001661
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001662 case 0x03c003f00: // "<?" in UTF-16LE
1663 charset = "UTF-16LE";
1664 buffer[0] = '<';
1665 buffer[1] = '?';
1666 limit = 2;
1667 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001668
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001669 case 0x03c3f786d: // "<?xm" in ASCII etc.
1670 while (true) {
1671 int i = is.read();
1672 if (i == -1) {
1673 break;
1674 }
1675 buffer[limit++] = (char) i;
1676 if (i == '>') {
1677 String s = new String(buffer, 0, limit);
1678 int i0 = s.indexOf("encoding");
1679 if (i0 != -1) {
1680 while (s.charAt(i0) != '"' && s.charAt(i0) != '\'') {
1681 i0++;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001682 }
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001683 char deli = s.charAt(i0++);
1684 int i1 = s.indexOf(deli, i0);
1685 charset = s.substring(i0, i1);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001686 }
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001687 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001688 }
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001689 }
1690 break;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001691
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001692 default:
1693 // handle a byte order mark followed by something other than <?
1694 if ((firstFourBytes & 0x0ffff0000) == 0x0feff0000) {
1695 charset = "UTF-16BE";
1696 buffer[0] = (char) ((buffer[2] << 8) | buffer[3]);
1697 limit = 1;
1698 } else if ((firstFourBytes & 0x0ffff0000) == 0x0fffe0000) {
1699 charset = "UTF-16LE";
1700 buffer[0] = (char) ((buffer[3] << 8) | buffer[2]);
1701 limit = 1;
1702 } else if ((firstFourBytes & 0x0ffffff00) == 0x0efbbbf00) {
1703 charset = "UTF-8";
1704 buffer[0] = buffer[3];
1705 limit = 1;
1706 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001707 }
1708 }
1709 }
1710
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001711 if (charset == null) {
1712 charset = "UTF-8";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001713 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001714
Jesse Wilson6ce8e6e2011-05-16 15:37:02 -07001715 int savedLimit = limit;
1716 setInput(new InputStreamReader(is, charset));
1717 encoding = charset;
1718 limit = savedLimit;
1719
1720 /*
1721 * Skip the optional BOM if we didn't above. This decrements limit
1722 * rather than incrementing position so that <?xml version='1.0'?>
1723 * is still at character 0.
1724 */
1725 if (!detectCharset && peekCharacter() == 0xfeff) {
1726 limit--;
1727 System.arraycopy(buffer, 1, buffer, 0, limit);
1728 }
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001729 } catch (Exception e) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001730 throw new XmlPullParserException("Invalid stream or encoding: " + e, this, e);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001731 }
1732 }
1733
Jesse Wilson086fd022010-11-29 16:01:57 -08001734 public void close() throws IOException {
1735 if (reader != null) {
1736 reader.close();
1737 }
1738 }
1739
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001740 public boolean getFeature(String feature) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001741 if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001742 return processNsp;
Jesse Wilson76c85882010-11-24 18:26:03 -08001743 } else if (FEATURE_RELAXED.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001744 return relaxed;
Jesse Wilson76c85882010-11-24 18:26:03 -08001745 } else if (FEATURE_PROCESS_DOCDECL.equals(feature)) {
1746 return processDocDecl;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001747 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001748 return false;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001749 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001750 }
1751
1752 public String getInputEncoding() {
1753 return encoding;
1754 }
1755
1756 public void defineEntityReplacementText(String entity, String value)
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001757 throws XmlPullParserException {
Jesse Wilson76c85882010-11-24 18:26:03 -08001758 if (processDocDecl) {
1759 throw new IllegalStateException(
1760 "Entity replacement text may not be defined with DOCTYPE processing enabled.");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001761 }
Jesse Wilson76c85882010-11-24 18:26:03 -08001762 if (reader == null) {
1763 throw new IllegalStateException(
1764 "Entity replacement text must be defined after setInput()");
1765 }
1766 if (documentEntities == null) {
1767 documentEntities = new HashMap<String, char[]>();
1768 }
1769 documentEntities.put(entity, value.toCharArray());
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001770 }
1771
1772 public Object getProperty(String property) {
Jesse Wilson76c85882010-11-24 18:26:03 -08001773 if (property.equals(PROPERTY_XMLDECL_VERSION)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001774 return version;
Jesse Wilson76c85882010-11-24 18:26:03 -08001775 } else if (property.equals(PROPERTY_XMLDECL_STANDALONE)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001776 return standalone;
Jesse Wilson76c85882010-11-24 18:26:03 -08001777 } else if (property.equals(PROPERTY_LOCATION)) {
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001778 return location != null ? location : reader.toString();
Jesse Wilson76c85882010-11-24 18:26:03 -08001779 } else {
1780 return null;
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001781 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001782 }
1783
Jesse Wilson086fd022010-11-29 16:01:57 -08001784 /**
1785 * Returns the root element's name if it was declared in the DTD. This
1786 * equals the first tag's name for valid documents.
1787 */
1788 public String getRootElementName() {
1789 return rootElementName;
1790 }
1791
1792 /**
1793 * Returns the document's system ID if it was declared. This is typically a
1794 * string like {@code http://www.w3.org/TR/html4/strict.dtd}.
1795 */
1796 public String getSystemId() {
1797 return systemId;
1798 }
1799
1800 /**
1801 * Returns the document's public ID if it was declared. This is typically a
1802 * string like {@code -//W3C//DTD HTML 4.01//EN}.
1803 */
1804 public String getPublicId() {
1805 return publicId;
1806 }
1807
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001808 public int getNamespaceCount(int depth) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001809 if (depth > this.depth) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001810 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001811 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001812 return nspCounts[depth];
1813 }
1814
1815 public String getNamespacePrefix(int pos) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001816 return nspStack[pos * 2];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001817 }
1818
1819 public String getNamespaceUri(int pos) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001820 return nspStack[(pos * 2) + 1];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001821 }
1822
1823 public String getNamespace(String prefix) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001824 if ("xml".equals(prefix)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001825 return "http://www.w3.org/XML/1998/namespace";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001826 }
1827 if ("xmlns".equals(prefix)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001828 return "http://www.w3.org/2000/xmlns/";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001829 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001830
1831 for (int i = (getNamespaceCount(depth) << 1) - 2; i >= 0; i -= 2) {
1832 if (prefix == null) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001833 if (nspStack[i] == null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001834 return nspStack[i + 1];
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001835 }
1836 } else if (prefix.equals(nspStack[i])) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001837 return nspStack[i + 1];
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001838 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001839 }
1840 return null;
1841 }
1842
1843 public int getDepth() {
1844 return depth;
1845 }
1846
1847 public String getPositionDescription() {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001848 StringBuilder buf = new StringBuilder(type < TYPES.length ? TYPES[type] : "unknown");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001849 buf.append(' ');
1850
1851 if (type == START_TAG || type == END_TAG) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001852 if (degenerated) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001853 buf.append("(empty) ");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001854 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001855 buf.append('<');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001856 if (type == END_TAG) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001857 buf.append('/');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001858 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001859
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001860 if (prefix != null) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001861 buf.append("{" + namespace + "}" + prefix + ":");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001862 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001863 buf.append(name);
1864
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001865 int cnt = attributeCount * 4;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001866 for (int i = 0; i < cnt; i += 4) {
1867 buf.append(' ');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001868 if (attributes[i + 1] != null) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001869 buf.append("{" + attributes[i] + "}" + attributes[i + 1] + ":");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001870 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001871 buf.append(attributes[i + 2] + "='" + attributes[i + 3] + "'");
1872 }
1873
1874 buf.append('>');
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001875 } else if (type == IGNORABLE_WHITESPACE) {
1876 ;
1877 } else if (type != TEXT) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001878 buf.append(getText());
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001879 } else if (isWhitespace) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001880 buf.append("(whitespace)");
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001881 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001882 String text = getText();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001883 if (text.length() > 16) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001884 text = text.substring(0, 16) + "...";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001885 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001886 buf.append(text);
1887 }
1888
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001889 buf.append("@" + getLineNumber() + ":" + getColumnNumber());
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001890 if (location != null) {
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001891 buf.append(" in ");
1892 buf.append(location);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001893 } else if (reader != null) {
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001894 buf.append(" in ");
1895 buf.append(reader.toString());
1896 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001897 return buf.toString();
1898 }
1899
1900 public int getLineNumber() {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001901 int result = bufferStartLine;
1902 for (int i = 0; i < position; i++) {
1903 if (buffer[i] == '\n') {
1904 result++;
1905 }
1906 }
1907 return result + 1; // the first line is '1'
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001908 }
1909
1910 public int getColumnNumber() {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001911 int result = bufferStartColumn;
1912 for (int i = 0; i < position; i++) {
1913 if (buffer[i] == '\n') {
1914 result = 0;
1915 } else {
1916 result++;
1917 }
1918 }
1919 return result + 1; // the first column is '1'
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001920 }
1921
1922 public boolean isWhitespace() throws XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001923 if (type != TEXT && type != IGNORABLE_WHITESPACE && type != CDSECT) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001924 throw new XmlPullParserException(ILLEGAL_TYPE, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001925 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001926 return isWhitespace;
1927 }
1928
1929 public String getText() {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001930 if (type < TEXT || (type == ENTITY_REF && unresolved)) {
1931 return null;
1932 } else if (text == null) {
1933 return "";
1934 } else {
1935 return text;
1936 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001937 }
1938
1939 public char[] getTextCharacters(int[] poslen) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001940 String text = getText();
1941 if (text == null) {
1942 poslen[0] = -1;
1943 poslen[1] = -1;
1944 return null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001945 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001946 char[] result = text.toCharArray();
1947 poslen[0] = 0;
1948 poslen[1] = result.length;
1949 return result;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001950 }
1951
1952 public String getNamespace() {
1953 return namespace;
1954 }
1955
1956 public String getName() {
1957 return name;
1958 }
1959
1960 public String getPrefix() {
1961 return prefix;
1962 }
1963
1964 public boolean isEmptyElementTag() throws XmlPullParserException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001965 if (type != START_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001966 throw new XmlPullParserException(ILLEGAL_TYPE, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001967 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001968 return degenerated;
1969 }
1970
1971 public int getAttributeCount() {
1972 return attributeCount;
1973 }
1974
1975 public String getAttributeType(int index) {
1976 return "CDATA";
1977 }
1978
1979 public boolean isAttributeDefault(int index) {
1980 return false;
1981 }
1982
1983 public String getAttributeNamespace(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001984 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001985 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001986 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001987 return attributes[index * 4];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001988 }
1989
1990 public String getAttributeName(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001991 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001992 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001993 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07001994 return attributes[(index * 4) + 2];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001995 }
1996
1997 public String getAttributePrefix(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07001998 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001999 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002000 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002001 return attributes[(index * 4) + 1];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002002 }
2003
2004 public String getAttributeValue(int index) {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002005 if (index >= attributeCount) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002006 throw new IndexOutOfBoundsException();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002007 }
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002008 return attributes[(index * 4) + 3];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002009 }
2010
2011 public String getAttributeValue(String namespace, String name) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002012 for (int i = (attributeCount * 4) - 4; i >= 0; i -= 4) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002013 if (attributes[i + 2].equals(name)
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002014 && (namespace == null || attributes[i].equals(namespace))) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002015 return attributes[i + 3];
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002016 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002017 }
2018
2019 return null;
2020 }
2021
2022 public int getEventType() throws XmlPullParserException {
2023 return type;
2024 }
2025
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002026 // utility methods to make XML parsing easier ...
2027
2028 public int nextTag() throws XmlPullParserException, IOException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002029 next();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002030 if (type == TEXT && isWhitespace) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002031 next();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002032 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002033
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002034 if (type != END_TAG && type != START_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002035 throw new XmlPullParserException("unexpected type", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002036 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002037
2038 return type;
2039 }
2040
2041 public void require(int type, String namespace, String name)
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002042 throws XmlPullParserException, IOException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002043 if (type != this.type
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002044 || (namespace != null && !namespace.equals(getNamespace()))
2045 || (name != null && !name.equals(getName()))) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002046 throw new XmlPullParserException(
2047 "expected: " + TYPES[type] + " {" + namespace + "}" + name, this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002048 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002049 }
2050
2051 public String nextText() throws XmlPullParserException, IOException {
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002052 if (type != START_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002053 throw new XmlPullParserException("precondition: START_TAG", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002054 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002055
2056 next();
2057
2058 String result;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002059 if (type == TEXT) {
2060 result = getText();
2061 next();
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002062 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002063 result = "";
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002064 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002065
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002066 if (type != END_TAG) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002067 throw new XmlPullParserException("END_TAG expected", this, null);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002068 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002069
2070 return result;
2071 }
2072
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002073 public void setFeature(String feature, boolean value) throws XmlPullParserException {
2074 if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002075 processNsp = value;
Jesse Wilson76c85882010-11-24 18:26:03 -08002076 } else if (XmlPullParser.FEATURE_PROCESS_DOCDECL.equals(feature)) {
2077 processDocDecl = value;
2078 } else if (FEATURE_RELAXED.equals(feature)) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002079 relaxed = value;
Elliott Hughesd21d78f2010-05-13 11:32:57 -07002080 } else {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002081 throw new XmlPullParserException("unsupported feature: " + feature, this, null);
Elliott Hughesd21d78f2010-05-13 11:32:57 -07002082 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002083 }
2084
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002085 public void setProperty(String property, Object value) throws XmlPullParserException {
Jesse Wilson76c85882010-11-24 18:26:03 -08002086 if (property.equals(PROPERTY_LOCATION)) {
Jesse Wilsonfda724d2010-11-04 18:32:03 -07002087 location = String.valueOf(value);
Jesse Wilsonccd79e22010-11-04 14:02:20 -07002088 } else {
2089 throw new XmlPullParserException("unsupported property: " + property);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002090 }
2091 }
Jesse Wilson76c85882010-11-24 18:26:03 -08002092
2093 /**
2094 * A chain of buffers containing XML content. Each content source contains
2095 * the parser's primary read buffer or the characters of entities actively
2096 * being parsed.
2097 *
2098 * <p>For example, note the buffers needed to parse this document:
2099 * <pre> {@code
2100 * <!DOCTYPE foo [
2101 * <!ENTITY baz "ghi">
2102 * <!ENTITY bar "def &baz; jkl">
2103 * ]>
2104 * <foo>abc &bar; mno</foo>
2105 * }</pre>
2106 *
2107 * <p>Things get interesting when the bar entity is encountered. At that
2108 * point two buffers are active:
2109 * <ol>
2110 * <li>The value for the bar entity, containing {@code "def &baz; jkl"}
2111 * <li>The parser's primary read buffer, containing {@code " mno</foo>"}
2112 * </ol>
2113 * <p>The parser will return the characters {@code "def "} from the bar
2114 * entity's buffer, and then it will encounter the baz entity. To handle
2115 * that, three buffers will be active:
2116 * <ol>
2117 * <li>The value for the baz entity, containing {@code "ghi"}
2118 * <li>The remaining value for the bar entity, containing {@code " jkl"}
2119 * <li>The parser's primary read buffer, containing {@code " mno</foo>"}
2120 * </ol>
2121 * <p>The parser will then return the characters {@code ghi jkl mno} in that
2122 * sequence by reading each buffer in sequence.
2123 */
2124 static class ContentSource {
2125 private final ContentSource next;
2126 private final char[] buffer;
2127 private final int position;
2128 private final int limit;
2129 ContentSource(ContentSource next, char[] buffer, int position, int limit) {
2130 this.next = next;
2131 this.buffer = buffer;
2132 this.position = position;
2133 this.limit = limit;
2134 }
2135 }
2136
2137 /**
2138 * Prepends the characters of {@code newBuffer} to be read before the
2139 * current buffer.
2140 */
2141 private void pushContentSource(char[] newBuffer) {
2142 nextContentSource = new ContentSource(nextContentSource, buffer, position, limit);
2143 buffer = newBuffer;
2144 position = 0;
2145 limit = newBuffer.length;
2146 }
2147
2148 /**
2149 * Replaces the current exhausted buffer with the next buffer in the chain.
2150 */
2151 private void popContentSource() {
2152 buffer = nextContentSource.buffer;
2153 position = nextContentSource.position;
2154 limit = nextContentSource.limit;
2155 nextContentSource = nextContentSource.next;
2156 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08002157}