View Javadoc
1   /* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
2   /*
3    * Copyright (c) 2003 Extreme! Lab, Indiana University. All rights reserved.
4    *
5    * This software is open source. See the bottom of this file for the licence.
6    */
7   
8   package org.codehaus.plexus.util.xml.pull;
9   
10  import java.io.EOFException;
11  import java.io.IOException;
12  import java.io.InputStreamReader;
13  import java.io.Reader;
14  import java.io.UnsupportedEncodingException;
15  
16  import org.codehaus.plexus.util.ReaderFactory;
17  import org.codehaus.plexus.util.xml.XmlReader;
18  
19  // import java.util.Hashtable;
20  
21  // TODO best handling of interning issues
22  //   have isAllNewStringInterned ???
23  
24  // TODO handling surrogate pairs: http://www.unicode.org/unicode/faq/utf_bom.html#6
25  
26  // TODO review code for use of bufAbsoluteStart when keeping pos between next()/fillBuf()
27  
28  /**
29   * Absolutely minimal implementation of XMLPULL V1 API. Encoding handling done with XmlReader
30   *
31   * @see org.codehaus.plexus.util.xml.XmlReader
32   * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
33   */
34  public class MXParser implements XmlPullParser {
35      // NOTE: no interning of those strings --> by Java leng spec they MUST be already interned
36      private static final String XML_URI = "http://www.w3.org/XML/1998/namespace";
37  
38      private static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
39  
40      private static final String FEATURE_XML_ROUNDTRIP =
41              // "http://xmlpull.org/v1/doc/features.html#xml-roundtrip";
42              "http://xmlpull.org/v1/doc/features.html#xml-roundtrip";
43  
44      private static final String FEATURE_NAMES_INTERNED = "http://xmlpull.org/v1/doc/features.html#names-interned";
45  
46      private static final String PROPERTY_XMLDECL_VERSION = "http://xmlpull.org/v1/doc/properties.html#xmldecl-version";
47  
48      private static final String PROPERTY_XMLDECL_STANDALONE =
49              "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone";
50  
51      private static final String PROPERTY_XMLDECL_CONTENT = "http://xmlpull.org/v1/doc/properties.html#xmldecl-content";
52  
53      private static final String PROPERTY_LOCATION = "http://xmlpull.org/v1/doc/properties.html#location";
54  
55      /**
56       * Implementation notice: the is instance variable that controls if newString() is interning.
57       * <p>
58       * <b>NOTE:</b> newStringIntern <b>always</b> returns interned strings and newString MAY return interned String
59       * depending on this variable.
60       * <p>
61       * <b>NOTE:</b> by default in this minimal implementation it is false!
62       */
63      private boolean allStringsInterned;
64  
65      private void resetStringCache() {
66          // System.out.println("resetStringCache() minimum called");
67      }
68  
69      private String newString(char[] cbuf, int off, int len) {
70          return new String(cbuf, off, len);
71      }
72  
73      private String newStringIntern(char[] cbuf, int off, int len) {
74          return (new String(cbuf, off, len)).intern();
75      }
76  
77      private static final boolean TRACE_SIZING = false;
78  
79      // NOTE: features are not resetable and typically defaults to false ...
80      private boolean processNamespaces;
81  
82      private boolean roundtripSupported;
83  
84      // global parser state
85      private String location;
86  
87      private int lineNumber;
88  
89      private int columnNumber;
90  
91      private boolean seenRoot;
92  
93      private boolean reachedEnd;
94  
95      private int eventType;
96  
97      private boolean emptyElementTag;
98  
99      // element stack
100     private int depth;
101 
102     private char[] elRawName[];
103 
104     private int elRawNameEnd[];
105 
106     private int elRawNameLine[];
107 
108     private String elName[];
109 
110     private String elPrefix[];
111 
112     private String elUri[];
113 
114     // private String elValue[];
115     private int elNamespaceCount[];
116 
117     private String fileEncoding = null;
118 
119     /**
120      * Make sure that we have enough space to keep element stack if passed size. It will always create one additional
121      * slot then current depth
122      */
123     private void ensureElementsCapacity() {
124         final int elStackSize = elName != null ? elName.length : 0;
125         if ((depth + 1) >= elStackSize) {
126             // we add at least one extra slot ...
127             final int newSize = (depth >= 7 ? 2 * depth : 8) + 2; // = lucky 7 + 1 //25
128             if (TRACE_SIZING) {
129                 System.err.println("TRACE_SIZING elStackSize " + elStackSize + " ==> " + newSize);
130             }
131             final boolean needsCopying = elStackSize > 0;
132             String[] arr = null;
133             // resue arr local variable slot
134             arr = new String[newSize];
135             if (needsCopying) System.arraycopy(elName, 0, arr, 0, elStackSize);
136             elName = arr;
137             arr = new String[newSize];
138             if (needsCopying) System.arraycopy(elPrefix, 0, arr, 0, elStackSize);
139             elPrefix = arr;
140             arr = new String[newSize];
141             if (needsCopying) System.arraycopy(elUri, 0, arr, 0, elStackSize);
142             elUri = arr;
143 
144             int[] iarr = new int[newSize];
145             if (needsCopying) {
146                 System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize);
147             } else {
148                 // special initialization
149                 iarr[0] = 0;
150             }
151             elNamespaceCount = iarr;
152 
153             // TODO: avoid using element raw name ...
154             iarr = new int[newSize];
155             if (needsCopying) {
156                 System.arraycopy(elRawNameEnd, 0, iarr, 0, elStackSize);
157             }
158             elRawNameEnd = iarr;
159 
160             iarr = new int[newSize];
161             if (needsCopying) {
162                 System.arraycopy(elRawNameLine, 0, iarr, 0, elStackSize);
163             }
164             elRawNameLine = iarr;
165 
166             final char[][] carr = new char[newSize][];
167             if (needsCopying) {
168                 System.arraycopy(elRawName, 0, carr, 0, elStackSize);
169             }
170             elRawName = carr;
171             // arr = new String[newSize];
172             // if(needsCopying) System.arraycopy(elLocalName, 0, arr, 0, elStackSize);
173             // elLocalName = arr;
174             // arr = new String[newSize];
175             // if(needsCopying) System.arraycopy(elDefaultNs, 0, arr, 0, elStackSize);
176             // elDefaultNs = arr;
177             // int[] iarr = new int[newSize];
178             // if(needsCopying) System.arraycopy(elNsStackPos, 0, iarr, 0, elStackSize);
179             // for (int i = elStackSize; i < iarr.length; i++)
180             // {
181             // iarr[i] = (i > 0) ? -1 : 0;
182             // }
183             // elNsStackPos = iarr;
184             // assert depth < elName.length;
185         }
186     }
187 
188     // attribute stack
189     private int attributeCount;
190 
191     private String attributeName[];
192 
193     private int attributeNameHash[];
194 
195     // private int attributeNameStart[];
196     // private int attributeNameEnd[];
197     private String attributePrefix[];
198 
199     private String attributeUri[];
200 
201     private String attributeValue[];
202     // private int attributeValueStart[];
203     // private int attributeValueEnd[];
204 
205     // Make sure that in attributes temporary array is enough space.
206     private void ensureAttributesCapacity(int size) {
207         final int attrPosSize = attributeName != null ? attributeName.length : 0;
208         if (size >= attrPosSize) {
209             final int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25
210             if (TRACE_SIZING) {
211                 System.err.println("TRACE_SIZING attrPosSize " + attrPosSize + " ==> " + newSize);
212             }
213             final boolean needsCopying = attrPosSize > 0;
214             String[] arr = null;
215 
216             arr = new String[newSize];
217             if (needsCopying) System.arraycopy(attributeName, 0, arr, 0, attrPosSize);
218             attributeName = arr;
219 
220             arr = new String[newSize];
221             if (needsCopying) System.arraycopy(attributePrefix, 0, arr, 0, attrPosSize);
222             attributePrefix = arr;
223 
224             arr = new String[newSize];
225             if (needsCopying) System.arraycopy(attributeUri, 0, arr, 0, attrPosSize);
226             attributeUri = arr;
227 
228             arr = new String[newSize];
229             if (needsCopying) System.arraycopy(attributeValue, 0, arr, 0, attrPosSize);
230             attributeValue = arr;
231 
232             if (!allStringsInterned) {
233                 final int[] iarr = new int[newSize];
234                 if (needsCopying) System.arraycopy(attributeNameHash, 0, iarr, 0, attrPosSize);
235                 attributeNameHash = iarr;
236             }
237 
238             arr = null;
239             // //assert attrUri.length > size
240         }
241     }
242 
243     // namespace stack
244     private int namespaceEnd;
245 
246     private String namespacePrefix[];
247 
248     private int namespacePrefixHash[];
249 
250     private String namespaceUri[];
251 
252     private void ensureNamespacesCapacity(int size) {
253         final int namespaceSize = namespacePrefix != null ? namespacePrefix.length : 0;
254         if (size >= namespaceSize) {
255             final int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25
256             if (TRACE_SIZING) {
257                 System.err.println("TRACE_SIZING namespaceSize " + namespaceSize + " ==> " + newSize);
258             }
259             final String[] newNamespacePrefix = new String[newSize];
260             final String[] newNamespaceUri = new String[newSize];
261             if (namespacePrefix != null) {
262                 System.arraycopy(namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd);
263                 System.arraycopy(namespaceUri, 0, newNamespaceUri, 0, namespaceEnd);
264             }
265             namespacePrefix = newNamespacePrefix;
266             namespaceUri = newNamespaceUri;
267 
268             if (!allStringsInterned) {
269                 final int[] newNamespacePrefixHash = new int[newSize];
270                 if (namespacePrefixHash != null) {
271                     System.arraycopy(namespacePrefixHash, 0, newNamespacePrefixHash, 0, namespaceEnd);
272                 }
273                 namespacePrefixHash = newNamespacePrefixHash;
274             }
275             // prefixesSize = newSize;
276             // //assert nsPrefixes.length > size && nsPrefixes.length == newSize
277         }
278     }
279 
280     // simplistic implementation of hash function that has <b>constant</b> time to compute - so it also means
281     // diminishing hash quality for long strings but for XML parsing it should be good enough ...
282 
283     private static final int fastHash(char ch[], int off, int len) {
284         if (len == 0) return 0;
285         // assert len >0
286         int hash = ch[off]; // hash at beginning
287         // try {
288         hash = (hash << 7) + ch[off + len - 1]; // hash at the end
289         // } catch(ArrayIndexOutOfBoundsException aie) {
290         // aie.printStackTrace(); //should never happen ...
291         // throw new RuntimeException("this is violation of pre-condition");
292         // }
293         if (len > 16) hash = (hash << 7) + ch[off + (len / 4)]; // 1/4 from beginning
294         if (len > 8) hash = (hash << 7) + ch[off + (len / 2)]; // 1/2 of string size ...
295         // notice that hash is at most done 3 times <<7 so shifted by 21 bits 8 bit value
296         // so max result == 29 bits so it is quite just below 31 bits for long (2^32) ...
297         // assert hash >= 0;
298         return hash;
299     }
300 
301     // entity replacement stack
302     private int entityEnd;
303 
304     private String entityName[];
305 
306     private char[] entityNameBuf[];
307 
308     private String entityReplacement[];
309 
310     private char[] entityReplacementBuf[];
311 
312     private int entityNameHash[];
313 
314     private final EntityReplacementMap replacementMapTemplate;
315 
316     private void ensureEntityCapacity() {
317         final int entitySize = entityReplacementBuf != null ? entityReplacementBuf.length : 0;
318         if (entityEnd >= entitySize) {
319             final int newSize = entityEnd > 7 ? 2 * entityEnd : 8; // = lucky 7 + 1 //25
320             if (TRACE_SIZING) {
321                 System.err.println("TRACE_SIZING entitySize " + entitySize + " ==> " + newSize);
322             }
323             final String[] newEntityName = new String[newSize];
324             final char[] newEntityNameBuf[] = new char[newSize][];
325             final String[] newEntityReplacement = new String[newSize];
326             final char[] newEntityReplacementBuf[] = new char[newSize][];
327             if (entityName != null) {
328                 System.arraycopy(entityName, 0, newEntityName, 0, entityEnd);
329                 System.arraycopy(entityNameBuf, 0, newEntityNameBuf, 0, entityEnd);
330                 System.arraycopy(entityReplacement, 0, newEntityReplacement, 0, entityEnd);
331                 System.arraycopy(entityReplacementBuf, 0, newEntityReplacementBuf, 0, entityEnd);
332             }
333             entityName = newEntityName;
334             entityNameBuf = newEntityNameBuf;
335             entityReplacement = newEntityReplacement;
336             entityReplacementBuf = newEntityReplacementBuf;
337 
338             if (!allStringsInterned) {
339                 final int[] newEntityNameHash = new int[newSize];
340                 if (entityNameHash != null) {
341                     System.arraycopy(entityNameHash, 0, newEntityNameHash, 0, entityEnd);
342                 }
343                 entityNameHash = newEntityNameHash;
344             }
345         }
346     }
347 
348     // input buffer management
349     private static final int READ_CHUNK_SIZE = 8 * 1024; // max data chars in one read() call
350 
351     private Reader reader;
352 
353     private String inputEncoding;
354 
355     private int bufLoadFactor = 95; // 99%
356     // private int bufHardLimit; // only matters when expanding
357 
358     private float bufferLoadFactor = bufLoadFactor / 100f;
359 
360     private char buf[] = new char[Runtime.getRuntime().freeMemory() > 1000000L ? READ_CHUNK_SIZE : 256];
361 
362     private int bufSoftLimit = (int) (bufferLoadFactor * buf.length); // desirable size of buffer
363 
364     private boolean preventBufferCompaction;
365 
366     private int bufAbsoluteStart; // this is buf
367 
368     private int bufStart;
369 
370     private int bufEnd;
371 
372     private int pos;
373 
374     private int posStart;
375 
376     private int posEnd;
377 
378     private char pc[] = new char[Runtime.getRuntime().freeMemory() > 1000000L ? READ_CHUNK_SIZE : 64];
379 
380     private int pcStart;
381 
382     private int pcEnd;
383 
384     // parsing state
385     // private boolean needsMore;
386     // private boolean seenMarkup;
387     private boolean usePC;
388 
389     private boolean seenStartTag;
390 
391     private boolean seenEndTag;
392 
393     private boolean pastEndTag;
394 
395     private boolean seenAmpersand;
396 
397     private boolean seenMarkup;
398 
399     private boolean seenDocdecl;
400 
401     // transient variable set during each call to next/Token()
402     private boolean tokenize;
403 
404     private String text;
405 
406     private String entityRefName;
407 
408     private String xmlDeclVersion;
409 
410     private Boolean xmlDeclStandalone;
411 
412     private String xmlDeclContent;
413 
414     private void reset() {
415         // System.out.println("reset() called");
416         location = null;
417         lineNumber = 1;
418         columnNumber = 1;
419         seenRoot = false;
420         reachedEnd = false;
421         eventType = START_DOCUMENT;
422         emptyElementTag = false;
423 
424         depth = 0;
425 
426         attributeCount = 0;
427 
428         namespaceEnd = 0;
429 
430         entityEnd = 0;
431         setupFromTemplate();
432 
433         reader = null;
434         inputEncoding = null;
435 
436         preventBufferCompaction = false;
437         bufAbsoluteStart = 0;
438         bufEnd = bufStart = 0;
439         pos = posStart = posEnd = 0;
440 
441         pcEnd = pcStart = 0;
442 
443         usePC = false;
444 
445         seenStartTag = false;
446         seenEndTag = false;
447         pastEndTag = false;
448         seenAmpersand = false;
449         seenMarkup = false;
450         seenDocdecl = false;
451 
452         xmlDeclVersion = null;
453         xmlDeclStandalone = null;
454         xmlDeclContent = null;
455 
456         resetStringCache();
457     }
458 
459     public MXParser() {
460         replacementMapTemplate = null;
461     }
462 
463     public MXParser(EntityReplacementMap entityReplacementMap) {
464         this.replacementMapTemplate = entityReplacementMap;
465     }
466 
467     public void setupFromTemplate() {
468         if (replacementMapTemplate != null) {
469             int length = replacementMapTemplate.entityEnd;
470 
471             // This is a bit cheeky, since the EntityReplacementMap contains exact-sized arrays,
472             // and elements are always added to the array, we can use the array from the template.
473             // Kids; dont do this at home.
474             entityName = replacementMapTemplate.entityName;
475             entityNameBuf = replacementMapTemplate.entityNameBuf;
476             entityReplacement = replacementMapTemplate.entityReplacement;
477             entityReplacementBuf = replacementMapTemplate.entityReplacementBuf;
478             entityNameHash = replacementMapTemplate.entityNameHash;
479             entityEnd = length;
480         }
481     }
482 
483     /**
484      * Method setFeature
485      *
486      * @param name a String
487      * @param state a boolean
488      * @throws XmlPullParserException issue
489      */
490     @Override
491     public void setFeature(String name, boolean state) throws XmlPullParserException {
492         if (name == null) throw new IllegalArgumentException("feature name should not be null");
493         if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
494             if (eventType != START_DOCUMENT)
495                 throw new XmlPullParserException(
496                         "namespace processing feature can only be changed before parsing", this, null);
497             processNamespaces = state;
498             // } else if(FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
499             // if(type != START_DOCUMENT) throw new XmlPullParserException(
500             // "namespace reporting feature can only be changed before parsing", this, null);
501             // reportNsAttribs = state;
502         } else if (FEATURE_NAMES_INTERNED.equals(name)) {
503             if (state != false) {
504                 throw new XmlPullParserException("interning names in this implementation is not supported");
505             }
506         } else if (FEATURE_PROCESS_DOCDECL.equals(name)) {
507             if (state != false) {
508                 throw new XmlPullParserException("processing DOCDECL is not supported");
509             }
510             // } else if(REPORT_DOCDECL.equals(name)) {
511             // paramNotifyDoctype = state;
512         } else if (FEATURE_XML_ROUNDTRIP.equals(name)) {
513             // if(state == false) {
514             // throw new XmlPullParserException(
515             // "roundtrip feature can not be switched off");
516             // }
517             roundtripSupported = state;
518         } else {
519             throw new XmlPullParserException("unsupported feature " + name);
520         }
521     }
522 
523     /**
524      * Unknown properties are <strong>always</strong> returned as false
525      */
526     @Override
527     public boolean getFeature(String name) {
528         if (name == null) throw new IllegalArgumentException("feature name should not be null");
529         if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
530             return processNamespaces;
531             // } else if(FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
532             // return reportNsAttribs;
533         } else if (FEATURE_NAMES_INTERNED.equals(name)) {
534             return false;
535         } else if (FEATURE_PROCESS_DOCDECL.equals(name)) {
536             return false;
537             // } else if(REPORT_DOCDECL.equals(name)) {
538             // return paramNotifyDoctype;
539         } else if (FEATURE_XML_ROUNDTRIP.equals(name)) {
540             // return true;
541             return roundtripSupported;
542         }
543         return false;
544     }
545 
546     @Override
547     public void setProperty(String name, Object value) throws XmlPullParserException {
548         if (PROPERTY_LOCATION.equals(name)) {
549             location = (String) value;
550         } else {
551             throw new XmlPullParserException("unsupported property: '" + name + "'");
552         }
553     }
554 
555     @Override
556     public Object getProperty(String name) {
557         if (name == null) throw new IllegalArgumentException("property name should not be null");
558         if (PROPERTY_XMLDECL_VERSION.equals(name)) {
559             return xmlDeclVersion;
560         } else if (PROPERTY_XMLDECL_STANDALONE.equals(name)) {
561             return xmlDeclStandalone;
562         } else if (PROPERTY_XMLDECL_CONTENT.equals(name)) {
563             return xmlDeclContent;
564         } else if (PROPERTY_LOCATION.equals(name)) {
565             return location;
566         }
567         return null;
568     }
569 
570     @Override
571     public void setInput(Reader in) throws XmlPullParserException {
572         reset();
573         reader = in;
574 
575         if (reader instanceof XmlReader) {
576             // encoding already detected
577             XmlReader xsr = (XmlReader) reader;
578             fileEncoding = xsr.getEncoding();
579         } else if (reader instanceof InputStreamReader) {
580             InputStreamReader isr = (InputStreamReader) reader;
581             if (isr.getEncoding() != null) {
582                 fileEncoding = isr.getEncoding().toUpperCase();
583             }
584         }
585     }
586 
587     @Override
588     public void setInput(java.io.InputStream inputStream, String inputEncoding) throws XmlPullParserException {
589         if (inputStream == null) {
590             throw new IllegalArgumentException("input stream can not be null");
591         }
592         Reader reader;
593         try {
594             if (inputEncoding != null) {
595                 reader = ReaderFactory.newReader(inputStream, inputEncoding);
596             } else {
597                 reader = ReaderFactory.newXmlReader(inputStream);
598             }
599         } catch (UnsupportedEncodingException une) {
600             throw new XmlPullParserException(
601                     "could not create reader for encoding " + inputEncoding + " : " + une, this, une);
602         } catch (IOException e) {
603             throw new XmlPullParserException("could not create reader : " + e, this, e);
604         }
605         setInput(reader);
606         // must be here as reset() was called in setInput() and has set this.inputEncoding to null ...
607         this.inputEncoding = inputEncoding;
608     }
609 
610     @Override
611     public String getInputEncoding() {
612         return inputEncoding;
613     }
614 
615     @Override
616     public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
617         // throw new XmlPullParserException("not allowed");
618 
619         if (!replacementText.startsWith("&#") && this.entityName != null && replacementText.length() > 1) {
620             String tmp = replacementText.substring(1, replacementText.length() - 1);
621             for (int i = 0; i < this.entityName.length; i++) {
622                 if (this.entityName[i] != null && this.entityName[i].equals(tmp)) {
623                     replacementText = this.entityReplacement[i];
624                 }
625             }
626         }
627 
628         // private char[] entityReplacement[];
629         ensureEntityCapacity();
630 
631         // this is to make sure that if interning works we will take advantage of it ...
632         char[] entityNameCharData = entityName.toCharArray();
633         this.entityName[entityEnd] = newString(entityNameCharData, 0, entityName.length());
634         entityNameBuf[entityEnd] = entityNameCharData;
635 
636         entityReplacement[entityEnd] = replacementText;
637         entityReplacementBuf[entityEnd] = replacementText.toCharArray();
638         if (!allStringsInterned) {
639             entityNameHash[entityEnd] = fastHash(entityNameBuf[entityEnd], 0, entityNameBuf[entityEnd].length);
640         }
641         ++entityEnd;
642         // TODO disallow < or & in entity replacement text (or ]]>???)
643         // TOOD keepEntityNormalizedForAttributeValue cached as well ...
644     }
645 
646     @Override
647     public int getNamespaceCount(int depth) throws XmlPullParserException {
648         if (!processNamespaces || depth == 0) {
649             return 0;
650         }
651         // int maxDepth = eventType == END_TAG ? this.depth + 1 : this.depth;
652         // if(depth < 0 || depth > maxDepth) throw new IllegalArgumentException(
653         if (depth < 0 || depth > this.depth)
654             throw new IllegalArgumentException("namespace count may be for depth 0.." + this.depth + " not " + depth);
655         return elNamespaceCount[depth];
656     }
657 
658     @Override
659     public String getNamespacePrefix(int pos) throws XmlPullParserException {
660 
661         // int end = eventType == END_TAG ? elNamespaceCount[ depth + 1 ] : namespaceEnd;
662         // if(pos < end) {
663         if (pos < namespaceEnd) {
664             return namespacePrefix[pos];
665         } else {
666             throw new XmlPullParserException(
667                     "position " + pos + " exceeded number of available namespaces " + namespaceEnd);
668         }
669     }
670 
671     @Override
672     public String getNamespaceUri(int pos) throws XmlPullParserException {
673         // int end = eventType == END_TAG ? elNamespaceCount[ depth + 1 ] : namespaceEnd;
674         // if(pos < end) {
675         if (pos < namespaceEnd) {
676             return namespaceUri[pos];
677         } else {
678             throw new XmlPullParserException(
679                     "position " + pos + " exceeded number of available namespaces " + namespaceEnd);
680         }
681     }
682 
683     @Override
684     public String getNamespace(String prefix)
685                 // throws XmlPullParserException
686             {
687         // int count = namespaceCount[ depth ];
688         if (prefix != null) {
689             for (int i = namespaceEnd - 1; i >= 0; i--) {
690                 if (prefix.equals(namespacePrefix[i])) {
691                     return namespaceUri[i];
692                 }
693             }
694             if ("xml".equals(prefix)) {
695                 return XML_URI;
696             } else if ("xmlns".equals(prefix)) {
697                 return XMLNS_URI;
698             }
699         } else {
700             for (int i = namespaceEnd - 1; i >= 0; i--) {
701                 if (namespacePrefix[i] == null) { // "") { //null ) { //TODO check FIXME Alek
702                     return namespaceUri[i];
703                 }
704             }
705         }
706         return null;
707     }
708 
709     @Override
710     public int getDepth() {
711         return depth;
712     }
713 
714     private static int findFragment(int bufMinPos, char[] b, int start, int end) {
715         // System.err.println("bufStart="+bufStart+" b="+printable(new String(b, start, end - start))+" start="+start+"
716         // end="+end);
717         if (start < bufMinPos) {
718             start = bufMinPos;
719             if (start > end) start = end;
720             return start;
721         }
722         if (end - start > 65) {
723             start = end - 10; // try to find good location
724         }
725         int i = start + 1;
726         while (--i > bufMinPos) {
727             if ((end - i) > 65) break;
728             final char c = b[i];
729             if (c == '<' && (start - i) > 10) break;
730         }
731         return i;
732     }
733 
734     /**
735      * Return string describing current position of parsers as text 'STATE [seen %s...] @line:column'.
736      */
737     @Override
738     public String getPositionDescription() {
739         String fragment = null;
740         if (posStart <= pos) {
741             final int start = findFragment(0, buf, posStart, pos);
742             // System.err.println("start="+start);
743             if (start < pos) {
744                 fragment = new String(buf, start, pos - start);
745             }
746             if (bufAbsoluteStart > 0 || start > 0) fragment = "..." + fragment;
747         }
748         // return " at line "+tokenizerPosRow
749         // +" and column "+(tokenizerPosCol-1)
750         // +(fragment != null ? " seen "+printable(fragment)+"..." : "");
751         return " " + TYPES[eventType] + (fragment != null ? " seen " + printable(fragment) + "..." : "") + " "
752                 + (location != null ? location : "") + "@" + getLineNumber() + ":" + getColumnNumber();
753     }
754 
755     @Override
756     public int getLineNumber() {
757         return lineNumber;
758     }
759 
760     @Override
761     public int getColumnNumber() {
762         return columnNumber;
763     }
764 
765     @Override
766     public boolean isWhitespace() throws XmlPullParserException {
767         if (eventType == TEXT || eventType == CDSECT) {
768             if (usePC) {
769                 for (int i = pcStart; i < pcEnd; i++) {
770                     if (!isS(pc[i])) return false;
771                 }
772                 return true;
773             } else {
774                 for (int i = posStart; i < posEnd; i++) {
775                     if (!isS(buf[i])) return false;
776                 }
777                 return true;
778             }
779         } else if (eventType == IGNORABLE_WHITESPACE) {
780             return true;
781         }
782         throw new XmlPullParserException("no content available to check for whitespaces");
783     }
784 
785     @Override
786     public String getText() {
787         if (eventType == START_DOCUMENT || eventType == END_DOCUMENT) {
788             // throw new XmlPullParserException("no content available to read");
789             // if(roundtripSupported) {
790             // text = new String(buf, posStart, posEnd - posStart);
791             // } else {
792             return null;
793             // }
794         } else if (eventType == ENTITY_REF) {
795             return text;
796         }
797         if (text == null) {
798             if (!usePC || eventType == START_TAG || eventType == END_TAG) {
799                 text = new String(buf, posStart, posEnd - posStart);
800             } else {
801                 text = new String(pc, pcStart, pcEnd - pcStart);
802             }
803         }
804         return text;
805     }
806 
807     @Override
808     public char[] getTextCharacters(int[] holderForStartAndLength) {
809         if (eventType == TEXT) {
810             if (usePC) {
811                 holderForStartAndLength[0] = pcStart;
812                 holderForStartAndLength[1] = pcEnd - pcStart;
813                 return pc;
814             } else {
815                 holderForStartAndLength[0] = posStart;
816                 holderForStartAndLength[1] = posEnd - posStart;
817                 return buf;
818             }
819         } else if (eventType == START_TAG
820                 || eventType == END_TAG
821                 || eventType == CDSECT
822                 || eventType == COMMENT
823                 || eventType == ENTITY_REF
824                 || eventType == PROCESSING_INSTRUCTION
825                 || eventType == IGNORABLE_WHITESPACE
826                 || eventType == DOCDECL) {
827             holderForStartAndLength[0] = posStart;
828             holderForStartAndLength[1] = posEnd - posStart;
829             return buf;
830         } else if (eventType == START_DOCUMENT || eventType == END_DOCUMENT) {
831             // throw new XmlPullParserException("no content available to read");
832             holderForStartAndLength[0] = holderForStartAndLength[1] = -1;
833             return null;
834         } else {
835             throw new IllegalArgumentException("unknown text eventType: " + eventType);
836         }
837         // String s = getText();
838         // char[] cb = null;
839         // if(s!= null) {
840         // cb = s.toCharArray();
841         // holderForStartAndLength[0] = 0;
842         // holderForStartAndLength[1] = s.length();
843         // } else {
844         // }
845         // return cb;
846     }
847 
848     @Override
849     public String getNamespace() {
850         if (eventType == START_TAG) {
851             // return processNamespaces ? elUri[ depth - 1 ] : NO_NAMESPACE;
852             return processNamespaces ? elUri[depth] : NO_NAMESPACE;
853         } else if (eventType == END_TAG) {
854             return processNamespaces ? elUri[depth] : NO_NAMESPACE;
855         }
856         return null;
857         // String prefix = elPrefix[ maxDepth ];
858         // if(prefix != null) {
859         // for( int i = namespaceEnd -1; i >= 0; i--) {
860         // if( prefix.equals( namespacePrefix[ i ] ) ) {
861         // return namespaceUri[ i ];
862         // }
863         // }
864         // } else {
865         // for( int i = namespaceEnd -1; i >= 0; i--) {
866         // if( namespacePrefix[ i ] == null ) {
867         // return namespaceUri[ i ];
868         // }
869         // }
870         //
871         // }
872         // return "";
873     }
874 
875     @Override
876     public String getName() {
877         if (eventType == START_TAG) {
878             // return elName[ depth - 1 ] ;
879             return elName[depth];
880         } else if (eventType == END_TAG) {
881             return elName[depth];
882         } else if (eventType == ENTITY_REF) {
883             if (entityRefName == null) {
884                 entityRefName = newString(buf, posStart, posEnd - posStart);
885             }
886             return entityRefName;
887         } else {
888             return null;
889         }
890     }
891 
892     @Override
893     public String getPrefix() {
894         if (eventType == START_TAG) {
895             // return elPrefix[ depth - 1 ] ;
896             return elPrefix[depth];
897         } else if (eventType == END_TAG) {
898             return elPrefix[depth];
899         }
900         return null;
901         // if(eventType != START_TAG && eventType != END_TAG) return null;
902         // int maxDepth = eventType == END_TAG ? depth : depth - 1;
903         // return elPrefix[ maxDepth ];
904     }
905 
906     @Override
907     public boolean isEmptyElementTag() throws XmlPullParserException {
908         if (eventType != START_TAG)
909             throw new XmlPullParserException("parser must be on START_TAG to check for empty element", this, null);
910         return emptyElementTag;
911     }
912 
913     @Override
914     public int getAttributeCount() {
915         if (eventType != START_TAG) return -1;
916         return attributeCount;
917     }
918 
919     @Override
920     public String getAttributeNamespace(int index) {
921         if (eventType != START_TAG) throw new IndexOutOfBoundsException("only START_TAG can have attributes");
922         if (!processNamespaces) return NO_NAMESPACE;
923         if (index < 0 || index >= attributeCount)
924             throw new IndexOutOfBoundsException(
925                     "attribute position must be 0.." + (attributeCount - 1) + " and not " + index);
926         return attributeUri[index];
927     }
928 
929     @Override
930     public String getAttributeName(int index) {
931         if (eventType != START_TAG) throw new IndexOutOfBoundsException("only START_TAG can have attributes");
932         if (index < 0 || index >= attributeCount)
933             throw new IndexOutOfBoundsException(
934                     "attribute position must be 0.." + (attributeCount - 1) + " and not " + index);
935         return attributeName[index];
936     }
937 
938     @Override
939     public String getAttributePrefix(int index) {
940         if (eventType != START_TAG) throw new IndexOutOfBoundsException("only START_TAG can have attributes");
941         if (!processNamespaces) return null;
942         if (index < 0 || index >= attributeCount)
943             throw new IndexOutOfBoundsException(
944                     "attribute position must be 0.." + (attributeCount - 1) + " and not " + index);
945         return attributePrefix[index];
946     }
947 
948     @Override
949     public String getAttributeType(int index) {
950         if (eventType != START_TAG) throw new IndexOutOfBoundsException("only START_TAG can have attributes");
951         if (index < 0 || index >= attributeCount)
952             throw new IndexOutOfBoundsException(
953                     "attribute position must be 0.." + (attributeCount - 1) + " and not " + index);
954         return "CDATA";
955     }
956 
957     @Override
958     public boolean isAttributeDefault(int index) {
959         if (eventType != START_TAG) throw new IndexOutOfBoundsException("only START_TAG can have attributes");
960         if (index < 0 || index >= attributeCount)
961             throw new IndexOutOfBoundsException(
962                     "attribute position must be 0.." + (attributeCount - 1) + " and not " + index);
963         return false;
964     }
965 
966     @Override
967     public String getAttributeValue(int index) {
968         if (eventType != START_TAG) throw new IndexOutOfBoundsException("only START_TAG can have attributes");
969         if (index < 0 || index >= attributeCount)
970             throw new IndexOutOfBoundsException(
971                     "attribute position must be 0.." + (attributeCount - 1) + " and not " + index);
972         return attributeValue[index];
973     }
974 
975     @Override
976     public String getAttributeValue(String namespace, String name) {
977         if (eventType != START_TAG)
978             throw new IndexOutOfBoundsException("only START_TAG can have attributes" + getPositionDescription());
979         if (name == null) {
980             throw new IllegalArgumentException("attribute name can not be null");
981         }
982         // TODO make check if namespace is interned!!! etc. for names!!!
983         if (processNamespaces) {
984             if (namespace == null) {
985                 namespace = "";
986             }
987 
988             for (int i = 0; i < attributeCount; ++i) {
989                 if ((namespace == attributeUri[i] || namespace.equals(attributeUri[i]))
990                         // (namespace != null && namespace.equals(attributeUri[ i ]))
991                         // taking advantage of String.intern()
992                         && name.equals(attributeName[i])) {
993                     return attributeValue[i];
994                 }
995             }
996         } else {
997             if (namespace != null && namespace.length() == 0) {
998                 namespace = null;
999             }
1000             if (namespace != null)
1001                 throw new IllegalArgumentException(
1002                         "when namespaces processing is disabled attribute namespace must be null");
1003             for (int i = 0; i < attributeCount; ++i) {
1004                 if (name.equals(attributeName[i])) {
1005                     return attributeValue[i];
1006                 }
1007             }
1008         }
1009         return null;
1010     }
1011 
1012     @Override
1013     public int getEventType() throws XmlPullParserException {
1014         return eventType;
1015     }
1016 
1017     @Override
1018     public void require(int type, String namespace, String name) throws XmlPullParserException, IOException {
1019         if (!processNamespaces && namespace != null) {
1020             throw new XmlPullParserException("processing namespaces must be enabled on parser (or factory)"
1021                     + " to have possible namespaces declared on elements" + (" (position:" + getPositionDescription())
1022                     + ")");
1023         }
1024         if (type != getEventType()
1025                 || (namespace != null && !namespace.equals(getNamespace()))
1026                 || (name != null && !name.equals(getName()))) {
1027             throw new XmlPullParserException("expected event " + TYPES[type]
1028                     + (name != null ? " with name '" + name + "'" : "")
1029                     + (namespace != null && name != null ? " and" : "")
1030                     + (namespace != null ? " with namespace '" + namespace + "'" : "") + " but got"
1031                     + (type != getEventType() ? " " + TYPES[getEventType()] : "")
1032                     + (name != null && getName() != null && !name.equals(getName()) ? " name '" + getName() + "'" : "")
1033                     + (namespace != null
1034                                     && name != null
1035                                     && getName() != null
1036                                     && !name.equals(getName())
1037                                     && getNamespace() != null
1038                                     && !namespace.equals(getNamespace())
1039                             ? " and"
1040                             : "")
1041                     + (namespace != null && getNamespace() != null && !namespace.equals(getNamespace())
1042                             ? " namespace '" + getNamespace() + "'"
1043                             : "")
1044                     + (" (position:" + getPositionDescription()) + ")");
1045         }
1046     }
1047 
1048     /**
1049      * <p>Skip sub tree that is currently parser positioned on.</p>
1050      * NOTE: parser must be on START_TAG and when function returns parser will be positioned on corresponding END_TAG
1051      * @throws XmlPullParserException issue
1052      * @throws IOException io
1053      */
1054     public void skipSubTree() throws XmlPullParserException, IOException {
1055         require(START_TAG, null, null);
1056         int level = 1;
1057         while (level > 0) {
1058             int eventType = next();
1059             if (eventType == END_TAG) {
1060                 --level;
1061             } else if (eventType == START_TAG) {
1062                 ++level;
1063             }
1064         }
1065     }
1066 
1067     // public String readText() throws XmlPullParserException, IOException
1068     // {
1069     // if (getEventType() != TEXT) return "";
1070     // String result = getText();
1071     // next();
1072     // return result;
1073     // }
1074 
1075     @Override
1076     public String nextText() throws XmlPullParserException, IOException {
1077         // String result = null;
1078         // boolean onStartTag = false;
1079         // if(eventType == START_TAG) {
1080         // onStartTag = true;
1081         // next();
1082         // }
1083         // if(eventType == TEXT) {
1084         // result = getText();
1085         // next();
1086         // } else if(onStartTag && eventType == END_TAG) {
1087         // result = "";
1088         // } else {
1089         // throw new XmlPullParserException(
1090         // "parser must be on START_TAG or TEXT to read text", this, null);
1091         // }
1092         // if(eventType != END_TAG) {
1093         // throw new XmlPullParserException(
1094         // "event TEXT it must be immediately followed by END_TAG", this, null);
1095         // }
1096         // return result;
1097         if (getEventType() != START_TAG) {
1098             throw new XmlPullParserException("parser must be on START_TAG to read next text", this, null);
1099         }
1100         int eventType = next();
1101         if (eventType == TEXT) {
1102             final String result = getText();
1103             eventType = next();
1104             if (eventType != END_TAG) {
1105                 throw new XmlPullParserException(
1106                         "TEXT must be immediately followed by END_TAG and not " + TYPES[getEventType()], this, null);
1107             }
1108             return result;
1109         } else if (eventType == END_TAG) {
1110             return "";
1111         } else {
1112             throw new XmlPullParserException("parser must be on START_TAG or TEXT to read text", this, null);
1113         }
1114     }
1115 
1116     @Override
1117     public int nextTag() throws XmlPullParserException, IOException {
1118         next();
1119         if (eventType == TEXT && isWhitespace()) { // skip whitespace
1120             next();
1121         }
1122         if (eventType != START_TAG && eventType != END_TAG) {
1123             throw new XmlPullParserException("expected START_TAG or END_TAG not " + TYPES[getEventType()], this, null);
1124         }
1125         return eventType;
1126     }
1127 
1128     @Override
1129     public int next() throws XmlPullParserException, IOException {
1130         tokenize = false;
1131         return nextImpl();
1132     }
1133 
1134     @Override
1135     public int nextToken() throws XmlPullParserException, IOException {
1136         tokenize = true;
1137         return nextImpl();
1138     }
1139 
1140     private int nextImpl() throws XmlPullParserException, IOException {
1141         text = null;
1142         pcEnd = pcStart = 0;
1143         usePC = false;
1144         bufStart = posEnd;
1145         if (pastEndTag) {
1146             pastEndTag = false;
1147             --depth;
1148             namespaceEnd = elNamespaceCount[depth]; // less namespaces available
1149         }
1150         if (emptyElementTag) {
1151             emptyElementTag = false;
1152             pastEndTag = true;
1153             return eventType = END_TAG;
1154         }
1155 
1156         // [1] document ::= prolog element Misc*
1157         if (depth > 0) {
1158 
1159             if (seenStartTag) {
1160                 seenStartTag = false;
1161                 return eventType = parseStartTag();
1162             }
1163             if (seenEndTag) {
1164                 seenEndTag = false;
1165                 return eventType = parseEndTag();
1166             }
1167 
1168             // ASSUMPTION: we are _on_ first character of content or markup!!!!
1169             // [43] content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)*
1170             char ch;
1171             if (seenMarkup) { // we have read ahead ...
1172                 seenMarkup = false;
1173                 ch = '<';
1174             } else if (seenAmpersand) {
1175                 seenAmpersand = false;
1176                 ch = '&';
1177             } else {
1178                 ch = more();
1179             }
1180             posStart = pos - 1; // VERY IMPORTANT: this is correct start of event!!!
1181 
1182             // when true there is some potential event TEXT to return - keep gathering
1183             boolean hadCharData = false;
1184 
1185             // when true TEXT data is not continuous (like <![CDATA[text]]>) and requires PC merging
1186             boolean needsMerging = false;
1187 
1188             MAIN_LOOP:
1189             while (true) {
1190                 // work on MARKUP
1191                 if (ch == '<') {
1192                     if (hadCharData) {
1193                         // posEnd = pos - 1;
1194                         if (tokenize) {
1195                             seenMarkup = true;
1196                             return eventType = TEXT;
1197                         }
1198                     }
1199                     ch = more();
1200                     if (ch == '/') {
1201                         if (!tokenize && hadCharData) {
1202                             seenEndTag = true;
1203                             // posEnd = pos - 2;
1204                             return eventType = TEXT;
1205                         }
1206                         return eventType = parseEndTag();
1207                     } else if (ch == '!') {
1208                         ch = more();
1209                         if (ch == '-') {
1210                             // note: if(tokenize == false) posStart/End is NOT changed!!!!
1211                             parseComment();
1212                             if (tokenize) return eventType = COMMENT;
1213                             if (!usePC && hadCharData) {
1214                                 needsMerging = true;
1215                             } else {
1216                                 posStart = pos; // completely ignore comment
1217                             }
1218                         } else if (ch == '[') {
1219                             // posEnd = pos - 3;
1220                             // must remember previous posStart/End as it merges with content of CDATA
1221                             // int oldStart = posStart + bufAbsoluteStart;
1222                             // int oldEnd = posEnd + bufAbsoluteStart;
1223                             parseCDSect(hadCharData);
1224                             if (tokenize) return eventType = CDSECT;
1225                             final int cdStart = posStart;
1226                             final int cdEnd = posEnd;
1227                             final int cdLen = cdEnd - cdStart;
1228 
1229                             if (cdLen > 0) { // was there anything inside CDATA section?
1230                                 hadCharData = true;
1231                                 if (!usePC) {
1232                                     needsMerging = true;
1233                                 }
1234                             }
1235 
1236                             // posStart = oldStart;
1237                             // posEnd = oldEnd;
1238                             // if(cdLen > 0) { // was there anything inside CDATA section?
1239                             // if(hadCharData) {
1240                             // // do merging if there was anything in CDSect!!!!
1241                             // // if(!usePC) {
1242                             // // // posEnd is correct already!!!
1243                             // // if(posEnd > posStart) {
1244                             // // joinPC();
1245                             // // } else {
1246                             // // usePC = true;
1247                             // // pcStart = pcEnd = 0;
1248                             // // }
1249                             // // }
1250                             // // if(pcEnd + cdLen >= pc.length) ensurePC(pcEnd + cdLen);
1251                             // // // copy [cdStart..cdEnd) into PC
1252                             // // System.arraycopy(buf, cdStart, pc, pcEnd, cdLen);
1253                             // // pcEnd += cdLen;
1254                             // if(!usePC) {
1255                             // needsMerging = true;
1256                             // posStart = cdStart;
1257                             // posEnd = cdEnd;
1258                             // }
1259                             // } else {
1260                             // if(!usePC) {
1261                             // needsMerging = true;
1262                             // posStart = cdStart;
1263                             // posEnd = cdEnd;
1264                             // hadCharData = true;
1265                             // }
1266                             // }
1267                             // //hadCharData = true;
1268                             // } else {
1269                             // if( !usePC && hadCharData ) {
1270                             // needsMerging = true;
1271                             // }
1272                             // }
1273                         } else {
1274                             throw new XmlPullParserException(
1275                                     "unexpected character in markup " + printable(ch), this, null);
1276                         }
1277                     } else if (ch == '?') {
1278                         parsePI();
1279                         if (tokenize) return eventType = PROCESSING_INSTRUCTION;
1280                         if (!usePC && hadCharData) {
1281                             needsMerging = true;
1282                         } else {
1283                             posStart = pos; // completely ignore PI
1284                         }
1285 
1286                     } else if (isNameStartChar(ch)) {
1287                         if (!tokenize && hadCharData) {
1288                             seenStartTag = true;
1289                             // posEnd = pos - 2;
1290                             return eventType = TEXT;
1291                         }
1292                         return eventType = parseStartTag();
1293                     } else {
1294                         throw new XmlPullParserException("unexpected character in markup " + printable(ch), this, null);
1295                     }
1296                     // do content compaction if it makes sense!!!!
1297 
1298                 } else if (ch == '&') {
1299                     // work on ENTITY
1300                     // posEnd = pos - 1;
1301                     if (tokenize && hadCharData) {
1302                         seenAmpersand = true;
1303                         return eventType = TEXT;
1304                     }
1305                     final int oldStart = posStart + bufAbsoluteStart;
1306                     final int oldEnd = posEnd + bufAbsoluteStart;
1307                     parseEntityRef();
1308                     if (tokenize) return eventType = ENTITY_REF;
1309                     // check if replacement text can be resolved !!!
1310                     if (resolvedEntityRefCharBuf == BUF_NOT_RESOLVED) {
1311                         if (entityRefName == null) {
1312                             entityRefName = newString(buf, posStart, posEnd - posStart);
1313                         }
1314                         throw new XmlPullParserException(
1315                                 "could not resolve entity named '" + printable(entityRefName) + "'", this, null);
1316                     }
1317                     // int entStart = posStart;
1318                     // int entEnd = posEnd;
1319                     posStart = oldStart - bufAbsoluteStart;
1320                     posEnd = oldEnd - bufAbsoluteStart;
1321                     if (!usePC) {
1322                         if (hadCharData) {
1323                             joinPC(); // posEnd is already set correctly!!!
1324                             needsMerging = false;
1325                         } else {
1326                             usePC = true;
1327                             pcStart = pcEnd = 0;
1328                         }
1329                     }
1330                     // assert usePC == true;
1331                     // write into PC replacement text - do merge for replacement text!!!!
1332                     for (char aResolvedEntity : resolvedEntityRefCharBuf) {
1333                         if (pcEnd >= pc.length) {
1334                             ensurePC(pcEnd);
1335                         }
1336                         pc[pcEnd++] = aResolvedEntity;
1337                     }
1338                     hadCharData = true;
1339                     // assert needsMerging == false;
1340                 } else {
1341 
1342                     if (needsMerging) {
1343                         // assert usePC == false;
1344                         joinPC(); // posEnd is already set correctly!!!
1345                         // posStart = pos - 1;
1346                         needsMerging = false;
1347                     }
1348 
1349                     // no MARKUP not ENTITIES so work on character data ...
1350 
1351                     // [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)
1352 
1353                     hadCharData = true;
1354 
1355                     boolean normalizedCR = false;
1356                     final boolean normalizeInput = !tokenize || !roundtripSupported;
1357                     // use loop locality here!!!!
1358                     boolean seenBracket = false;
1359                     boolean seenBracketBracket = false;
1360                     do {
1361 
1362                         // check that ]]> does not show in
1363                         if (ch == ']') {
1364                             if (seenBracket) {
1365                                 seenBracketBracket = true;
1366                             } else {
1367                                 seenBracket = true;
1368                             }
1369                         } else if (seenBracketBracket && ch == '>') {
1370                             throw new XmlPullParserException("characters ]]> are not allowed in content", this, null);
1371                         } else {
1372                             if (seenBracket) {
1373                                 seenBracketBracket = seenBracket = false;
1374                             }
1375                             // assert seenTwoBrackets == seenBracket == false;
1376                         }
1377                         if (normalizeInput) {
1378                             // deal with normalization issues ...
1379                             if (ch == '\r') {
1380                                 normalizedCR = true;
1381                                 posEnd = pos - 1;
1382                                 // posEnd is already set
1383                                 if (!usePC) {
1384                                     if (posEnd > posStart) {
1385                                         joinPC();
1386                                     } else {
1387                                         usePC = true;
1388                                         pcStart = pcEnd = 0;
1389                                     }
1390                                 }
1391                                 // assert usePC == true;
1392                                 if (pcEnd >= pc.length) ensurePC(pcEnd);
1393                                 pc[pcEnd++] = '\n';
1394                             } else if (ch == '\n') {
1395                                 // if(!usePC) { joinPC(); } else { if(pcEnd >= pc.length) ensurePC(); }
1396                                 if (!normalizedCR && usePC) {
1397                                     if (pcEnd >= pc.length) ensurePC(pcEnd);
1398                                     pc[pcEnd++] = '\n';
1399                                 }
1400                                 normalizedCR = false;
1401                             } else {
1402                                 if (usePC) {
1403                                     if (pcEnd >= pc.length) ensurePC(pcEnd);
1404                                     pc[pcEnd++] = ch;
1405                                 }
1406                                 normalizedCR = false;
1407                             }
1408                         }
1409 
1410                         ch = more();
1411                     } while (ch != '<' && ch != '&');
1412                     posEnd = pos - 1;
1413                     continue MAIN_LOOP; // skip ch = more() from below - we are already ahead ...
1414                 }
1415                 ch = more();
1416             } // endless while(true)
1417         } else {
1418             if (seenRoot) {
1419                 return parseEpilog();
1420             } else {
1421                 return parseProlog();
1422             }
1423         }
1424     }
1425 
1426     private int parseProlog() throws XmlPullParserException, IOException {
1427         // [2] prolog: ::= XMLDecl? Misc* (doctypedecl Misc*)? and look for [39] element
1428 
1429         char ch;
1430         if (seenMarkup) {
1431             ch = buf[pos - 1];
1432         } else {
1433             ch = more();
1434         }
1435 
1436         if (eventType == START_DOCUMENT) {
1437             // bootstrap parsing with getting first character input!
1438             // deal with BOM
1439             // detect BOM and crop it (Unicode int Order Mark)
1440             if (ch == '\uFFFE') {
1441                 throw new XmlPullParserException(
1442                         "first character in input was UNICODE noncharacter (0xFFFE)" + "- input requires int swapping",
1443                         this,
1444                         null);
1445             }
1446             if (ch == '\uFEFF') {
1447                 // skipping UNICODE int Order Mark (so called BOM)
1448                 ch = more();
1449             } else if (ch == '\uFFFD') {
1450                 // UTF-16 BOM in an UTF-8 encoded file?
1451                 // This is a hack...not the best way to check for BOM in UTF-16
1452                 ch = more();
1453                 if (ch == '\uFFFD') {
1454                     throw new XmlPullParserException("UTF-16 BOM in a UTF-8 encoded file is incompatible", this, null);
1455                 }
1456             }
1457         }
1458         seenMarkup = false;
1459         boolean gotS = false;
1460         posStart = pos - 1;
1461         final boolean normalizeIgnorableWS = tokenize && !roundtripSupported;
1462         boolean normalizedCR = false;
1463         while (true) {
1464             // deal with Misc
1465             // [27] Misc ::= Comment | PI | S
1466             // deal with docdecl --> mark it!
1467             // else parseStartTag seen <[^/]
1468             if (ch == '<') {
1469                 if (gotS && tokenize) {
1470                     posEnd = pos - 1;
1471                     seenMarkup = true;
1472                     return eventType = IGNORABLE_WHITESPACE;
1473                 }
1474                 ch = more();
1475                 if (ch == '?') {
1476                     // check if it is 'xml'
1477                     // deal with XMLDecl
1478                     boolean isXMLDecl = parsePI();
1479                     if (tokenize) {
1480                         if (isXMLDecl) {
1481                             return eventType = START_DOCUMENT;
1482                         }
1483                         return eventType = PROCESSING_INSTRUCTION;
1484                     }
1485                 } else if (ch == '!') {
1486                     ch = more();
1487                     if (ch == 'D') {
1488                         if (seenDocdecl) {
1489                             throw new XmlPullParserException("only one docdecl allowed in XML document", this, null);
1490                         }
1491                         seenDocdecl = true;
1492                         parseDocdecl();
1493                         if (tokenize) return eventType = DOCDECL;
1494                     } else if (ch == '-') {
1495                         parseComment();
1496                         if (tokenize) return eventType = COMMENT;
1497                     } else {
1498                         throw new XmlPullParserException("unexpected markup <!" + printable(ch), this, null);
1499                     }
1500                 } else if (ch == '/') {
1501                     throw new XmlPullParserException("expected start tag name and not " + printable(ch), this, null);
1502                 } else if (isNameStartChar(ch)) {
1503                     seenRoot = true;
1504                     return parseStartTag();
1505                 } else {
1506                     throw new XmlPullParserException("expected start tag name and not " + printable(ch), this, null);
1507                 }
1508             } else if (isS(ch)) {
1509                 gotS = true;
1510                 if (normalizeIgnorableWS) {
1511                     if (ch == '\r') {
1512                         normalizedCR = true;
1513                         // posEnd = pos -1;
1514                         // joinPC();
1515                         // posEnd is already set
1516                         if (!usePC) {
1517                             posEnd = pos - 1;
1518                             if (posEnd > posStart) {
1519                                 joinPC();
1520                             } else {
1521                                 usePC = true;
1522                                 pcStart = pcEnd = 0;
1523                             }
1524                         }
1525                         // assert usePC == true;
1526                         if (pcEnd >= pc.length) ensurePC(pcEnd);
1527                         pc[pcEnd++] = '\n';
1528                     } else if (ch == '\n') {
1529                         if (!normalizedCR && usePC) {
1530                             if (pcEnd >= pc.length) ensurePC(pcEnd);
1531                             pc[pcEnd++] = '\n';
1532                         }
1533                         normalizedCR = false;
1534                     } else {
1535                         if (usePC) {
1536                             if (pcEnd >= pc.length) ensurePC(pcEnd);
1537                             pc[pcEnd++] = ch;
1538                         }
1539                         normalizedCR = false;
1540                     }
1541                 }
1542             } else {
1543                 throw new XmlPullParserException(
1544                         "only whitespace content allowed before start tag and not " + printable(ch), this, null);
1545             }
1546             ch = more();
1547         }
1548     }
1549 
1550     private int parseEpilog() throws XmlPullParserException, IOException {
1551         if (eventType == END_DOCUMENT) {
1552             throw new XmlPullParserException("already reached end of XML input", this, null);
1553         }
1554         if (reachedEnd) {
1555             return eventType = END_DOCUMENT;
1556         }
1557         boolean gotS = false;
1558         final boolean normalizeIgnorableWS = tokenize && !roundtripSupported;
1559         boolean normalizedCR = false;
1560         try {
1561             // epilog: Misc*
1562             char ch;
1563             if (seenMarkup) {
1564                 ch = buf[pos - 1];
1565             } else {
1566                 ch = more();
1567             }
1568             seenMarkup = false;
1569             posStart = pos - 1;
1570             if (!reachedEnd) {
1571                 while (true) {
1572                     // deal with Misc
1573                     // [27] Misc ::= Comment | PI | S
1574                     if (ch == '<') {
1575                         if (gotS && tokenize) {
1576                             posEnd = pos - 1;
1577                             seenMarkup = true;
1578                             return eventType = IGNORABLE_WHITESPACE;
1579                         }
1580                         ch = more();
1581                         if (reachedEnd) {
1582                             break;
1583                         }
1584                         if (ch == '?') {
1585                             // check if it is 'xml'
1586                             // deal with XMLDecl
1587                             parsePI();
1588                             if (tokenize) return eventType = PROCESSING_INSTRUCTION;
1589 
1590                         } else if (ch == '!') {
1591                             ch = more();
1592                             if (reachedEnd) {
1593                                 break;
1594                             }
1595                             if (ch == 'D') {
1596                                 parseDocdecl(); // FIXME
1597                                 if (tokenize) return eventType = DOCDECL;
1598                             } else if (ch == '-') {
1599                                 parseComment();
1600                                 if (tokenize) return eventType = COMMENT;
1601                             } else {
1602                                 throw new XmlPullParserException("unexpected markup <!" + printable(ch), this, null);
1603                             }
1604                         } else if (ch == '/') {
1605                             throw new XmlPullParserException(
1606                                     "end tag not allowed in epilog but got " + printable(ch), this, null);
1607                         } else if (isNameStartChar(ch)) {
1608                             throw new XmlPullParserException(
1609                                     "start tag not allowed in epilog but got " + printable(ch), this, null);
1610                         } else {
1611                             throw new XmlPullParserException(
1612                                     "in epilog expected ignorable content and not " + printable(ch), this, null);
1613                         }
1614                     } else if (isS(ch)) {
1615                         gotS = true;
1616                         if (normalizeIgnorableWS) {
1617                             if (ch == '\r') {
1618                                 normalizedCR = true;
1619                                 // posEnd = pos -1;
1620                                 // joinPC();
1621                                 // posEnd is already set
1622                                 if (!usePC) {
1623                                     posEnd = pos - 1;
1624                                     if (posEnd > posStart) {
1625                                         joinPC();
1626                                     } else {
1627                                         usePC = true;
1628                                         pcStart = pcEnd = 0;
1629                                     }
1630                                 }
1631                                 // assert usePC == true;
1632                                 if (pcEnd >= pc.length) ensurePC(pcEnd);
1633                                 pc[pcEnd++] = '\n';
1634                             } else if (ch == '\n') {
1635                                 if (!normalizedCR && usePC) {
1636                                     if (pcEnd >= pc.length) ensurePC(pcEnd);
1637                                     pc[pcEnd++] = '\n';
1638                                 }
1639                                 normalizedCR = false;
1640                             } else {
1641                                 if (usePC) {
1642                                     if (pcEnd >= pc.length) ensurePC(pcEnd);
1643                                     pc[pcEnd++] = ch;
1644                                 }
1645                                 normalizedCR = false;
1646                             }
1647                         }
1648                     } else {
1649                         throw new XmlPullParserException(
1650                                 "in epilog non whitespace content is not allowed but got " + printable(ch), this, null);
1651                     }
1652                     ch = more();
1653                     if (reachedEnd) {
1654                         break;
1655                     }
1656                 }
1657             }
1658 
1659             // throw Exception("unexpected content in epilog
1660             // catch EOFException return END_DOCUMENT
1661             // try {
1662         } catch (EOFException ex) {
1663             reachedEnd = true;
1664         }
1665         if (tokenize && gotS) {
1666             posEnd = pos; // well - this is LAST available character pos
1667             return eventType = IGNORABLE_WHITESPACE;
1668         }
1669         return eventType = END_DOCUMENT;
1670     }
1671 
1672     public int parseEndTag() throws XmlPullParserException, IOException {
1673         // ASSUMPTION ch is past "</"
1674         // [42] ETag ::= '</' Name S? '>'
1675         char ch = more();
1676         if (!isNameStartChar(ch)) {
1677             throw new XmlPullParserException("expected name start and not " + printable(ch), this, null);
1678         }
1679         posStart = pos - 3;
1680         final int nameStart = pos - 1 + bufAbsoluteStart;
1681         do {
1682             ch = more();
1683         } while (isNameChar(ch));
1684 
1685         // now we go one level down -- do checks
1686         // --depth; //FIXME
1687 
1688         // check that end tag name is the same as start tag
1689         // String name = new String(buf, nameStart - bufAbsoluteStart,
1690         // (pos - 1) - (nameStart - bufAbsoluteStart));
1691         // int last = pos - 1;
1692         int off = nameStart - bufAbsoluteStart;
1693         // final int len = last - off;
1694         final int len = (pos - 1) - off;
1695         final char[] cbuf = elRawName[depth];
1696         if (elRawNameEnd[depth] != len) {
1697             // construct strings for exception
1698             final String startname = new String(cbuf, 0, elRawNameEnd[depth]);
1699             final String endname = new String(buf, off, len);
1700             throw new XmlPullParserException(
1701                     "end tag name </" + endname + "> must match start tag name <" + startname + ">" + " from line "
1702                             + elRawNameLine[depth],
1703                     this,
1704                     null);
1705         }
1706         for (int i = 0; i < len; i++) {
1707             if (buf[off++] != cbuf[i]) {
1708                 // construct strings for exception
1709                 final String startname = new String(cbuf, 0, len);
1710                 final String endname = new String(buf, off - i - 1, len);
1711                 throw new XmlPullParserException(
1712                         "end tag name </" + endname + "> must be the same as start tag <" + startname + ">"
1713                                 + " from line " + elRawNameLine[depth],
1714                         this,
1715                         null);
1716             }
1717         }
1718 
1719         while (isS(ch)) {
1720             ch = more();
1721         } // skip additional white spaces
1722         if (ch != '>') {
1723             throw new XmlPullParserException(
1724                     "expected > to finsh end tag not " + printable(ch) + " from line " + elRawNameLine[depth],
1725                     this,
1726                     null);
1727         }
1728 
1729         // namespaceEnd = elNamespaceCount[ depth ]; //FIXME
1730 
1731         posEnd = pos;
1732         pastEndTag = true;
1733         return eventType = END_TAG;
1734     }
1735 
1736     public int parseStartTag() throws XmlPullParserException, IOException {
1737         // ASSUMPTION ch is past <T
1738         // [40] STag ::= '<' Name (S Attribute)* S? '>'
1739         // [44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
1740         ++depth; // FIXME
1741 
1742         posStart = pos - 2;
1743 
1744         emptyElementTag = false;
1745         attributeCount = 0;
1746         // retrieve name
1747         final int nameStart = pos - 1 + bufAbsoluteStart;
1748         int colonPos = -1;
1749         char ch = buf[pos - 1];
1750         if (ch == ':' && processNamespaces)
1751             throw new XmlPullParserException(
1752                     "when namespaces processing enabled colon can not be at element name start", this, null);
1753         while (true) {
1754             ch = more();
1755             if (!isNameChar(ch)) break;
1756             if (ch == ':' && processNamespaces) {
1757                 if (colonPos != -1)
1758                     throw new XmlPullParserException(
1759                             "only one colon is allowed in name of element when namespaces are enabled", this, null);
1760                 colonPos = pos - 1 + bufAbsoluteStart;
1761             }
1762         }
1763 
1764         // retrieve name
1765         ensureElementsCapacity();
1766 
1767         // TODO check for efficient interning and then use elRawNameInterned!!!!
1768 
1769         int elLen = (pos - 1) - (nameStart - bufAbsoluteStart);
1770         if (elRawName[depth] == null || elRawName[depth].length < elLen) {
1771             elRawName[depth] = new char[2 * elLen];
1772         }
1773         System.arraycopy(buf, nameStart - bufAbsoluteStart, elRawName[depth], 0, elLen);
1774         elRawNameEnd[depth] = elLen;
1775         elRawNameLine[depth] = lineNumber;
1776 
1777         String name = null;
1778 
1779         // work on prefixes and namespace URI
1780         String prefix = null;
1781         if (processNamespaces) {
1782             if (colonPos != -1) {
1783                 prefix = elPrefix[depth] = newString(buf, nameStart - bufAbsoluteStart, colonPos - nameStart);
1784                 name = elName[depth] = newString(
1785                         buf,
1786                         colonPos + 1 - bufAbsoluteStart,
1787                         // (pos -1) - (colonPos + 1));
1788                         pos - 2 - (colonPos - bufAbsoluteStart));
1789             } else {
1790                 prefix = elPrefix[depth] = null;
1791                 name = elName[depth] = newString(buf, nameStart - bufAbsoluteStart, elLen);
1792             }
1793         } else {
1794 
1795             name = elName[depth] = newString(buf, nameStart - bufAbsoluteStart, elLen);
1796         }
1797 
1798         while (true) {
1799 
1800             while (isS(ch)) {
1801                 ch = more();
1802             } // skip additional white spaces
1803 
1804             if (ch == '>') {
1805                 break;
1806             } else if (ch == '/') {
1807                 if (emptyElementTag) throw new XmlPullParserException("repeated / in tag declaration", this, null);
1808                 emptyElementTag = true;
1809                 ch = more();
1810                 if (ch != '>')
1811                     throw new XmlPullParserException("expected > to end empty tag not " + printable(ch), this, null);
1812                 break;
1813             } else if (isNameStartChar(ch)) {
1814                 ch = parseAttribute();
1815                 ch = more();
1816             } else {
1817                 throw new XmlPullParserException("start tag unexpected character " + printable(ch), this, null);
1818             }
1819             // ch = more(); // skip space
1820         }
1821 
1822         // now when namespaces were declared we can resolve them
1823         if (processNamespaces) {
1824             String uri = getNamespace(prefix);
1825             if (uri == null) {
1826                 if (prefix == null) { // no prefix and no uri => use default namespace
1827                     uri = NO_NAMESPACE;
1828                 } else {
1829                     throw new XmlPullParserException(
1830                             "could not determine namespace bound to element prefix " + prefix, this, null);
1831                 }
1832             }
1833             elUri[depth] = uri;
1834 
1835             // String uri = getNamespace(prefix);
1836             // if(uri == null && prefix == null) { // no prefix and no uri => use default namespace
1837             // uri = "";
1838             // }
1839             // resolve attribute namespaces
1840             for (int i = 0; i < attributeCount; i++) {
1841                 final String attrPrefix = attributePrefix[i];
1842                 if (attrPrefix != null) {
1843                     final String attrUri = getNamespace(attrPrefix);
1844                     if (attrUri == null) {
1845                         throw new XmlPullParserException(
1846                                 "could not determine namespace bound to attribute prefix " + attrPrefix, this, null);
1847                     }
1848                     attributeUri[i] = attrUri;
1849                 } else {
1850                     attributeUri[i] = NO_NAMESPACE;
1851                 }
1852             }
1853 
1854             // TODO
1855             // [ WFC: Unique Att Spec ]
1856             // check namespaced attribute uniqueness constraint!!!
1857 
1858             for (int i = 1; i < attributeCount; i++) {
1859                 for (int j = 0; j < i; j++) {
1860                     if (attributeUri[j] == attributeUri[i]
1861                             && (allStringsInterned && attributeName[j].equals(attributeName[i])
1862                                     || (!allStringsInterned
1863                                             && attributeNameHash[j] == attributeNameHash[i]
1864                                             && attributeName[j].equals(attributeName[i])))) {
1865 
1866                         // prepare data for nice error message?
1867                         String attr1 = attributeName[j];
1868                         if (attributeUri[j] != null) attr1 = attributeUri[j] + ":" + attr1;
1869                         String attr2 = attributeName[i];
1870                         if (attributeUri[i] != null) attr2 = attributeUri[i] + ":" + attr2;
1871                         throw new XmlPullParserException(
1872                                 "duplicated attributes " + attr1 + " and " + attr2, this, null);
1873                     }
1874                 }
1875             }
1876 
1877         } else { // ! processNamespaces
1878 
1879             // [ WFC: Unique Att Spec ]
1880             // check raw attribute uniqueness constraint!!!
1881             for (int i = 1; i < attributeCount; i++) {
1882                 for (int j = 0; j < i; j++) {
1883                     if ((allStringsInterned && attributeName[j].equals(attributeName[i])
1884                             || (!allStringsInterned
1885                                     && attributeNameHash[j] == attributeNameHash[i]
1886                                     && attributeName[j].equals(attributeName[i])))) {
1887 
1888                         // prepare data for nice error message?
1889                         final String attr1 = attributeName[j];
1890                         final String attr2 = attributeName[i];
1891                         throw new XmlPullParserException(
1892                                 "duplicated attributes " + attr1 + " and " + attr2, this, null);
1893                     }
1894                 }
1895             }
1896         }
1897 
1898         elNamespaceCount[depth] = namespaceEnd;
1899         posEnd = pos;
1900         return eventType = START_TAG;
1901     }
1902 
1903     private char parseAttribute() throws XmlPullParserException, IOException {
1904         // parse attribute
1905         // [41] Attribute ::= Name Eq AttValue
1906         // [WFC: No External Entity References]
1907         // [WFC: No < in Attribute Values]
1908         final int prevPosStart = posStart + bufAbsoluteStart;
1909         final int nameStart = pos - 1 + bufAbsoluteStart;
1910         int colonPos = -1;
1911         char ch = buf[pos - 1];
1912         if (ch == ':' && processNamespaces)
1913             throw new XmlPullParserException(
1914                     "when namespaces processing enabled colon can not be at attribute name start", this, null);
1915 
1916         boolean startsWithXmlns = processNamespaces && ch == 'x';
1917         int xmlnsPos = 0;
1918 
1919         ch = more();
1920         while (isNameChar(ch)) {
1921             if (processNamespaces) {
1922                 if (startsWithXmlns && xmlnsPos < 5) {
1923                     ++xmlnsPos;
1924                     if (xmlnsPos == 1) {
1925                         if (ch != 'm') startsWithXmlns = false;
1926                     } else if (xmlnsPos == 2) {
1927                         if (ch != 'l') startsWithXmlns = false;
1928                     } else if (xmlnsPos == 3) {
1929                         if (ch != 'n') startsWithXmlns = false;
1930                     } else if (xmlnsPos == 4) {
1931                         if (ch != 's') startsWithXmlns = false;
1932                     } else if (xmlnsPos == 5) {
1933                         if (ch != ':')
1934                             throw new XmlPullParserException(
1935                                     "after xmlns in attribute name must be colon" + "when namespaces are enabled",
1936                                     this,
1937                                     null);
1938                         // colonPos = pos - 1 + bufAbsoluteStart;
1939                     }
1940                 }
1941                 if (ch == ':') {
1942                     if (colonPos != -1)
1943                         throw new XmlPullParserException(
1944                                 "only one colon is allowed in attribute name" + " when namespaces are enabled",
1945                                 this,
1946                                 null);
1947                     colonPos = pos - 1 + bufAbsoluteStart;
1948                 }
1949             }
1950             ch = more();
1951         }
1952 
1953         ensureAttributesCapacity(attributeCount);
1954 
1955         // --- start processing attributes
1956         String name = null;
1957         String prefix = null;
1958         // work on prefixes and namespace URI
1959         if (processNamespaces) {
1960             if (xmlnsPos < 4) startsWithXmlns = false;
1961             if (startsWithXmlns) {
1962                 if (colonPos != -1) {
1963                     // prefix = attributePrefix[ attributeCount ] = null;
1964                     final int nameLen = pos - 2 - (colonPos - bufAbsoluteStart);
1965                     if (nameLen == 0) {
1966                         throw new XmlPullParserException(
1967                                 "namespace prefix is required after xmlns: " + " when namespaces are enabled",
1968                                 this,
1969                                 null);
1970                     }
1971                     name = // attributeName[ attributeCount ] =
1972                             newString(buf, colonPos - bufAbsoluteStart + 1, nameLen);
1973                     // pos - 1 - (colonPos + 1 - bufAbsoluteStart)
1974                 }
1975             } else {
1976                 if (colonPos != -1) {
1977                     int prefixLen = colonPos - nameStart;
1978                     prefix = attributePrefix[attributeCount] = newString(buf, nameStart - bufAbsoluteStart, prefixLen);
1979                     // colonPos - (nameStart - bufAbsoluteStart));
1980                     int nameLen = pos - 2 - (colonPos - bufAbsoluteStart);
1981                     name = attributeName[attributeCount] = newString(buf, colonPos - bufAbsoluteStart + 1, nameLen);
1982                     // pos - 1 - (colonPos + 1 - bufAbsoluteStart));
1983 
1984                     // name.substring(0, colonPos-nameStart);
1985                 } else {
1986                     prefix = attributePrefix[attributeCount] = null;
1987                     name = attributeName[attributeCount] =
1988                             newString(buf, nameStart - bufAbsoluteStart, pos - 1 - (nameStart - bufAbsoluteStart));
1989                 }
1990                 if (!allStringsInterned) {
1991                     attributeNameHash[attributeCount] = name.hashCode();
1992                 }
1993             }
1994 
1995         } else {
1996             // retrieve name
1997             name = attributeName[attributeCount] =
1998                     newString(buf, nameStart - bufAbsoluteStart, pos - 1 - (nameStart - bufAbsoluteStart));
1999             //// assert name != null;
2000             if (!allStringsInterned) {
2001                 attributeNameHash[attributeCount] = name.hashCode();
2002             }
2003         }
2004 
2005         // [25] Eq ::= S? '=' S?
2006         while (isS(ch)) {
2007             ch = more();
2008         } // skip additional spaces
2009         if (ch != '=') throw new XmlPullParserException("expected = after attribute name", this, null);
2010         ch = more();
2011         while (isS(ch)) {
2012             ch = more();
2013         } // skip additional spaces
2014 
2015         // [10] AttValue ::= '"' ([^<&"] | Reference)* '"'
2016         // | "'" ([^<&'] | Reference)* "'"
2017         final char delimit = ch;
2018         if (delimit != '"' && delimit != '\'')
2019             throw new XmlPullParserException(
2020                     "attribute value must start with quotation or apostrophe not " + printable(delimit), this, null);
2021         // parse until delimit or < and resolve Reference
2022         // [67] Reference ::= EntityRef | CharRef
2023         // int valueStart = pos + bufAbsoluteStart;
2024 
2025         boolean normalizedCR = false;
2026         usePC = false;
2027         pcStart = pcEnd;
2028         posStart = pos;
2029 
2030         while (true) {
2031             ch = more();
2032             if (ch == delimit) {
2033                 break;
2034             }
2035             if (ch == '<') {
2036                 throw new XmlPullParserException("markup not allowed inside attribute value - illegal < ", this, null);
2037             }
2038             if (ch == '&') {
2039                 extractEntityRef();
2040             } else if (ch == '\t' || ch == '\n' || ch == '\r') {
2041                 // do attribute value normalization
2042                 // as described in http://www.w3.org/TR/REC-xml#AVNormalize
2043                 // TODO add test for it form spec ...
2044                 // handle EOL normalization ...
2045                 if (!usePC) {
2046                     posEnd = pos - 1;
2047                     if (posEnd > posStart) {
2048                         joinPC();
2049                     } else {
2050                         usePC = true;
2051                         pcEnd = pcStart = 0;
2052                     }
2053                 }
2054                 // assert usePC == true;
2055                 if (pcEnd >= pc.length) ensurePC(pcEnd);
2056                 if (ch != '\n' || !normalizedCR) {
2057                     pc[pcEnd++] = ' '; // '\n';
2058                 }
2059 
2060             } else {
2061                 if (usePC) {
2062                     if (pcEnd >= pc.length) ensurePC(pcEnd);
2063                     pc[pcEnd++] = ch;
2064                 }
2065             }
2066             normalizedCR = ch == '\r';
2067         }
2068 
2069         if (processNamespaces && startsWithXmlns) {
2070             String ns = null;
2071             if (!usePC) {
2072                 ns = newStringIntern(buf, posStart, pos - 1 - posStart);
2073             } else {
2074                 ns = newStringIntern(pc, pcStart, pcEnd - pcStart);
2075             }
2076             ensureNamespacesCapacity(namespaceEnd);
2077             int prefixHash = -1;
2078             if (colonPos != -1) {
2079                 if (ns.length() == 0) {
2080                     throw new XmlPullParserException(
2081                             "non-default namespace can not be declared to be empty string", this, null);
2082                 }
2083                 // declare new namespace
2084                 namespacePrefix[namespaceEnd] = name;
2085                 if (!allStringsInterned) {
2086                     prefixHash = namespacePrefixHash[namespaceEnd] = name.hashCode();
2087                 }
2088             } else {
2089                 // declare new default namespace...
2090                 namespacePrefix[namespaceEnd] = null; // ""; //null; //TODO check FIXME Alek
2091                 if (!allStringsInterned) {
2092                     prefixHash = namespacePrefixHash[namespaceEnd] = -1;
2093                 }
2094             }
2095             namespaceUri[namespaceEnd] = ns;
2096 
2097             // detect duplicate namespace declarations!!!
2098             final int startNs = elNamespaceCount[depth - 1];
2099             for (int i = namespaceEnd - 1; i >= startNs; --i) {
2100                 if (((allStringsInterned || name == null) && namespacePrefix[i] == name)
2101                         || (!allStringsInterned
2102                                 && name != null
2103                                 && namespacePrefixHash[i] == prefixHash
2104                                 && name.equals(namespacePrefix[i]))) {
2105                     final String s = name == null ? "default" : "'" + name + "'";
2106                     throw new XmlPullParserException(
2107                             "duplicated namespace declaration for " + s + " prefix", this, null);
2108                 }
2109             }
2110 
2111             ++namespaceEnd;
2112 
2113         } else {
2114             if (!usePC) {
2115                 attributeValue[attributeCount] = new String(buf, posStart, pos - 1 - posStart);
2116             } else {
2117                 attributeValue[attributeCount] = new String(pc, pcStart, pcEnd - pcStart);
2118             }
2119             ++attributeCount;
2120         }
2121         posStart = prevPosStart - bufAbsoluteStart;
2122         return ch;
2123     }
2124 
2125     // state representing that no entity ref have been resolved
2126     private static final char[] BUF_NOT_RESOLVED = new char[0];
2127 
2128     // predefined entity refs
2129     private static final char[] BUF_LT = new char[] {'<'};
2130     private static final char[] BUF_AMP = new char[] {'&'};
2131     private static final char[] BUF_GT = new char[] {'>'};
2132     private static final char[] BUF_APO = new char[] {'\''};
2133     private static final char[] BUF_QUOT = new char[] {'"'};
2134 
2135     private char[] resolvedEntityRefCharBuf = BUF_NOT_RESOLVED;
2136 
2137     /**
2138      * parse Entity Ref, either a character entity or one of the predefined name entities.
2139      *
2140      * @return the length of the valid found character reference, which may be one of the predefined character reference
2141      *         names (resolvedEntityRefCharBuf contains the replaced chars). Returns the length of the not found entity
2142      *         name, otherwise.
2143      * @throws XmlPullParserException if invalid XML is detected.
2144      * @throws IOException if an I/O error is found.
2145      */
2146     private int parseCharOrPredefinedEntityRef() throws XmlPullParserException, IOException {
2147         // entity reference http://www.w3.org/TR/2000/REC-xml-20001006#NT-Reference
2148         // [67] Reference ::= EntityRef | CharRef
2149 
2150         // ASSUMPTION just after &
2151         entityRefName = null;
2152         posStart = pos;
2153         int len = 0;
2154         resolvedEntityRefCharBuf = BUF_NOT_RESOLVED;
2155         char ch = more();
2156         if (ch == '#') {
2157             // parse character reference
2158 
2159             char charRef = 0;
2160             ch = more();
2161             StringBuilder sb = new StringBuilder();
2162             boolean isHex = (ch == 'x');
2163 
2164             if (isHex) {
2165                 // encoded in hex
2166                 while (true) {
2167                     ch = more();
2168                     if (ch >= '0' && ch <= '9') {
2169                         charRef = (char) (charRef * 16 + (ch - '0'));
2170                         sb.append(ch);
2171                     } else if (ch >= 'a' && ch <= 'f') {
2172                         charRef = (char) (charRef * 16 + (ch - ('a' - 10)));
2173                         sb.append(ch);
2174                     } else if (ch >= 'A' && ch <= 'F') {
2175                         charRef = (char) (charRef * 16 + (ch - ('A' - 10)));
2176                         sb.append(ch);
2177                     } else if (ch == ';') {
2178                         break;
2179                     } else {
2180                         throw new XmlPullParserException(
2181                                 "character reference (with hex value) may not contain " + printable(ch), this, null);
2182                     }
2183                 }
2184             } else {
2185                 // encoded in decimal
2186                 while (true) {
2187                     if (ch >= '0' && ch <= '9') {
2188                         charRef = (char) (charRef * 10 + (ch - '0'));
2189                         sb.append(ch);
2190                     } else if (ch == ';') {
2191                         break;
2192                     } else {
2193                         throw new XmlPullParserException(
2194                                 "character reference (with decimal value) may not contain " + printable(ch),
2195                                 this,
2196                                 null);
2197                     }
2198                     ch = more();
2199                 }
2200             }
2201 
2202             boolean isValidCodePoint = true;
2203             try {
2204                 int codePoint = Integer.parseInt(sb.toString(), isHex ? 16 : 10);
2205                 isValidCodePoint = isValidCodePoint(codePoint);
2206                 if (isValidCodePoint) {
2207                     resolvedEntityRefCharBuf = Character.toChars(codePoint);
2208                 }
2209             } catch (IllegalArgumentException e) {
2210                 isValidCodePoint = false;
2211             }
2212 
2213             if (!isValidCodePoint) {
2214                 throw new XmlPullParserException(
2215                         "character reference (with " + (isHex ? "hex" : "decimal") + " value " + sb.toString()
2216                                 + ") is invalid",
2217                         this,
2218                         null);
2219             }
2220 
2221             if (tokenize) {
2222                 text = newString(resolvedEntityRefCharBuf, 0, resolvedEntityRefCharBuf.length);
2223             }
2224             len = resolvedEntityRefCharBuf.length;
2225         } else {
2226             // [68] EntityRef ::= '&' Name ';'
2227             // scan name until ;
2228             if (!isNameStartChar(ch)) {
2229                 throw new XmlPullParserException(
2230                         "entity reference names can not start with character '" + printable(ch) + "'", this, null);
2231             }
2232             while (true) {
2233                 ch = more();
2234                 if (ch == ';') {
2235                     break;
2236                 }
2237                 if (!isNameChar(ch)) {
2238                     throw new XmlPullParserException(
2239                             "entity reference name can not contain character " + printable(ch) + "'", this, null);
2240                 }
2241             }
2242             // determine what name maps to
2243             len = (pos - 1) - posStart;
2244             if (len == 2 && buf[posStart] == 'l' && buf[posStart + 1] == 't') {
2245                 if (tokenize) {
2246                     text = "<";
2247                 }
2248                 resolvedEntityRefCharBuf = BUF_LT;
2249                 // if(paramPC || isParserTokenizing) {
2250                 // if(pcEnd >= pc.length) ensurePC();
2251                 // pc[pcEnd++] = '<';
2252                 // }
2253             } else if (len == 3 && buf[posStart] == 'a' && buf[posStart + 1] == 'm' && buf[posStart + 2] == 'p') {
2254                 if (tokenize) {
2255                     text = "&";
2256                 }
2257                 resolvedEntityRefCharBuf = BUF_AMP;
2258             } else if (len == 2 && buf[posStart] == 'g' && buf[posStart + 1] == 't') {
2259                 if (tokenize) {
2260                     text = ">";
2261                 }
2262                 resolvedEntityRefCharBuf = BUF_GT;
2263             } else if (len == 4
2264                     && buf[posStart] == 'a'
2265                     && buf[posStart + 1] == 'p'
2266                     && buf[posStart + 2] == 'o'
2267                     && buf[posStart + 3] == 's') {
2268                 if (tokenize) {
2269                     text = "'";
2270                 }
2271                 resolvedEntityRefCharBuf = BUF_APO;
2272             } else if (len == 4
2273                     && buf[posStart] == 'q'
2274                     && buf[posStart + 1] == 'u'
2275                     && buf[posStart + 2] == 'o'
2276                     && buf[posStart + 3] == 't') {
2277                 if (tokenize) {
2278                     text = "\"";
2279                 }
2280                 resolvedEntityRefCharBuf = BUF_QUOT;
2281             }
2282         }
2283 
2284         posEnd = pos;
2285 
2286         return len;
2287     }
2288 
2289     /**
2290      * Parse an entity reference inside the DOCDECL section.
2291      *
2292      * @throws XmlPullParserException if invalid XML is detected.
2293      * @throws IOException if an I/O error is found.
2294      */
2295     private void parseEntityRefInDocDecl() throws XmlPullParserException, IOException {
2296         parseCharOrPredefinedEntityRef();
2297         if (usePC) {
2298             posStart--; // include in PC the starting '&' of the entity
2299             joinPC();
2300         }
2301 
2302         if (resolvedEntityRefCharBuf != BUF_NOT_RESOLVED) return;
2303         if (tokenize) text = null;
2304     }
2305 
2306     /**
2307      * Parse an entity reference inside a tag or attribute.
2308      *
2309      * @throws XmlPullParserException if invalid XML is detected.
2310      * @throws IOException if an I/O error is found.
2311      */
2312     private void parseEntityRef() throws XmlPullParserException, IOException {
2313         final int len = parseCharOrPredefinedEntityRef();
2314 
2315         posEnd--; // don't involve the final ';' from the entity in the search
2316 
2317         if (resolvedEntityRefCharBuf != BUF_NOT_RESOLVED) {
2318             return;
2319         }
2320 
2321         resolvedEntityRefCharBuf = lookuEntityReplacement(len);
2322         if (resolvedEntityRefCharBuf != BUF_NOT_RESOLVED) {
2323             return;
2324         }
2325         if (tokenize) text = null;
2326     }
2327 
2328     /**
2329      * Check if the provided parameter is a valid Char. According to
2330      * <a href="https://www.w3.org/TR/REC-xml/#NT-Char">https://www.w3.org/TR/REC-xml/#NT-Char</a>
2331      *
2332      * @param codePoint the numeric value to check
2333      * @return true if it is a valid numeric character reference. False otherwise.
2334      */
2335     private static boolean isValidCodePoint(int codePoint) {
2336         // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
2337         return codePoint == 0x9
2338                 || codePoint == 0xA
2339                 || codePoint == 0xD
2340                 || (0x20 <= codePoint && codePoint <= 0xD7FF)
2341                 || (0xE000 <= codePoint && codePoint <= 0xFFFD)
2342                 || (0x10000 <= codePoint && codePoint <= 0x10FFFF);
2343     }
2344 
2345     private char[] lookuEntityReplacement(int entityNameLen) {
2346         if (!allStringsInterned) {
2347             final int hash = fastHash(buf, posStart, posEnd - posStart);
2348             LOOP:
2349             for (int i = entityEnd - 1; i >= 0; --i) {
2350                 if (hash == entityNameHash[i] && entityNameLen == entityNameBuf[i].length) {
2351                     final char[] entityBuf = entityNameBuf[i];
2352                     for (int j = 0; j < entityNameLen; j++) {
2353                         if (buf[posStart + j] != entityBuf[j]) continue LOOP;
2354                     }
2355                     if (tokenize) text = entityReplacement[i];
2356                     return entityReplacementBuf[i];
2357                 }
2358             }
2359         } else {
2360             entityRefName = newString(buf, posStart, posEnd - posStart);
2361             for (int i = entityEnd - 1; i >= 0; --i) {
2362                 // take advantage that interning for newString is enforced
2363                 if (entityRefName == entityName[i]) {
2364                     if (tokenize) text = entityReplacement[i];
2365                     return entityReplacementBuf[i];
2366                 }
2367             }
2368         }
2369         return BUF_NOT_RESOLVED;
2370     }
2371 
2372     private void parseComment() throws XmlPullParserException, IOException {
2373         // implements XML 1.0 Section 2.5 Comments
2374 
2375         // ASSUMPTION: seen <!-
2376         char cch = more();
2377         if (cch != '-') throw new XmlPullParserException("expected <!-- for comment start", this, null);
2378         if (tokenize) posStart = pos;
2379 
2380         final int curLine = lineNumber;
2381         final int curColumn = columnNumber - 4;
2382         try {
2383             final boolean normalizeIgnorableWS = tokenize && !roundtripSupported;
2384             boolean normalizedCR = false;
2385 
2386             boolean seenDash = false;
2387             boolean seenDashDash = false;
2388             while (true) {
2389                 // scan until it hits -->
2390                 cch = more();
2391                 int ch;
2392                 char cch2;
2393                 if (Character.isHighSurrogate(cch)) {
2394                     cch2 = more();
2395                     ch = Character.toCodePoint(cch, cch2);
2396                 } else {
2397                     cch2 = 0;
2398                     ch = cch;
2399                 }
2400                 if (seenDashDash && ch != '>') {
2401                     throw new XmlPullParserException(
2402                             "in comment after two dashes (--) next character must be >" + " not " + printable(ch),
2403                             this,
2404                             null);
2405                 }
2406                 if (ch == '-') {
2407                     if (!seenDash) {
2408                         seenDash = true;
2409                     } else {
2410                         seenDashDash = true;
2411                     }
2412                 } else if (ch == '>') {
2413                     if (seenDashDash) {
2414                         break; // found end sequence!!!!
2415                     }
2416                     seenDash = false;
2417                 } else if (isValidCodePoint(ch)) {
2418                     seenDash = false;
2419                 } else {
2420                     throw new XmlPullParserException(
2421                             "Illegal character 0x" + Integer.toHexString(ch) + " found in comment", this, null);
2422                 }
2423                 if (normalizeIgnorableWS) {
2424                     if (ch == '\r') {
2425                         normalizedCR = true;
2426                         // posEnd = pos -1;
2427                         // joinPC();
2428                         // posEnd is alreadys set
2429                         if (!usePC) {
2430                             posEnd = pos - 1;
2431                             if (posEnd > posStart) {
2432                                 joinPC();
2433                             } else {
2434                                 usePC = true;
2435                                 pcStart = pcEnd = 0;
2436                             }
2437                         }
2438                         // assert usePC == true;
2439                         if (pcEnd >= pc.length) ensurePC(pcEnd);
2440                         pc[pcEnd++] = '\n';
2441                     } else if (ch == '\n') {
2442                         if (!normalizedCR && usePC) {
2443                             if (pcEnd >= pc.length) ensurePC(pcEnd);
2444                             pc[pcEnd++] = '\n';
2445                         }
2446                         normalizedCR = false;
2447                     } else {
2448                         if (usePC) {
2449                             if (pcEnd >= pc.length) ensurePC(pcEnd);
2450                             pc[pcEnd++] = cch;
2451                             if (cch2 != 0) {
2452                                 pc[pcEnd++] = cch2;
2453                             }
2454                         }
2455                         normalizedCR = false;
2456                     }
2457                 }
2458             }
2459 
2460         } catch (EOFException ex) {
2461             // detect EOF and create meaningful error ...
2462             throw new XmlPullParserException(
2463                     "comment started on line " + curLine + " and column " + curColumn + " was not closed", this, ex);
2464         }
2465         if (tokenize) {
2466             posEnd = pos - 3;
2467             if (usePC) {
2468                 pcEnd -= 2;
2469             }
2470         }
2471     }
2472 
2473     private boolean parsePI() throws XmlPullParserException, IOException {
2474         // implements XML 1.0 Section 2.6 Processing Instructions
2475 
2476         // [16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
2477         // [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
2478         // ASSUMPTION: seen <?
2479         if (tokenize) posStart = pos;
2480         final int curLine = lineNumber;
2481         final int curColumn = columnNumber - 2;
2482         int piTargetStart = pos;
2483         int piTargetEnd = -1;
2484         final boolean normalizeIgnorableWS = tokenize && !roundtripSupported;
2485         boolean normalizedCR = false;
2486 
2487         try {
2488             boolean seenPITarget = false;
2489             boolean seenInnerTag = false;
2490             boolean seenQ = false;
2491             char ch = more();
2492             if (isS(ch)) {
2493                 throw new XmlPullParserException(
2494                         "processing instruction PITarget must be exactly after <? and not white space character",
2495                         this,
2496                         null);
2497             }
2498             while (true) {
2499                 // scan until it hits ?>
2500                 // ch = more();
2501 
2502                 if (ch == '?') {
2503                     if (!seenPITarget) {
2504                         throw new XmlPullParserException("processing instruction PITarget name not found", this, null);
2505                     }
2506                     seenQ = true;
2507                 } else if (ch == '>') {
2508                     if (seenQ) {
2509                         break; // found end sequence!!!!
2510                     }
2511 
2512                     if (!seenPITarget) {
2513                         throw new XmlPullParserException("processing instruction PITarget name not found", this, null);
2514                     } else if (!seenInnerTag) {
2515                         // seenPITarget && !seenQ
2516                         throw new XmlPullParserException(
2517                                 "processing instruction started on line " + curLine + " and column " + curColumn
2518                                         + " was not closed",
2519                                 this,
2520                                 null);
2521                     } else {
2522                         seenInnerTag = false;
2523                     }
2524                 } else if (ch == '<') {
2525                     seenInnerTag = true;
2526                 } else {
2527                     if (piTargetEnd == -1 && isS(ch)) {
2528                         piTargetEnd = pos - 1;
2529 
2530                         // [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
2531                         if ((piTargetEnd - piTargetStart) >= 3) {
2532                             if ((buf[piTargetStart] == 'x' || buf[piTargetStart] == 'X')
2533                                     && (buf[piTargetStart + 1] == 'm' || buf[piTargetStart + 1] == 'M')
2534                                     && (buf[piTargetStart + 2] == 'l' || buf[piTargetStart + 2] == 'L')) {
2535                                 if (piTargetStart > 3) { // <?xml is allowed as first characters in input ...
2536                                     throw new XmlPullParserException(
2537                                             "processing instruction can not have PITarget with reserved xml name",
2538                                             this,
2539                                             null);
2540                                 } else {
2541                                     if (buf[piTargetStart] != 'x'
2542                                             && buf[piTargetStart + 1] != 'm'
2543                                             && buf[piTargetStart + 2] != 'l') {
2544                                         throw new XmlPullParserException(
2545                                                 "XMLDecl must have xml name in lowercase", this, null);
2546                                     }
2547                                 }
2548                                 parseXmlDecl(ch);
2549                                 if (tokenize) posEnd = pos - 2;
2550                                 final int off = piTargetStart + 3;
2551                                 final int len = pos - 2 - off;
2552                                 xmlDeclContent = newString(buf, off, len);
2553                                 return false;
2554                             }
2555                         }
2556                     }
2557 
2558                     seenQ = false;
2559                 }
2560                 if (normalizeIgnorableWS) {
2561                     if (ch == '\r') {
2562                         normalizedCR = true;
2563                         // posEnd = pos -1;
2564                         // joinPC();
2565                         // posEnd is alreadys set
2566                         if (!usePC) {
2567                             posEnd = pos - 1;
2568                             if (posEnd > posStart) {
2569                                 joinPC();
2570                             } else {
2571                                 usePC = true;
2572                                 pcStart = pcEnd = 0;
2573                             }
2574                         }
2575                         // assert usePC == true;
2576                         if (pcEnd >= pc.length) ensurePC(pcEnd);
2577                         pc[pcEnd++] = '\n';
2578                     } else if (ch == '\n') {
2579                         if (!normalizedCR && usePC) {
2580                             if (pcEnd >= pc.length) ensurePC(pcEnd);
2581                             pc[pcEnd++] = '\n';
2582                         }
2583                         normalizedCR = false;
2584                     } else {
2585                         if (usePC) {
2586                             if (pcEnd >= pc.length) ensurePC(pcEnd);
2587                             pc[pcEnd++] = ch;
2588                         }
2589                         normalizedCR = false;
2590                     }
2591                 }
2592                 seenPITarget = true;
2593                 ch = more();
2594             }
2595         } catch (EOFException ex) {
2596             // detect EOF and create meaningful error ...
2597             throw new XmlPullParserException(
2598                     "processing instruction started on line " + curLine + " and column " + curColumn
2599                             + " was not closed",
2600                     this,
2601                     ex);
2602         }
2603         if (piTargetEnd == -1) {
2604             piTargetEnd = pos - 2 + bufAbsoluteStart;
2605             // throw new XmlPullParserException(
2606             // "processing instruction must have PITarget name", this, null);
2607         }
2608         if (tokenize) {
2609             posEnd = pos - 2;
2610             if (normalizeIgnorableWS) {
2611                 --pcEnd;
2612             }
2613         }
2614         return true;
2615     }
2616 
2617     // protected final static char[] VERSION = {'v','e','r','s','i','o','n'};
2618     // protected final static char[] NCODING = {'n','c','o','d','i','n','g'};
2619     // protected final static char[] TANDALONE = {'t','a','n','d','a','l','o','n','e'};
2620     // protected final static char[] YES = {'y','e','s'};
2621     // protected final static char[] NO = {'n','o'};
2622 
2623     private static final char[] VERSION = "version".toCharArray();
2624 
2625     private static final char[] NCODING = "ncoding".toCharArray();
2626 
2627     private static final char[] TANDALONE = "tandalone".toCharArray();
2628 
2629     private static final char[] YES = "yes".toCharArray();
2630 
2631     private static final char[] NO = "no".toCharArray();
2632 
2633     private void parseXmlDecl(char ch) throws XmlPullParserException, IOException {
2634         // [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
2635 
2636         // first make sure that relative positions will stay OK
2637         preventBufferCompaction = true;
2638         bufStart = 0; // necessary to keep pos unchanged during expansion!
2639 
2640         // --- parse VersionInfo
2641 
2642         // [24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"')
2643         // parse is positioned just on first S past <?xml
2644         ch = skipS(ch);
2645         ch = requireInput(ch, VERSION);
2646         // [25] Eq ::= S? '=' S?
2647         ch = skipS(ch);
2648         if (ch != '=') {
2649             throw new XmlPullParserException(
2650                     "expected equals sign (=) after version and not " + printable(ch), this, null);
2651         }
2652         ch = more();
2653         ch = skipS(ch);
2654         if (ch != '\'' && ch != '"') {
2655             throw new XmlPullParserException(
2656                     "expected apostrophe (') or quotation mark (\") after version and not " + printable(ch),
2657                     this,
2658                     null);
2659         }
2660         final char quotChar = ch;
2661         // int versionStart = pos + bufAbsoluteStart; // required if preventBufferCompaction==false
2662         final int versionStart = pos;
2663         ch = more();
2664         // [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')+
2665         while (ch != quotChar) {
2666             if ((ch < 'a' || ch > 'z')
2667                     && (ch < 'A' || ch > 'Z')
2668                     && (ch < '0' || ch > '9')
2669                     && ch != '_'
2670                     && ch != '.'
2671                     && ch != ':'
2672                     && ch != '-') {
2673                 throw new XmlPullParserException(
2674                         "<?xml version value expected to be in ([a-zA-Z0-9_.:] | '-')" + " not " + printable(ch),
2675                         this,
2676                         null);
2677             }
2678             ch = more();
2679         }
2680         final int versionEnd = pos - 1;
2681         parseXmlDeclWithVersion(versionStart, versionEnd);
2682         preventBufferCompaction = false; // allow again buffer compaction - pos MAY change
2683     }
2684     // private String xmlDeclVersion;
2685 
2686     private void parseXmlDeclWithVersion(int versionStart, int versionEnd) throws XmlPullParserException, IOException {
2687         // check version is "1.0"
2688         if ((versionEnd - versionStart != 3)
2689                 || buf[versionStart] != '1'
2690                 || buf[versionStart + 1] != '.'
2691                 || buf[versionStart + 2] != '0') {
2692             throw new XmlPullParserException(
2693                     "only 1.0 is supported as <?xml version not '"
2694                             + printable(new String(buf, versionStart, versionEnd - versionStart)) + "'",
2695                     this,
2696                     null);
2697         }
2698         xmlDeclVersion = newString(buf, versionStart, versionEnd - versionStart);
2699 
2700         String lastParsedAttr = "version";
2701 
2702         // [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )
2703         char ch = more();
2704         char prevCh = ch;
2705         ch = skipS(ch);
2706 
2707         if (ch != 'e' && ch != 's' && ch != '?' && ch != '>') {
2708             throw new XmlPullParserException("unexpected character " + printable(ch), this, null);
2709         }
2710 
2711         if (ch == 'e') {
2712             if (!isS(prevCh)) {
2713                 throw new XmlPullParserException(
2714                         "expected a space after " + lastParsedAttr + " and not " + printable(ch), this, null);
2715             }
2716             ch = more();
2717             ch = requireInput(ch, NCODING);
2718             ch = skipS(ch);
2719             if (ch != '=') {
2720                 throw new XmlPullParserException(
2721                         "expected equals sign (=) after encoding and not " + printable(ch), this, null);
2722             }
2723             ch = more();
2724             ch = skipS(ch);
2725             if (ch != '\'' && ch != '"') {
2726                 throw new XmlPullParserException(
2727                         "expected apostrophe (') or quotation mark (\") after encoding and not " + printable(ch),
2728                         this,
2729                         null);
2730             }
2731             final char quotChar = ch;
2732             final int encodingStart = pos;
2733             ch = more();
2734             // [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
2735             if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) {
2736                 throw new XmlPullParserException(
2737                         "<?xml encoding name expected to start with [A-Za-z]" + " not " + printable(ch), this, null);
2738             }
2739             ch = more();
2740             while (ch != quotChar) {
2741                 if ((ch < 'a' || ch > 'z')
2742                         && (ch < 'A' || ch > 'Z')
2743                         && (ch < '0' || ch > '9')
2744                         && ch != '.'
2745                         && ch != '_'
2746                         && ch != '-') {
2747                     throw new XmlPullParserException(
2748                             "<?xml encoding value expected to be in ([A-Za-z0-9._] | '-')" + " not " + printable(ch),
2749                             this,
2750                             null);
2751                 }
2752                 ch = more();
2753             }
2754             final int encodingEnd = pos - 1;
2755 
2756             // TODO reconcile with setInput encodingName
2757             inputEncoding = newString(buf, encodingStart, encodingEnd - encodingStart);
2758 
2759             if ("UTF8".equals(fileEncoding) && inputEncoding.toUpperCase().startsWith("ISO-")) {
2760                 throw new XmlPullParserException(
2761                         "UTF-8 BOM plus xml decl of " + inputEncoding + " is incompatible", this, null);
2762             } else if ("UTF-16".equals(fileEncoding) && inputEncoding.equalsIgnoreCase("UTF-8")) {
2763                 throw new XmlPullParserException(
2764                         "UTF-16 BOM plus xml decl of " + inputEncoding + " is incompatible", this, null);
2765             }
2766 
2767             lastParsedAttr = "encoding";
2768 
2769             ch = more();
2770             prevCh = ch;
2771             ch = skipS(ch);
2772         }
2773 
2774         // [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"'))
2775         if (ch == 's') {
2776             if (!isS(prevCh)) {
2777                 throw new XmlPullParserException(
2778                         "expected a space after " + lastParsedAttr + " and not " + printable(ch), this, null);
2779             }
2780 
2781             ch = more();
2782             ch = requireInput(ch, TANDALONE);
2783             ch = skipS(ch);
2784             if (ch != '=') {
2785                 throw new XmlPullParserException(
2786                         "expected equals sign (=) after standalone and not " + printable(ch), this, null);
2787             }
2788             ch = more();
2789             ch = skipS(ch);
2790             if (ch != '\'' && ch != '"') {
2791                 throw new XmlPullParserException(
2792                         "expected apostrophe (') or quotation mark (\") after standalone and not " + printable(ch),
2793                         this,
2794                         null);
2795             }
2796             char quotChar = ch;
2797             ch = more();
2798             if (ch == 'y') {
2799                 ch = requireInput(ch, YES);
2800                 // Boolean standalone = new Boolean(true);
2801                 xmlDeclStandalone = true;
2802             } else if (ch == 'n') {
2803                 ch = requireInput(ch, NO);
2804                 // Boolean standalone = new Boolean(false);
2805                 xmlDeclStandalone = false;
2806             } else {
2807                 throw new XmlPullParserException(
2808                         "expected 'yes' or 'no' after standalone and not " + printable(ch), this, null);
2809             }
2810             if (ch != quotChar) {
2811                 throw new XmlPullParserException(
2812                         "expected " + quotChar + " after standalone value not " + printable(ch), this, null);
2813             }
2814             ch = more();
2815             ch = skipS(ch);
2816         }
2817 
2818         if (ch != '?') {
2819             throw new XmlPullParserException("expected ?> as last part of <?xml not " + printable(ch), this, null);
2820         }
2821         ch = more();
2822         if (ch != '>') {
2823             throw new XmlPullParserException("expected ?> as last part of <?xml not " + printable(ch), this, null);
2824         }
2825     }
2826 
2827     private void parseDocdecl() throws XmlPullParserException, IOException {
2828         // ASSUMPTION: seen <!D
2829         char ch = more();
2830         if (ch != 'O') throw new XmlPullParserException("expected <!DOCTYPE", this, null);
2831         ch = more();
2832         if (ch != 'C') throw new XmlPullParserException("expected <!DOCTYPE", this, null);
2833         ch = more();
2834         if (ch != 'T') throw new XmlPullParserException("expected <!DOCTYPE", this, null);
2835         ch = more();
2836         if (ch != 'Y') throw new XmlPullParserException("expected <!DOCTYPE", this, null);
2837         ch = more();
2838         if (ch != 'P') throw new XmlPullParserException("expected <!DOCTYPE", this, null);
2839         ch = more();
2840         if (ch != 'E') throw new XmlPullParserException("expected <!DOCTYPE", this, null);
2841         posStart = pos;
2842         // do simple and crude scanning for end of doctype
2843 
2844         // [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('['
2845         // (markupdecl | DeclSep)* ']' S?)? '>'
2846         int bracketLevel = 0;
2847         final boolean normalizeIgnorableWS = tokenize && !roundtripSupported;
2848         boolean normalizedCR = false;
2849         while (true) {
2850             ch = more();
2851             if (ch == '[') ++bracketLevel;
2852             else if (ch == ']') --bracketLevel;
2853             else if (ch == '>' && bracketLevel == 0) break;
2854             else if (ch == '&') {
2855                 extractEntityRefInDocDecl();
2856                 continue;
2857             }
2858             if (normalizeIgnorableWS) {
2859                 if (ch == '\r') {
2860                     normalizedCR = true;
2861                     // posEnd = pos -1;
2862                     // joinPC();
2863                     // posEnd is alreadys set
2864                     if (!usePC) {
2865                         posEnd = pos - 1;
2866                         if (posEnd > posStart) {
2867                             joinPC();
2868                         } else {
2869                             usePC = true;
2870                             pcStart = pcEnd = 0;
2871                         }
2872                     }
2873                     // assert usePC == true;
2874                     if (pcEnd >= pc.length) ensurePC(pcEnd);
2875                     pc[pcEnd++] = '\n';
2876                 } else if (ch == '\n') {
2877                     if (!normalizedCR && usePC) {
2878                         if (pcEnd >= pc.length) ensurePC(pcEnd);
2879                         pc[pcEnd++] = '\n';
2880                     }
2881                     normalizedCR = false;
2882                 } else {
2883                     if (usePC) {
2884                         if (pcEnd >= pc.length) ensurePC(pcEnd);
2885                         pc[pcEnd++] = ch;
2886                     }
2887                     normalizedCR = false;
2888                 }
2889             }
2890         }
2891         posEnd = pos - 1;
2892         text = null;
2893     }
2894 
2895     private void extractEntityRefInDocDecl() throws XmlPullParserException, IOException {
2896         // extractEntityRef
2897         posEnd = pos - 1;
2898 
2899         int prevPosStart = posStart;
2900         parseEntityRefInDocDecl();
2901 
2902         posStart = prevPosStart;
2903     }
2904 
2905     private void extractEntityRef() throws XmlPullParserException, IOException {
2906         // extractEntityRef
2907         posEnd = pos - 1;
2908         if (!usePC) {
2909             final boolean hadCharData = posEnd > posStart;
2910             if (hadCharData) {
2911                 // posEnd is already set correctly!!!
2912                 joinPC();
2913             } else {
2914                 usePC = true;
2915                 pcStart = pcEnd = 0;
2916             }
2917         }
2918         // assert usePC == true;
2919 
2920         parseEntityRef();
2921         // check if replacement text can be resolved !!!
2922         if (resolvedEntityRefCharBuf == BUF_NOT_RESOLVED) {
2923             if (entityRefName == null) {
2924                 entityRefName = newString(buf, posStart, posEnd - posStart);
2925             }
2926             throw new XmlPullParserException(
2927                     "could not resolve entity named '" + printable(entityRefName) + "'", this, null);
2928         }
2929         // write into PC replacement text - do merge for replacement text!!!!
2930         for (char aResolvedEntity : resolvedEntityRefCharBuf) {
2931             if (pcEnd >= pc.length) {
2932                 ensurePC(pcEnd);
2933             }
2934             pc[pcEnd++] = aResolvedEntity;
2935         }
2936     }
2937 
2938     private void parseCDSect(boolean hadCharData) throws XmlPullParserException, IOException {
2939         // implements XML 1.0 Section 2.7 CDATA Sections
2940 
2941         // [18] CDSect ::= CDStart CData CDEnd
2942         // [19] CDStart ::= '<![CDATA['
2943         // [20] CData ::= (Char* - (Char* ']]>' Char*))
2944         // [21] CDEnd ::= ']]>'
2945 
2946         // ASSUMPTION: seen <![
2947         char ch = more();
2948         if (ch != 'C') throw new XmlPullParserException("expected <[CDATA[ for comment start", this, null);
2949         ch = more();
2950         if (ch != 'D') throw new XmlPullParserException("expected <[CDATA[ for comment start", this, null);
2951         ch = more();
2952         if (ch != 'A') throw new XmlPullParserException("expected <[CDATA[ for comment start", this, null);
2953         ch = more();
2954         if (ch != 'T') throw new XmlPullParserException("expected <[CDATA[ for comment start", this, null);
2955         ch = more();
2956         if (ch != 'A') throw new XmlPullParserException("expected <[CDATA[ for comment start", this, null);
2957         ch = more();
2958         if (ch != '[') throw new XmlPullParserException("expected <![CDATA[ for comment start", this, null);
2959 
2960         // if(tokenize) {
2961         final int cdStart = pos + bufAbsoluteStart;
2962         final int curLine = lineNumber;
2963         final int curColumn = columnNumber;
2964         final boolean normalizeInput = !tokenize || !roundtripSupported;
2965         try {
2966             if (normalizeInput) {
2967                 if (hadCharData) {
2968                     if (!usePC) {
2969                         // posEnd is correct already!!!
2970                         if (posEnd > posStart) {
2971                             joinPC();
2972                         } else {
2973                             usePC = true;
2974                             pcStart = pcEnd = 0;
2975                         }
2976                     }
2977                 }
2978             }
2979             boolean seenBracket = false;
2980             boolean seenBracketBracket = false;
2981             boolean normalizedCR = false;
2982             while (true) {
2983                 // scan until it hits "]]>"
2984                 ch = more();
2985                 if (ch == ']') {
2986                     if (!seenBracket) {
2987                         seenBracket = true;
2988                     } else {
2989                         seenBracketBracket = true;
2990                         // seenBracket = false;
2991                     }
2992                 } else if (ch == '>') {
2993                     if (seenBracket && seenBracketBracket) {
2994                         break; // found end sequence!!!!
2995                     } else {
2996                         seenBracketBracket = false;
2997                     }
2998                     seenBracket = false;
2999                 } else {
3000                     if (seenBracket) {
3001                         seenBracket = false;
3002                     }
3003                 }
3004                 if (normalizeInput) {
3005                     // deal with normalization issues ...
3006                     if (ch == '\r') {
3007                         normalizedCR = true;
3008                         posStart = cdStart - bufAbsoluteStart;
3009                         posEnd = pos - 1; // posEnd is alreadys set
3010                         if (!usePC) {
3011                             if (posEnd > posStart) {
3012                                 joinPC();
3013                             } else {
3014                                 usePC = true;
3015                                 pcStart = pcEnd = 0;
3016                             }
3017                         }
3018                         // assert usePC == true;
3019                         if (pcEnd >= pc.length) ensurePC(pcEnd);
3020                         pc[pcEnd++] = '\n';
3021                     } else if (ch == '\n') {
3022                         if (!normalizedCR && usePC) {
3023                             if (pcEnd >= pc.length) ensurePC(pcEnd);
3024                             pc[pcEnd++] = '\n';
3025                         }
3026                         normalizedCR = false;
3027                     } else {
3028                         if (usePC) {
3029                             if (pcEnd >= pc.length) ensurePC(pcEnd);
3030                             pc[pcEnd++] = ch;
3031                         }
3032                         normalizedCR = false;
3033                     }
3034                 }
3035             }
3036         } catch (EOFException ex) {
3037             // detect EOF and create meaningful error ...
3038             throw new XmlPullParserException(
3039                     "CDATA section started on line " + curLine + " and column " + curColumn + " was not closed",
3040                     this,
3041                     ex);
3042         }
3043         if (normalizeInput) {
3044             if (usePC) {
3045                 pcEnd = pcEnd - 2;
3046             }
3047         }
3048         posStart = cdStart - bufAbsoluteStart;
3049         posEnd = pos - 3;
3050     }
3051 
3052     private void fillBuf() throws IOException, XmlPullParserException {
3053         if (reader == null) throw new XmlPullParserException("reader must be set before parsing is started");
3054 
3055         // see if we are in compaction area
3056         if (bufEnd > bufSoftLimit) {
3057 
3058             // check if we need to compact or expand the buffer
3059             boolean compact = !preventBufferCompaction && (bufStart > bufSoftLimit || bufStart >= buf.length / 2);
3060 
3061             // if buffer almost full then compact it
3062             if (compact) {
3063                 // TODO: look on trashing
3064                 // //assert bufStart > 0
3065                 System.arraycopy(buf, bufStart, buf, 0, bufEnd - bufStart);
3066                 if (TRACE_SIZING)
3067                     System.out.println("TRACE_SIZING fillBuf() compacting " + bufStart + " bufEnd=" + bufEnd + " pos="
3068                             + pos + " posStart=" + posStart + " posEnd=" + posEnd + " buf first 100 chars:"
3069                             + new String(buf, bufStart, Math.min(bufEnd - bufStart, 100)));
3070 
3071             } else {
3072                 final int newSize = 2 * buf.length;
3073                 final char[] newBuf = new char[newSize];
3074                 if (TRACE_SIZING) System.out.println("TRACE_SIZING fillBuf() " + buf.length + " => " + newSize);
3075                 System.arraycopy(buf, bufStart, newBuf, 0, bufEnd - bufStart);
3076                 buf = newBuf;
3077                 if (bufLoadFactor > 0) {
3078                     // Include a fix for
3079                     // https://web.archive.org/web/20070831191548/http://www.extreme.indiana.edu/bugzilla/show_bug.cgi?id=228
3080                     bufSoftLimit = (int) (bufferLoadFactor * buf.length);
3081                 }
3082             }
3083             bufEnd -= bufStart;
3084             pos -= bufStart;
3085             posStart -= bufStart;
3086             posEnd -= bufStart;
3087             bufAbsoluteStart += bufStart;
3088             bufStart = 0;
3089             if (TRACE_SIZING)
3090                 System.out.println("TRACE_SIZING fillBuf() after bufEnd=" + bufEnd + " pos=" + pos + " posStart="
3091                         + posStart + " posEnd=" + posEnd + " buf first 100 chars:"
3092                         + new String(buf, 0, Math.min(bufEnd, 100)));
3093         }
3094         // at least one character must be read or error
3095         final int len = Math.min(buf.length - bufEnd, READ_CHUNK_SIZE);
3096         final int ret = reader.read(buf, bufEnd, len);
3097         if (ret > 0) {
3098             bufEnd += ret;
3099             if (TRACE_SIZING)
3100                 System.out.println("TRACE_SIZING fillBuf() after filling in buffer" + " buf first 100 chars:"
3101                         + new String(buf, 0, Math.min(bufEnd, 100)));
3102 
3103             return;
3104         }
3105         if (ret == -1) {
3106             if (bufAbsoluteStart == 0 && pos == 0) {
3107                 throw new EOFException("input contained no data");
3108             } else {
3109                 if (seenRoot && depth == 0) { // inside parsing epilog!!!
3110                     reachedEnd = true;
3111                     return;
3112                 } else {
3113                     StringBuilder expectedTagStack = new StringBuilder();
3114                     if (depth > 0) {
3115                         if (elRawName == null || elRawName[depth] == null) {
3116                             String tagName = new String(buf, posStart + 1, pos - posStart - 1);
3117                             expectedTagStack
3118                                     .append(" - expected the opening tag <")
3119                                     .append(tagName)
3120                                     .append("...>");
3121                         } else {
3122                             // final char[] cbuf = elRawName[depth];
3123                             // final String startname = new String(cbuf, 0, elRawNameEnd[depth]);
3124                             expectedTagStack.append(" - expected end tag");
3125                             if (depth > 1) {
3126                                 expectedTagStack.append("s"); // more than one end tag
3127                             }
3128                             expectedTagStack.append(" ");
3129 
3130                             for (int i = depth; i > 0; i--) {
3131                                 if (elRawName == null || elRawName[i] == null) {
3132                                     String tagName = new String(buf, posStart + 1, pos - posStart - 1);
3133                                     expectedTagStack
3134                                             .append(" - expected the opening tag <")
3135                                             .append(tagName)
3136                                             .append("...>");
3137                                 } else {
3138                                     String tagName = new String(elRawName[i], 0, elRawNameEnd[i]);
3139                                     expectedTagStack
3140                                             .append("</")
3141                                             .append(tagName)
3142                                             .append('>');
3143                                 }
3144                             }
3145                             expectedTagStack.append(" to close");
3146                             for (int i = depth; i > 0; i--) {
3147                                 if (i != depth) {
3148                                     expectedTagStack.append(" and"); // more than one end tag
3149                                 }
3150                                 if (elRawName == null || elRawName[i] == null) {
3151                                     String tagName = new String(buf, posStart + 1, pos - posStart - 1);
3152                                     expectedTagStack
3153                                             .append(" start tag <")
3154                                             .append(tagName)
3155                                             .append(">");
3156                                     expectedTagStack.append(" from line ").append(elRawNameLine[i]);
3157                                 } else {
3158                                     String tagName = new String(elRawName[i], 0, elRawNameEnd[i]);
3159                                     expectedTagStack
3160                                             .append(" start tag <")
3161                                             .append(tagName)
3162                                             .append(">");
3163                                     expectedTagStack.append(" from line ").append(elRawNameLine[i]);
3164                                 }
3165                             }
3166                             expectedTagStack.append(", parser stopped on");
3167                         }
3168                     }
3169                     throw new EOFException(
3170                             "no more data available" + expectedTagStack.toString() + getPositionDescription());
3171                 }
3172             }
3173         } else {
3174             throw new IOException("error reading input, returned " + ret);
3175         }
3176     }
3177 
3178     private char more() throws IOException, XmlPullParserException {
3179         if (pos >= bufEnd) {
3180             fillBuf();
3181             // this return value should be ignored as it is used in epilog parsing ...
3182             if (reachedEnd) throw new EOFException("no more data available" + getPositionDescription());
3183         }
3184         final char ch = buf[pos++];
3185         // line/columnNumber
3186         if (ch == '\n') {
3187             ++lineNumber;
3188             columnNumber = 1;
3189         } else {
3190             ++columnNumber;
3191         }
3192         // System.out.print(ch);
3193         return ch;
3194     }
3195 
3196     // /**
3197     // * This function returns position of parser in XML input stream
3198     // * (how many <b>characters</b> were processed.
3199     // * <p><b>NOTE:</b> this logical position and not byte offset as encodings
3200     // * such as UTF8 may use more than one byte to encode one character.
3201     // */
3202     // public int getCurrentInputPosition() {
3203     // return pos + bufAbsoluteStart;
3204     // }
3205 
3206     private void ensurePC(int end) {
3207         // assert end >= pc.length;
3208         final int newSize = end > READ_CHUNK_SIZE ? 2 * end : 2 * READ_CHUNK_SIZE;
3209         final char[] newPC = new char[newSize];
3210         if (TRACE_SIZING)
3211             System.out.println("TRACE_SIZING ensurePC() " + pc.length + " ==> " + newSize + " end=" + end);
3212         System.arraycopy(pc, 0, newPC, 0, pcEnd);
3213         pc = newPC;
3214         // assert end < pc.length;
3215     }
3216 
3217     private void joinPC() {
3218         // assert usePC == false;
3219         // assert posEnd > posStart;
3220         final int len = posEnd - posStart;
3221         final int newEnd = pcEnd + len + 1;
3222         if (newEnd >= pc.length) ensurePC(newEnd); // add 1 for extra space for one char
3223         // assert newEnd < pc.length;
3224         System.arraycopy(buf, posStart, pc, pcEnd, len);
3225         pcEnd += len;
3226         usePC = true;
3227     }
3228 
3229     private char requireInput(char ch, char[] input) throws XmlPullParserException, IOException {
3230         for (char anInput : input) {
3231             if (ch != anInput) {
3232                 throw new XmlPullParserException(
3233                         "expected " + printable(anInput) + " in " + new String(input) + " and not " + printable(ch),
3234                         this,
3235                         null);
3236             }
3237             ch = more();
3238         }
3239         return ch;
3240     }
3241 
3242     private char skipS(char ch) throws XmlPullParserException, IOException {
3243         while (isS(ch)) {
3244             ch = more();
3245         } // skip additional spaces
3246         return ch;
3247     }
3248 
3249     // nameStart / name lookup tables based on XML 1.1 http://www.w3.org/TR/2001/WD-xml11-20011213/
3250     private static final int LOOKUP_MAX = 0x400;
3251 
3252     private static final char LOOKUP_MAX_CHAR = (char) LOOKUP_MAX;
3253 
3254     // private static int lookupNameStartChar[] = new int[ LOOKUP_MAX_CHAR / 32 ];
3255     // private static int lookupNameChar[] = new int[ LOOKUP_MAX_CHAR / 32 ];
3256     private static final boolean[] lookupNameStartChar = new boolean[LOOKUP_MAX];
3257 
3258     private static final boolean[] lookupNameChar = new boolean[LOOKUP_MAX];
3259 
3260     private static void setName(char ch)
3261                 // { lookupNameChar[ (int)ch / 32 ] |= (1 << (ch % 32)); }
3262             {
3263         lookupNameChar[ch] = true;
3264     }
3265 
3266     private static void setNameStart(char ch)
3267                 // { lookupNameStartChar[ (int)ch / 32 ] |= (1 << (ch % 32)); setName(ch); }
3268             {
3269         lookupNameStartChar[ch] = true;
3270         setName(ch);
3271     }
3272 
3273     static {
3274         setNameStart(':');
3275         for (char ch = 'A'; ch <= 'Z'; ++ch) setNameStart(ch);
3276         setNameStart('_');
3277         for (char ch = 'a'; ch <= 'z'; ++ch) setNameStart(ch);
3278         for (char ch = '\u00c0'; ch <= '\u02FF'; ++ch) setNameStart(ch);
3279         for (char ch = '\u0370'; ch <= '\u037d'; ++ch) setNameStart(ch);
3280         for (char ch = '\u037f'; ch < '\u0400'; ++ch) setNameStart(ch);
3281 
3282         setName('-');
3283         setName('.');
3284         for (char ch = '0'; ch <= '9'; ++ch) setName(ch);
3285         setName('\u00b7');
3286         for (char ch = '\u0300'; ch <= '\u036f'; ++ch) setName(ch);
3287     }
3288 
3289     // protected boolean isNameStartChar( char ch )
3290     private static boolean isNameStartChar(char ch) {
3291         return ch < LOOKUP_MAX_CHAR
3292                 ? lookupNameStartChar[ch]
3293                 : (ch <= '\u2027') || (ch >= '\u202A' && ch <= '\u218F') || (ch >= '\u2800' && ch <= '\uFFEF');
3294 
3295         // if(ch < LOOKUP_MAX_CHAR) return lookupNameStartChar[ ch ];
3296         // else return ch <= '\u2027'
3297         // || (ch >= '\u202A' && ch <= '\u218F')
3298         // || (ch >= '\u2800' && ch <= '\uFFEF')
3299         // ;
3300         // return false;
3301         // return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == ':'
3302         // || (ch >= '0' && ch <= '9');
3303         // if(ch < LOOKUP_MAX_CHAR) return (lookupNameStartChar[ (int)ch / 32 ] & (1 << (ch % 32))) != 0;
3304         // if(ch <= '\u2027') return true;
3305         // //[#x202A-#x218F]
3306         // if(ch < '\u202A') return false;
3307         // if(ch <= '\u218F') return true;
3308         // // added parts [#x2800-#xD7FF] | [#xE000-#xFDCF] | [#xFDE0-#xFFEF] | [#x10000-#x10FFFF]
3309         // if(ch < '\u2800') return false;
3310         // if(ch <= '\uFFEF') return true;
3311         // return false;
3312 
3313         // else return (supportXml11 && ( (ch < '\u2027') || (ch > '\u2029' && ch < '\u2200') ...
3314     }
3315 
3316     // protected boolean isNameChar( char ch )
3317     private static boolean isNameChar(char ch) {
3318         // return isNameStartChar(ch);
3319 
3320         // if(ch < LOOKUP_MAX_CHAR) return (lookupNameChar[ (int)ch / 32 ] & (1 << (ch % 32))) != 0;
3321 
3322         return ch < LOOKUP_MAX_CHAR
3323                 ? lookupNameChar[ch]
3324                 : (ch <= '\u2027') || (ch >= '\u202A' && ch <= '\u218F') || (ch >= '\u2800' && ch <= '\uFFEF');
3325         // return false;
3326         // return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == ':'
3327         // || (ch >= '0' && ch <= '9');
3328         // if(ch < LOOKUP_MAX_CHAR) return (lookupNameStartChar[ (int)ch / 32 ] & (1 << (ch % 32))) != 0;
3329 
3330         // else return
3331         // else if(ch <= '\u2027') return true;
3332         // //[#x202A-#x218F]
3333         // else if(ch < '\u202A') return false;
3334         // else if(ch <= '\u218F') return true;
3335         // // added parts [#x2800-#xD7FF] | [#xE000-#xFDCF] | [#xFDE0-#xFFEF] | [#x10000-#x10FFFF]
3336         // else if(ch < '\u2800') return false;
3337         // else if(ch <= '\uFFEF') return true;
3338         // else return false;
3339     }
3340 
3341     private static boolean isS(char ch) {
3342         return (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t');
3343         // || (supportXml11 && (ch == '\u0085' || ch == '\u2028');
3344     }
3345 
3346     // protected boolean isChar(char ch) { return (ch < '\uD800' || ch > '\uDFFF')
3347     // ch != '\u0000' ch < '\uFFFE'
3348 
3349     // private char printable(char ch) { return ch; }
3350     private static String printable(int ch) {
3351         if (ch == '\n') {
3352             return "\\n";
3353         } else if (ch == '\r') {
3354             return "\\r";
3355         } else if (ch == '\t') {
3356             return "\\t";
3357         } else if (ch == '\'') {
3358             return "\\'";
3359         }
3360         if (ch > 127 || ch < 32) {
3361             return "\\u" + Integer.toHexString(ch);
3362         }
3363         if (Character.isBmpCodePoint(ch)) {
3364             return Character.toString((char) ch);
3365         } else {
3366             return new String(new char[] {Character.highSurrogate(ch), Character.lowSurrogate(ch)});
3367         }
3368     }
3369 
3370     private static String printable(String s) {
3371         if (s == null) return null;
3372         final int sLen = s.codePointCount(0, s.length());
3373         StringBuilder buf = new StringBuilder(sLen + 10);
3374         for (int i = 0; i < sLen; ++i) {
3375             buf.append(printable(s.codePointAt(i)));
3376         }
3377         s = buf.toString();
3378         return s;
3379     }
3380 }
3381 
3382 /*
3383  * Indiana University Extreme! Lab Software License, Version 1.2 Copyright (C) 2003 The Trustees of Indiana University.
3384  * All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted
3385  * provided that the following conditions are met: 1) All redistributions of source code must retain the above copyright
3386  * notice, the list of authors in the original source code, this list of conditions and the disclaimer listed in this
3387  * license; 2) All redistributions in binary form must reproduce the above copyright notice, this list of conditions and
3388  * the disclaimer listed in this license in the documentation and/or other materials provided with the distribution; 3)
3389  * Any documentation included with all redistributions must include the following acknowledgement: "This product
3390  * includes software developed by the Indiana University Extreme! Lab. For further information please visit
3391  * http://www.extreme.indiana.edu/" Alternatively, this acknowledgment may appear in the software itself, and wherever
3392  * such third-party acknowledgments normally appear. 4) The name "Indiana University" or "Indiana University Extreme!
3393  * Lab" shall not be used to endorse or promote products derived from this software without prior written permission
3394  * from Indiana University. For written permission, please contact http://www.extreme.indiana.edu/. 5) Products derived
3395  * from this software may not use "Indiana University" name nor may "Indiana University" appear in their name, without
3396  * prior written permission of the Indiana University. Indiana University provides no reassurances that the source code
3397  * provided does not infringe the patent or any other intellectual property rights of any other entity. Indiana
3398  * University disclaims any liability to any recipient for claims brought by any other entity based on infringement of
3399  * intellectual property rights or otherwise. LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
3400  * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES NO WARRANTIES AND MAKES NO
3401  * REPRESENTATION THAT SOFTWARE IS FREE OF INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
3402  * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", "VIRUSES", "TROJAN HORSES", "TRAP
3403  * DOORS", "WORMS", OR OTHER HARMFUL CODE. LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
3404  * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION GENERATED USING SOFTWARE.
3405  */