blob: dd73f0ba14be9d21dcff598760e02a9a631477d3 [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
25import java.io.*;
26import java.util.*;
27
28import org.xmlpull.v1.*;
29
30/** A simple, pull based XML parser. This classe replaces the kXML 1
31 XmlParser class and the corresponding event classes. */
32
33public class KXmlParser implements XmlPullParser {
34
35 private Object location;
Elliott Hughesd21d78f2010-05-13 11:32:57 -070036 static final private String UNEXPECTED_EOF = "Unexpected EOF";
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080037 static final private String ILLEGAL_TYPE = "Wrong event type";
38 static final private int LEGACY = 999;
39 static final private int XML_DECL = 998;
40
41 // general
42
43 private String version;
44 private Boolean standalone;
45
46 private boolean processNsp;
47 private boolean relaxed;
Jesse Wilson1ec94fe2010-02-19 09:22:21 -080048 private boolean keepNamespaceAttributes; // android-added
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080049 private Hashtable entityMap;
50 private int depth;
51 private String[] elementStack = new String[16];
52 private String[] nspStack = new String[8];
53 private int[] nspCounts = new int[4];
54
55 // source
56
57 private Reader reader;
58 private String encoding;
59 private char[] srcBuf;
60
61 private int srcPos;
62 private int srcCount;
63
64 private int line;
65 private int column;
66
67 // txtbuffer
68
Elliott Hughesb211e132009-11-09 16:06:42 -080069 /** Target buffer for storing incoming text (including aggregated resolved entities) */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080070 private char[] txtBuf = new char[128];
Elliott Hughesb211e132009-11-09 16:06:42 -080071 /** Write position */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080072 private int txtPos;
73
74 // Event-related
75
76 private int type;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080077 private boolean isWhitespace;
78 private String namespace;
79 private String prefix;
80 private String name;
81
82 private boolean degenerated;
83 private int attributeCount;
Jesse Wilson1ec94fe2010-02-19 09:22:21 -080084
85 /**
86 * The current element's attributes arranged in groups of 4:
87 * i + 0 = attribute namespace URI
88 * i + 1 = attribute namespace prefix
89 * i + 2 = attribute qualified name (may contain ":", as in "html:h1")
90 * i + 3 = attribute value
91 */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080092 private String[] attributes = new String[16];
Elliott Hughesb211e132009-11-09 16:06:42 -080093// private int stackMismatch = 0;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080094 private String error;
95
Elliott Hughesf33eae72010-05-13 12:36:25 -070096 /**
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080097 * A separate peek buffer seems simpler than managing
98 * wrap around in the first level read buffer */
99
100 private int[] peek = new int[2];
101 private int peekCount;
102 private boolean wasCR;
103
104 private boolean unresolved;
105 private boolean token;
106
107 public KXmlParser() {
Elliott Hughesb211e132009-11-09 16:06:42 -0800108 srcBuf =
109 new char[Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 128];
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800110 }
111
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800112 // BEGIN android-added
113 /**
114 * Retains namespace attributes like {@code xmlns="http://foo"} or {@code
115 * xmlns:foo="http:foo"} in pulled elements. Most applications will only be
116 * interested in the effective namespaces of their elements, so these
117 * attributes aren't useful. But for structure preserving wrappers like DOM,
118 * it is necessary to keep the namespace data around.
119 */
120 public void keepNamespaceAttributes() {
121 this.keepNamespaceAttributes = true;
122 }
123 // END android-added
124
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800125 private final boolean isProp(String n1, boolean prop, String n2) {
126 if (!n1.startsWith("http://xmlpull.org/v1/doc/"))
127 return false;
128 if (prop)
129 return n1.substring(42).equals(n2);
130 else
131 return n1.substring(40).equals(n2);
132 }
133
134 private final boolean adjustNsp() throws XmlPullParserException {
135
136 boolean any = false;
137
138 for (int i = 0; i < attributeCount << 2; i += 4) {
139 // * 4 - 4; i >= 0; i -= 4) {
140
141 String attrName = attributes[i + 2];
142 int cut = attrName.indexOf(':');
143 String prefix;
144
145 if (cut != -1) {
146 prefix = attrName.substring(0, cut);
147 attrName = attrName.substring(cut + 1);
148 }
149 else if (attrName.equals("xmlns")) {
150 prefix = attrName;
151 attrName = null;
152 }
153 else
154 continue;
155
156 if (!prefix.equals("xmlns")) {
157 any = true;
158 }
159 else {
160 int j = (nspCounts[depth]++) << 1;
161
162 nspStack = ensureCapacity(nspStack, j + 2);
163 nspStack[j] = attrName;
164 nspStack[j + 1] = attributes[i + 3];
165
Elliott Hughes80a7fba2010-05-21 16:58:35 -0700166 if (attrName != null && attributes[i + 3].isEmpty())
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800167 error("illegal empty namespace");
168
169 // prefixMap = new PrefixMap (prefixMap, attrName, attr.getValue ());
170
171 //System.out.println (prefixMap);
172
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800173 // BEGIN android-changed
174 if (keepNamespaceAttributes) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700175 // explicitly set the namespace for unprefixed attributes
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800176 // such as xmlns="http://foo"
177 attributes[i] = "http://www.w3.org/2000/xmlns/";
178 any = true;
179 } else {
180 System.arraycopy(
181 attributes,
182 i + 4,
183 attributes,
184 i,
185 ((--attributeCount) << 2) - i);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800186
Jesse Wilson1ec94fe2010-02-19 09:22:21 -0800187 i -= 4;
188 }
189 // END android-changed
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800190 }
191 }
192
193 if (any) {
194 for (int i = (attributeCount << 2) - 4; i >= 0; i -= 4) {
195
196 String attrName = attributes[i + 2];
197 int cut = attrName.indexOf(':');
198
199 if (cut == 0 && !relaxed)
200 throw new RuntimeException(
201 "illegal attribute name: " + attrName + " at " + this);
202
203 else if (cut != -1) {
204 String attrPrefix = attrName.substring(0, cut);
205
206 attrName = attrName.substring(cut + 1);
207
208 String attrNs = getNamespace(attrPrefix);
209
210 if (attrNs == null && !relaxed)
211 throw new RuntimeException(
212 "Undefined Prefix: " + attrPrefix + " in " + this);
213
214 attributes[i] = attrNs;
215 attributes[i + 1] = attrPrefix;
216 attributes[i + 2] = attrName;
217
218 /*
219 if (!relaxed) {
220 for (int j = (attributeCount << 2) - 4; j > i; j -= 4)
221 if (attrName.equals(attributes[j + 2])
222 && attrNs.equals(attributes[j]))
223 exception(
224 "Duplicate Attribute: {"
225 + attrNs
226 + "}"
227 + attrName);
228 }
229 */
230 }
231 }
232 }
233
234 int cut = name.indexOf(':');
235
236 if (cut == 0)
237 error("illegal tag name: " + name);
238
239 if (cut != -1) {
240 prefix = name.substring(0, cut);
241 name = name.substring(cut + 1);
242 }
243
244 this.namespace = getNamespace(prefix);
245
246 if (this.namespace == null) {
247 if (prefix != null)
248 error("undefined prefix: " + prefix);
249 this.namespace = NO_NAMESPACE;
250 }
251
252 return any;
253 }
254
255 private final String[] ensureCapacity(String[] arr, int required) {
256 if (arr.length >= required)
257 return arr;
258 String[] bigger = new String[required + 16];
259 System.arraycopy(arr, 0, bigger, 0, arr.length);
260 return bigger;
261 }
262
263 private final void error(String desc) throws XmlPullParserException {
264 if (relaxed) {
265 if (error == null)
266 error = "ERR: " + desc;
267 }
268 else
269 exception(desc);
270 }
271
272 private final void exception(String desc) throws XmlPullParserException {
273 throw new XmlPullParserException(
274 desc.length() < 100 ? desc : desc.substring(0, 100) + "\n",
275 this,
276 null);
277 }
278
Elliott Hughesf33eae72010-05-13 12:36:25 -0700279 /**
280 * common base for next and nextToken. Clears the state, except from
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800281 * txtPos and whitespace. Does not set the type variable */
282
283 private final void nextImpl() throws IOException, XmlPullParserException {
284
285 if (reader == null)
286 exception("No Input specified");
287
288 if (type == END_TAG)
289 depth--;
290
291 while (true) {
292 attributeCount = -1;
293
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700294 // degenerated needs to be handled before error because of possible
295 // processor expectations(!)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800296
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700297 if (degenerated) {
298 degenerated = false;
299 type = END_TAG;
300 return;
301 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800302
303
304 if (error != null) {
305 for (int i = 0; i < error.length(); i++)
306 push(error.charAt(i));
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700307 //text = error;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800308 error = null;
309 type = COMMENT;
310 return;
311 }
312
313
Elliott Hughesb211e132009-11-09 16:06:42 -0800314// if (relaxed
315// && (stackMismatch > 0 || (peek(0) == -1 && depth > 0))) {
316// int sp = (depth - 1) << 2;
317// type = END_TAG;
318// namespace = elementStack[sp];
319// prefix = elementStack[sp + 1];
320// name = elementStack[sp + 2];
321// if (stackMismatch != 1)
322// error = "missing end tag /" + name + " inserted";
323// if (stackMismatch > 0)
324// stackMismatch--;
325// return;
326// }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800327
328 prefix = null;
329 name = null;
330 namespace = null;
331 // text = null;
332
333 type = peekType();
334
335 switch (type) {
336
337 case ENTITY_REF :
338 pushEntity();
339 return;
340
341 case START_TAG :
342 parseStartTag(false);
343 return;
344
345 case END_TAG :
346 parseEndTag();
347 return;
348
349 case END_DOCUMENT :
350 return;
351
352 case TEXT :
Elliott Hughes6bcf32a2009-11-16 21:23:11 -0800353 // BEGIN android-changed: distinguish attribute values from normal text.
354 pushText('<', !token, false);
355 // END android-changed
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800356 if (depth == 0) {
357 if (isWhitespace)
358 type = IGNORABLE_WHITESPACE;
359 // make exception switchable for instances.chg... !!!!
Elliott Hughesf33eae72010-05-13 12:36:25 -0700360 // else
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800361 // exception ("text '"+getText ()+"' not allowed outside root element");
362 }
363 return;
364
365 default :
366 type = parseLegacy(token);
367 if (type != XML_DECL)
368 return;
369 }
370 }
371 }
372
373 private final int parseLegacy(boolean push)
374 throws IOException, XmlPullParserException {
375
376 String req = "";
377 int term;
378 int result;
379 int prev = 0;
380
381 read(); // <
382 int c = read();
383
384 if (c == '?') {
385 if ((peek(0) == 'x' || peek(0) == 'X')
386 && (peek(1) == 'm' || peek(1) == 'M')) {
387
388 if (push) {
389 push(peek(0));
390 push(peek(1));
391 }
392 read();
393 read();
394
395 if ((peek(0) == 'l' || peek(0) == 'L') && peek(1) <= ' ') {
396
397 if (line != 1 || column > 4)
398 error("PI must not start with xml");
399
400 parseStartTag(true);
401
402 if (attributeCount < 1 || !"version".equals(attributes[2]))
403 error("version expected");
404
405 version = attributes[3];
406
407 int pos = 1;
408
409 if (pos < attributeCount
410 && "encoding".equals(attributes[2 + 4])) {
411 encoding = attributes[3 + 4];
412 pos++;
413 }
414
415 if (pos < attributeCount
416 && "standalone".equals(attributes[4 * pos + 2])) {
417 String st = attributes[3 + 4 * pos];
418 if ("yes".equals(st))
419 standalone = new Boolean(true);
420 else if ("no".equals(st))
421 standalone = new Boolean(false);
422 else
423 error("illegal standalone value: " + st);
424 pos++;
425 }
426
427 if (pos != attributeCount)
428 error("illegal xmldecl");
429
430 isWhitespace = true;
431 txtPos = 0;
432
433 return XML_DECL;
434 }
435 }
436
437 /* int c0 = read ();
438 int c1 = read ();
439 int */
440
441 term = '?';
442 result = PROCESSING_INSTRUCTION;
443 }
444 else if (c == '!') {
445 if (peek(0) == '-') {
446 result = COMMENT;
447 req = "--";
448 term = '-';
449 }
450 else if (peek(0) == '[') {
451 result = CDSECT;
452 req = "[CDATA[";
453 term = ']';
454 push = true;
455 }
456 else {
457 result = DOCDECL;
458 req = "DOCTYPE";
459 term = -1;
460 }
461 }
462 else {
463 error("illegal: <" + c);
464 return COMMENT;
465 }
466
467 for (int i = 0; i < req.length(); i++)
468 read(req.charAt(i));
469
470 if (result == DOCDECL)
471 parseDoctype(push);
472 else {
473 while (true) {
474 c = read();
475 if (c == -1){
476 error(UNEXPECTED_EOF);
477 return COMMENT;
478 }
479
480 if (push)
481 push(c);
482
483 if ((term == '?' || c == term)
484 && peek(0) == term
485 && peek(1) == '>')
486 break;
487
488 prev = c;
489 }
490
Elliott Hughesb211e132009-11-09 16:06:42 -0800491 if (term == '-' && prev == '-' && !relaxed)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800492 error("illegal comment delimiter: --->");
493
494 read();
495 read();
496
497 if (push && term != '?')
498 txtPos--;
499
500 }
501 return result;
502 }
503
504 /** precondition: &lt! consumed */
505
506 private final void parseDoctype(boolean push)
507 throws IOException, XmlPullParserException {
508
509 int nesting = 1;
510 boolean quoted = false;
511
512 // read();
513
514 while (true) {
515 int i = read();
516 switch (i) {
517
518 case -1 :
519 error(UNEXPECTED_EOF);
520 return;
521
522 case '\'' :
523 quoted = !quoted;
524 break;
525
526 case '<' :
527 if (!quoted)
528 nesting++;
529 break;
530
531 case '>' :
532 if (!quoted) {
533 if ((--nesting) == 0)
534 return;
535 }
536 break;
537 }
538 if (push)
539 push(i);
540 }
541 }
542
543 /* precondition: &lt;/ consumed */
544
545 private final void parseEndTag()
546 throws IOException, XmlPullParserException {
547
548 read(); // '<'
549 read(); // '/'
550 name = readName();
551 skip();
552 read('>');
553
554 int sp = (depth - 1) << 2;
555
556 if (depth == 0) {
557 error("element stack empty");
558 type = COMMENT;
559 return;
560 }
561
Elliott Hughesb211e132009-11-09 16:06:42 -0800562 if (!relaxed) {
563 if (!name.equals(elementStack[sp + 3])) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800564 error("expected: /" + elementStack[sp + 3] + " read: " + name);
565
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700566 // become case insensitive in relaxed mode
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800567
Elliott Hughesb211e132009-11-09 16:06:42 -0800568// int probe = sp;
569// while (probe >= 0 && !name.toLowerCase().equals(elementStack[probe + 3].toLowerCase())) {
570// stackMismatch++;
571// probe -= 4;
572// }
573//
574// if (probe < 0) {
575// stackMismatch = 0;
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700576// // text = "unexpected end tag ignored";
Elliott Hughesb211e132009-11-09 16:06:42 -0800577// type = COMMENT;
578// return;
579// }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800580 }
581
582 namespace = elementStack[sp];
583 prefix = elementStack[sp + 1];
584 name = elementStack[sp + 2];
Elliott Hughesb211e132009-11-09 16:06:42 -0800585 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800586 }
587
588 private final int peekType() throws IOException {
589 switch (peek(0)) {
590 case -1 :
591 return END_DOCUMENT;
592 case '&' :
593 return ENTITY_REF;
594 case '<' :
595 switch (peek(1)) {
596 case '/' :
597 return END_TAG;
598 case '?' :
599 case '!' :
600 return LEGACY;
601 default :
602 return START_TAG;
603 }
604 default :
605 return TEXT;
606 }
607 }
608
609 private final String get(int pos) {
610 return new String(txtBuf, pos, txtPos - pos);
611 }
612
613 /*
614 private final String pop (int pos) {
615 String result = new String (txtBuf, pos, txtPos - pos);
616 txtPos = pos;
617 return result;
618 }
619 */
620
621 private final void push(int c) {
622
623 isWhitespace &= c <= ' ';
624
625 if (txtPos == txtBuf.length) {
626 char[] bigger = new char[txtPos * 4 / 3 + 4];
627 System.arraycopy(txtBuf, 0, bigger, 0, txtPos);
628 txtBuf = bigger;
629 }
630
631 txtBuf[txtPos++] = (char) c;
632 }
633
634 /** Sets name and attributes */
635
636 private final void parseStartTag(boolean xmldecl)
637 throws IOException, XmlPullParserException {
638
639 if (!xmldecl)
640 read();
641 name = readName();
642 attributeCount = 0;
643
644 while (true) {
645 skip();
646
647 int c = peek(0);
648
649 if (xmldecl) {
650 if (c == '?') {
651 read();
652 read('>');
653 return;
654 }
655 }
656 else {
657 if (c == '/') {
658 degenerated = true;
659 read();
660 skip();
661 read('>');
662 break;
663 }
664
665 if (c == '>' && !xmldecl) {
666 read();
667 break;
668 }
669 }
670
671 if (c == -1) {
672 error(UNEXPECTED_EOF);
673 //type = COMMENT;
674 return;
675 }
676
677 String attrName = readName();
678
679 if (attrName.length() == 0) {
680 error("attr name expected");
681 //type = COMMENT;
682 break;
683 }
684
685 int i = (attributeCount++) << 2;
686
687 attributes = ensureCapacity(attributes, i + 4);
688
689 attributes[i++] = "";
690 attributes[i++] = null;
691 attributes[i++] = attrName;
692
693 skip();
694
695 if (peek(0) != '=') {
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700696 if(!relaxed){
697 error("Attr.value missing f. "+attrName);
698 }
Elliott Hughesb211e132009-11-09 16:06:42 -0800699 attributes[i] = attrName;
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700700 } else {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800701 read('=');
702 skip();
703 int delimiter = peek(0);
704
705 if (delimiter != '\'' && delimiter != '"') {
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700706 if(!relaxed){
707 error("attr value delimiter missing!");
708 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800709 delimiter = ' ';
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700710 } else {
711 read();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800712 }
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700713
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800714 int p = txtPos;
Elliott Hughes6bcf32a2009-11-16 21:23:11 -0800715 // BEGIN android-changed: distinguish attribute values from normal text.
716 pushText(delimiter, true, true);
717 // END android-changed
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800718
719 attributes[i] = get(p);
720 txtPos = p;
721
722 if (delimiter != ' ')
723 read(); // skip endquote
724 }
725 }
726
727 int sp = depth++ << 2;
728
729 elementStack = ensureCapacity(elementStack, sp + 4);
730 elementStack[sp + 3] = name;
731
732 if (depth >= nspCounts.length) {
733 int[] bigger = new int[depth + 4];
734 System.arraycopy(nspCounts, 0, bigger, 0, nspCounts.length);
735 nspCounts = bigger;
736 }
737
738 nspCounts[depth] = nspCounts[depth - 1];
739
740 /*
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700741 if(!relaxed){
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800742 for (int i = attributeCount - 1; i > 0; i--) {
743 for (int j = 0; j < i; j++) {
744 if (getAttributeName(i).equals(getAttributeName(j)))
745 exception("Duplicate Attribute: " + getAttributeName(i));
746 }
747 }
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700748 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800749 */
750 if (processNsp)
751 adjustNsp();
752 else
753 namespace = "";
754
755 elementStack[sp] = namespace;
756 elementStack[sp + 1] = prefix;
757 elementStack[sp + 2] = name;
758 }
759
Elliott Hughesf33eae72010-05-13 12:36:25 -0700760 /**
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800761 * result: isWhitespace; if the setName parameter is set,
762 * the name of the entity is stored in "name" */
763
764 private final void pushEntity()
765 throws IOException, XmlPullParserException {
766
767 push(read()); // &
Elliott Hughesf33eae72010-05-13 12:36:25 -0700768
769
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800770 int pos = txtPos;
771
772 while (true) {
Elliott Hughesb211e132009-11-09 16:06:42 -0800773 int c = peek(0);
774 if (c == ';') {
775 read();
776 break;
777 }
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700778 if (c < 128 && (c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z')
779 && c != '_' && c != '-' && c != '#') {
780 if(!relaxed){
781 error("unterminated entity ref");
782 }
Elliott Hughesf33eae72010-05-13 12:36:25 -0700783
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700784 // BEGIN android-removed: avoid log spam.
785 // System.out.println("broken entitiy: "+get(pos-1));
786 // END android-removed
Elliott Hughesf33eae72010-05-13 12:36:25 -0700787
788 //; ends with:"+(char)c);
Elliott Hughesb211e132009-11-09 16:06:42 -0800789// if (c != -1)
790// push(c);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800791 return;
792 }
793
Elliott Hughesb211e132009-11-09 16:06:42 -0800794 push(read());
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800795 }
796
797 String code = get(pos);
798 txtPos = pos - 1;
799 if (token && type == ENTITY_REF){
800 name = code;
801 }
802
803 if (code.charAt(0) == '#') {
804 int c =
805 (code.charAt(1) == 'x'
806 ? Integer.parseInt(code.substring(2), 16)
807 : Integer.parseInt(code.substring(1)));
808 push(c);
809 return;
810 }
811
812 String result = (String) entityMap.get(code);
813
814 unresolved = result == null;
815
816 if (unresolved) {
817 if (!token)
818 error("unresolved: &" + code + ";");
819 }
820 else {
821 for (int i = 0; i < result.length(); i++)
822 push(result.charAt(i));
823 }
824 }
825
826 /** types:
827 '<': parse to any token (for nextToken ())
828 '"': parse to quote
829 ' ': parse to whitespace or '>'
830 */
831
Elliott Hughes6bcf32a2009-11-16 21:23:11 -0800832 private final void pushText(int delimiter, boolean resolveEntities, boolean inAttributeValue)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800833 throws IOException, XmlPullParserException {
834
835 int next = peek(0);
836 int cbrCount = 0;
837
838 while (next != -1 && next != delimiter) { // covers eof, '<', '"'
839
840 if (delimiter == ' ')
841 if (next <= ' ' || next == '>')
842 break;
843
Elliott Hughes6bcf32a2009-11-16 21:23:11 -0800844 // BEGIN android-changed: "<" is not allowed in attribute values.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800845 if (next == '&') {
846 if (!resolveEntities)
847 break;
848
849 pushEntity();
850 }
Elliott Hughes6bcf32a2009-11-16 21:23:11 -0800851 else if (next == '<' && inAttributeValue) {
852 error("Illegal: \"<\" inside attribute value");
853 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800854 else if (next == '\n' && type == START_TAG) {
855 read();
856 push(' ');
857 }
858 else
859 push(read());
Elliott Hughes6bcf32a2009-11-16 21:23:11 -0800860 // END android-changed
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800861
Elliott Hughes6bcf32a2009-11-16 21:23:11 -0800862 // BEGIN android-changed: "]]>" *is* allowed in attribute values, but
863 // is not allowed in regular text between markup.
864 final boolean allowCloseCdata = inAttributeValue;
865 if (!allowCloseCdata && (next == '>' && cbrCount >= 2 && delimiter != ']')) {
866 error("Illegal: \"]]>\" outside CDATA section");
867 }
868 // END android-changed
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800869
870 if (next == ']')
871 cbrCount++;
872 else
873 cbrCount = 0;
874
875 next = peek(0);
876 }
877 }
878
879 private final void read(char c)
880 throws IOException, XmlPullParserException {
881 int a = read();
882 if (a != c)
883 error("expected: '" + c + "' actual: '" + ((char) a) + "'");
884 }
885
886 private final int read() throws IOException {
887 int result;
888
889 if (peekCount == 0)
890 result = peek(0);
891 else {
892 result = peek[0];
893 peek[0] = peek[1];
894 }
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700895 // else {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700896 // result = peek[0];
Elliott Hughesd21d78f2010-05-13 11:32:57 -0700897 // System.arraycopy (peek, 1, peek, 0, peekCount-1);
898 // }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800899 peekCount--;
900
901 column++;
902
903 if (result == '\n') {
904
905 line++;
906 column = 1;
907 }
908
909 return result;
910 }
911
912 /** Does never read more than needed */
913
914 private final int peek(int pos) throws IOException {
915
916 while (pos >= peekCount) {
917
918 int nw;
919
920 if (srcBuf.length <= 1)
921 nw = reader.read();
922 else if (srcPos < srcCount)
923 nw = srcBuf[srcPos++];
924 else {
925 srcCount = reader.read(srcBuf, 0, srcBuf.length);
926 if (srcCount <= 0)
927 nw = -1;
928 else
929 nw = srcBuf[0];
930
931 srcPos = 1;
932 }
933
934 if (nw == '\r') {
935 wasCR = true;
936 peek[peekCount++] = '\n';
937 }
938 else {
939 if (nw == '\n') {
940 if (!wasCR)
941 peek[peekCount++] = '\n';
942 }
943 else
944 peek[peekCount++] = nw;
945
946 wasCR = false;
947 }
948 }
949
950 return peek[pos];
951 }
952
953 private final String readName()
954 throws IOException, XmlPullParserException {
955
956 int pos = txtPos;
957 int c = peek(0);
958 if ((c < 'a' || c > 'z')
959 && (c < 'A' || c > 'Z')
960 && c != '_'
961 && c != ':'
962 && c < 0x0c0
963 && !relaxed)
964 error("name expected");
965
966 do {
967 push(read());
968 c = peek(0);
969 }
970 while ((c >= 'a' && c <= 'z')
971 || (c >= 'A' && c <= 'Z')
972 || (c >= '0' && c <= '9')
973 || c == '_'
974 || c == '-'
975 || c == ':'
976 || c == '.'
977 || c >= 0x0b7);
978
979 String result = get(pos);
980 txtPos = pos;
981 return result;
982 }
983
984 private final void skip() throws IOException {
985
986 while (true) {
987 int c = peek(0);
988 if (c > ' ' || c == -1)
989 break;
990 read();
991 }
992 }
993
994 // public part starts here...
995
996 public void setInput(Reader reader) throws XmlPullParserException {
997 this.reader = reader;
998
999 line = 1;
1000 column = 0;
1001 type = START_DOCUMENT;
1002 name = null;
1003 namespace = null;
1004 degenerated = false;
1005 attributeCount = -1;
1006 encoding = null;
1007 version = null;
1008 standalone = null;
1009
1010 if (reader == null)
1011 return;
1012
1013 srcPos = 0;
1014 srcCount = 0;
1015 peekCount = 0;
1016 depth = 0;
1017
1018 entityMap = new Hashtable();
1019 entityMap.put("amp", "&");
1020 entityMap.put("apos", "'");
1021 entityMap.put("gt", ">");
1022 entityMap.put("lt", "<");
1023 entityMap.put("quot", "\"");
1024 }
1025
1026 public void setInput(InputStream is, String _enc)
1027 throws XmlPullParserException {
1028
1029 srcPos = 0;
1030 srcCount = 0;
1031 String enc = _enc;
1032
1033 if (is == null)
1034 throw new IllegalArgumentException();
1035
1036 try {
1037
1038 if (enc == null) {
Elliott Hughesf33eae72010-05-13 12:36:25 -07001039 // read four bytes
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001040
1041 int chk = 0;
1042
1043 while (srcCount < 4) {
1044 int i = is.read();
1045 if (i == -1)
1046 break;
1047 chk = (chk << 8) | i;
1048 srcBuf[srcCount++] = (char) i;
1049 }
1050
1051 if (srcCount == 4) {
1052 switch (chk) {
1053 case 0x00000FEFF :
1054 enc = "UTF-32BE";
1055 srcCount = 0;
1056 break;
1057
1058 case 0x0FFFE0000 :
1059 enc = "UTF-32LE";
1060 srcCount = 0;
1061 break;
1062
1063 case 0x03c :
1064 enc = "UTF-32BE";
1065 srcBuf[0] = '<';
1066 srcCount = 1;
1067 break;
1068
1069 case 0x03c000000 :
1070 enc = "UTF-32LE";
1071 srcBuf[0] = '<';
1072 srcCount = 1;
1073 break;
1074
1075 case 0x0003c003f :
1076 enc = "UTF-16BE";
1077 srcBuf[0] = '<';
1078 srcBuf[1] = '?';
1079 srcCount = 2;
1080 break;
1081
1082 case 0x03c003f00 :
1083 enc = "UTF-16LE";
1084 srcBuf[0] = '<';
1085 srcBuf[1] = '?';
1086 srcCount = 2;
1087 break;
1088
1089 case 0x03c3f786d :
1090 while (true) {
1091 int i = is.read();
1092 if (i == -1)
1093 break;
1094 srcBuf[srcCount++] = (char) i;
1095 if (i == '>') {
1096 String s = new String(srcBuf, 0, srcCount);
1097 int i0 = s.indexOf("encoding");
1098 if (i0 != -1) {
1099 while (s.charAt(i0) != '"'
1100 && s.charAt(i0) != '\'')
1101 i0++;
1102 char deli = s.charAt(i0++);
1103 int i1 = s.indexOf(deli, i0);
1104 enc = s.substring(i0, i1);
1105 }
1106 break;
1107 }
1108 }
1109
1110 default :
1111 if ((chk & 0x0ffff0000) == 0x0FEFF0000) {
1112 enc = "UTF-16BE";
1113 srcBuf[0] =
1114 (char) ((srcBuf[2] << 8) | srcBuf[3]);
1115 srcCount = 1;
1116 }
1117 else if ((chk & 0x0ffff0000) == 0x0fffe0000) {
1118 enc = "UTF-16LE";
1119 srcBuf[0] =
1120 (char) ((srcBuf[3] << 8) | srcBuf[2]);
1121 srcCount = 1;
1122 }
1123 else if ((chk & 0x0ffffff00) == 0x0EFBBBF00) {
1124 enc = "UTF-8";
1125 srcBuf[0] = srcBuf[3];
1126 srcCount = 1;
1127 }
1128 }
1129 }
1130 }
1131
1132 if (enc == null)
1133 enc = "UTF-8";
1134
1135 int sc = srcCount;
1136 setInput(new InputStreamReader(is, enc));
1137 encoding = _enc;
1138 srcCount = sc;
1139 }
1140 catch (Exception e) {
1141 throw new XmlPullParserException(
1142 "Invalid stream or encoding: " + e.toString(),
1143 this,
1144 e);
1145 }
1146 }
1147
1148 public boolean getFeature(String feature) {
1149 if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature))
1150 return processNsp;
1151 else if (isProp(feature, false, "relaxed"))
1152 return relaxed;
1153 else
1154 return false;
1155 }
1156
1157 public String getInputEncoding() {
1158 return encoding;
1159 }
1160
1161 public void defineEntityReplacementText(String entity, String value)
1162 throws XmlPullParserException {
1163 if (entityMap == null)
1164 throw new RuntimeException("entity replacement text must be defined after setInput!");
1165 entityMap.put(entity, value);
1166 }
1167
1168 public Object getProperty(String property) {
1169 if (isProp(property, true, "xmldecl-version"))
1170 return version;
1171 if (isProp(property, true, "xmldecl-standalone"))
1172 return standalone;
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001173 if (isProp(property, true, "location"))
1174 return location != null ? location : reader.toString();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001175 return null;
1176 }
1177
1178 public int getNamespaceCount(int depth) {
1179 if (depth > this.depth)
1180 throw new IndexOutOfBoundsException();
1181 return nspCounts[depth];
1182 }
1183
1184 public String getNamespacePrefix(int pos) {
1185 return nspStack[pos << 1];
1186 }
1187
1188 public String getNamespaceUri(int pos) {
1189 return nspStack[(pos << 1) + 1];
1190 }
1191
1192 public String getNamespace(String prefix) {
1193
1194 if ("xml".equals(prefix))
1195 return "http://www.w3.org/XML/1998/namespace";
1196 if ("xmlns".equals(prefix))
1197 return "http://www.w3.org/2000/xmlns/";
1198
1199 for (int i = (getNamespaceCount(depth) << 1) - 2; i >= 0; i -= 2) {
1200 if (prefix == null) {
1201 if (nspStack[i] == null)
1202 return nspStack[i + 1];
1203 }
1204 else if (prefix.equals(nspStack[i]))
1205 return nspStack[i + 1];
1206 }
1207 return null;
1208 }
1209
1210 public int getDepth() {
1211 return depth;
1212 }
1213
1214 public String getPositionDescription() {
1215
1216 StringBuffer buf =
1217 new StringBuffer(type < TYPES.length ? TYPES[type] : "unknown");
1218 buf.append(' ');
1219
1220 if (type == START_TAG || type == END_TAG) {
1221 if (degenerated)
1222 buf.append("(empty) ");
1223 buf.append('<');
1224 if (type == END_TAG)
1225 buf.append('/');
1226
1227 if (prefix != null)
1228 buf.append("{" + namespace + "}" + prefix + ":");
1229 buf.append(name);
1230
1231 int cnt = attributeCount << 2;
1232 for (int i = 0; i < cnt; i += 4) {
1233 buf.append(' ');
1234 if (attributes[i + 1] != null)
1235 buf.append(
1236 "{" + attributes[i] + "}" + attributes[i + 1] + ":");
1237 buf.append(attributes[i + 2] + "='" + attributes[i + 3] + "'");
1238 }
1239
1240 buf.append('>');
1241 }
1242 else if (type == IGNORABLE_WHITESPACE);
1243 else if (type != TEXT)
1244 buf.append(getText());
1245 else if (isWhitespace)
1246 buf.append("(whitespace)");
1247 else {
1248 String text = getText();
1249 if (text.length() > 16)
1250 text = text.substring(0, 16) + "...";
1251 buf.append(text);
1252 }
1253
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001254 buf.append("@"+line + ":" + column);
1255 if(location != null){
1256 buf.append(" in ");
1257 buf.append(location);
1258 } else if(reader != null){
1259 buf.append(" in ");
1260 buf.append(reader.toString());
1261 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001262 return buf.toString();
1263 }
1264
1265 public int getLineNumber() {
1266 return line;
1267 }
1268
1269 public int getColumnNumber() {
1270 return column;
1271 }
1272
1273 public boolean isWhitespace() throws XmlPullParserException {
1274 if (type != TEXT && type != IGNORABLE_WHITESPACE && type != CDSECT)
1275 exception(ILLEGAL_TYPE);
1276 return isWhitespace;
1277 }
1278
1279 public String getText() {
1280 return type < TEXT
1281 || (type == ENTITY_REF && unresolved) ? null : get(0);
1282 }
1283
1284 public char[] getTextCharacters(int[] poslen) {
1285 if (type >= TEXT) {
1286 if (type == ENTITY_REF) {
1287 poslen[0] = 0;
1288 poslen[1] = name.length();
1289 return name.toCharArray();
1290 }
1291 poslen[0] = 0;
1292 poslen[1] = txtPos;
1293 return txtBuf;
1294 }
1295
1296 poslen[0] = -1;
1297 poslen[1] = -1;
1298 return null;
1299 }
1300
1301 public String getNamespace() {
1302 return namespace;
1303 }
1304
1305 public String getName() {
1306 return name;
1307 }
1308
1309 public String getPrefix() {
1310 return prefix;
1311 }
1312
1313 public boolean isEmptyElementTag() throws XmlPullParserException {
1314 if (type != START_TAG)
1315 exception(ILLEGAL_TYPE);
1316 return degenerated;
1317 }
1318
1319 public int getAttributeCount() {
1320 return attributeCount;
1321 }
1322
1323 public String getAttributeType(int index) {
1324 return "CDATA";
1325 }
1326
1327 public boolean isAttributeDefault(int index) {
1328 return false;
1329 }
1330
1331 public String getAttributeNamespace(int index) {
1332 if (index >= attributeCount)
1333 throw new IndexOutOfBoundsException();
1334 return attributes[index << 2];
1335 }
1336
1337 public String getAttributeName(int index) {
1338 if (index >= attributeCount)
1339 throw new IndexOutOfBoundsException();
1340 return attributes[(index << 2) + 2];
1341 }
1342
1343 public String getAttributePrefix(int index) {
1344 if (index >= attributeCount)
1345 throw new IndexOutOfBoundsException();
1346 return attributes[(index << 2) + 1];
1347 }
1348
1349 public String getAttributeValue(int index) {
1350 if (index >= attributeCount)
1351 throw new IndexOutOfBoundsException();
1352 return attributes[(index << 2) + 3];
1353 }
1354
1355 public String getAttributeValue(String namespace, String name) {
1356
1357 for (int i = (attributeCount << 2) - 4; i >= 0; i -= 4) {
1358 if (attributes[i + 2].equals(name)
1359 && (namespace == null || attributes[i].equals(namespace)))
1360 return attributes[i + 3];
1361 }
1362
1363 return null;
1364 }
1365
1366 public int getEventType() throws XmlPullParserException {
1367 return type;
1368 }
1369
1370 public int next() throws XmlPullParserException, IOException {
1371
1372 txtPos = 0;
1373 isWhitespace = true;
1374 int minType = 9999;
1375 token = false;
1376
1377 do {
1378 nextImpl();
1379 if (type < minType)
1380 minType = type;
Elliott Hughesf33eae72010-05-13 12:36:25 -07001381 // if (curr <= TEXT) type = curr;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001382 }
1383 while (minType > ENTITY_REF // ignorable
1384 || (minType >= TEXT && peekType() >= TEXT));
1385
1386 type = minType;
1387 if (type > TEXT)
1388 type = TEXT;
1389
1390 return type;
1391 }
1392
1393 public int nextToken() throws XmlPullParserException, IOException {
1394
1395 isWhitespace = true;
1396 txtPos = 0;
1397
1398 token = true;
1399 nextImpl();
1400 return type;
1401 }
1402
1403 //
1404 // utility methods to make XML parsing easier ...
1405
1406 public int nextTag() throws XmlPullParserException, IOException {
1407
1408 next();
1409 if (type == TEXT && isWhitespace)
1410 next();
1411
1412 if (type != END_TAG && type != START_TAG)
1413 exception("unexpected type");
1414
1415 return type;
1416 }
1417
1418 public void require(int type, String namespace, String name)
1419 throws XmlPullParserException, IOException {
1420
1421 if (type != this.type
1422 || (namespace != null && !namespace.equals(getNamespace()))
1423 || (name != null && !name.equals(getName())))
1424 exception(
1425 "expected: " + TYPES[type] + " {" + namespace + "}" + name);
1426 }
1427
1428 public String nextText() throws XmlPullParserException, IOException {
1429 if (type != START_TAG)
1430 exception("precondition: START_TAG");
1431
1432 next();
1433
1434 String result;
1435
1436 if (type == TEXT) {
1437 result = getText();
1438 next();
1439 }
1440 else
1441 result = "";
1442
1443 if (type != END_TAG)
1444 exception("END_TAG expected");
1445
1446 return result;
1447 }
1448
1449 public void setFeature(String feature, boolean value)
1450 throws XmlPullParserException {
1451 if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature))
1452 processNsp = value;
1453 else if (isProp(feature, false, "relaxed"))
1454 relaxed = value;
1455 else
1456 exception("unsupported feature: " + feature);
1457 }
1458
1459 public void setProperty(String property, Object value)
1460 throws XmlPullParserException {
Elliott Hughesd21d78f2010-05-13 11:32:57 -07001461 if(isProp(property, true, "location")) {
1462 location = value;
1463 } else {
1464 throw new XmlPullParserException("unsupported property: " + property);
1465 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001466 }
1467
1468 /**
1469 * Skip sub tree that is currently porser positioned on.
1470 * <br>NOTE: parser must be on START_TAG and when funtion returns
Elliott Hughesf33eae72010-05-13 12:36:25 -07001471 * parser will be positioned on corresponding END_TAG.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001472 */
1473
Elliott Hughesf33eae72010-05-13 12:36:25 -07001474 // Implementation copied from Alek's mail...
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001475
1476 public void skipSubTree() throws XmlPullParserException, IOException {
1477 require(START_TAG, null, null);
1478 int level = 1;
1479 while (level > 0) {
1480 int eventType = next();
1481 if (eventType == END_TAG) {
1482 --level;
1483 }
1484 else if (eventType == START_TAG) {
1485 ++level;
1486 }
1487 }
1488 }
1489}