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