001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.xml;
014
015import static javax.xml.stream.XMLStreamConstants.*;
016import static org.apache.juneau.internal.StringUtils.*;
017import static org.apache.juneau.xml.annotation.XmlFormat.*;
018
019import java.lang.reflect.*;
020import java.util.*;
021
022import javax.xml.stream.*;
023import javax.xml.stream.util.*;
024
025import org.apache.juneau.*;
026import org.apache.juneau.parser.*;
027import org.apache.juneau.transform.*;
028import org.apache.juneau.xml.annotation.*;
029
030/**
031 * Session object that lives for the duration of a single use of {@link XmlParser}.
032 *
033 * <p>
034 * This class is NOT thread safe.
035 * It is typically discarded after one-time use although it can be reused against multiple inputs.
036 */
037@SuppressWarnings({ "unchecked", "rawtypes" })
038public class XmlParserSession extends ReaderParserSession {
039
040   private static final int UNKNOWN=0, OBJECT=1, ARRAY=2, STRING=3, NUMBER=4, BOOLEAN=5, NULL=6;
041
042   private final XmlParser ctx;
043   private final StringBuilder rsb = new StringBuilder();  // Reusable string builder used in this class.
044
045   /**
046    * Create a new session using properties specified in the context.
047    *
048    * @param ctx
049    *    The context creating this session object.
050    *    The context contains all the configuration settings for this object.
051    * @param args
052    *    Runtime session arguments.
053    */
054   protected XmlParserSession(XmlParser ctx, ParserSessionArgs args) {
055      super(ctx, args);
056      this.ctx = ctx;
057   }
058
059   @Override /* Session */
060   public ObjectMap asMap() {
061      return super.asMap()
062         .append("XmlParser", new ObjectMap()
063         );
064   }
065
066   /**
067    * Wrap the specified reader in a STAX reader based on settings in this context.
068    *
069    * @param pipe The parser input.
070    * @return The new STAX reader.
071    * @throws Exception If problem occurred trying to create reader.
072    */
073   protected final XmlReader getXmlReader(ParserPipe pipe) throws Exception {
074      return new XmlReader(pipe, isValidating(), getReporter(), getResolver(), getEventAllocator());
075   }
076
077   /**
078    * Decodes and trims the specified string.
079    *
080    * <p>
081    * Any <js>'_x####_'</js> sequences in the string will be decoded.
082    *
083    * @param s The string to be decoded.
084    * @return The decoded string.
085    */
086   protected final String decodeString(String s) {
087      if (s == null)
088         return null;
089      rsb.setLength(0);
090      s = XmlUtils.decode(s, rsb);
091      if (isTrimStrings())
092         s = s.trim();
093      return s;
094   }
095
096   /*
097    * Returns the name of the current XML element.
098    * Any <js>'_x####_'</js> sequences in the string will be decoded.
099    */
100   private String getElementName(XmlReader r) {
101      return decodeString(r.getLocalName());
102   }
103
104   /*
105    * Returns the name of the specified attribute on the current XML element.
106    * Any <js>'_x####_'</js> sequences in the string will be decoded.
107    */
108   private String getAttributeName(XmlReader r, int i) {
109      return decodeString(r.getAttributeLocalName(i));
110   }
111
112   /*
113    * Returns the value of the specified attribute on the current XML element.
114    * Any <js>'_x####_'</js> sequences in the string will be decoded.
115    */
116   private String getAttributeValue(XmlReader r, int i) {
117      return decodeString(r.getAttributeValue(i));
118   }
119
120   /**
121    * Returns the text content of the current XML element.
122    *
123    * <p>
124    * Any <js>'_x####_'</js> sequences in the string will be decoded.
125    *
126    * <p>
127    * Leading and trailing whitespace (unencoded) will be trimmed from the result.
128    *
129    * @param r The reader to read the element text from.
130    * @return The decoded text.  <jk>null</jk> if the text consists of the sequence <js>'_x0000_'</js>.
131    * @throws Exception
132    */
133   protected String getElementText(XmlReader r) throws Exception {
134      return decodeString(r.getElementText().trim());
135   }
136
137   /*
138    * Returns the content of the current CHARACTERS node.
139    * Any <js>'_x####_'</js> sequences in the string will be decoded.
140    * Leading and trailing whitespace (unencoded) will be trimmed from the result.
141    */
142   private String getText(XmlReader r, boolean trim) {
143      String s = r.getText();
144      if (trim)
145         s = s.trim();
146      if (s.isEmpty())
147         return null;
148      return decodeString(s);
149   }
150
151   /*
152    * Shortcut for calling <code>getText(r, <jk>true</jk>);</code>.
153    */
154   private String getText(XmlReader r) {
155      return getText(r, true);
156   }
157
158   /*
159    * Takes the element being read from the XML stream reader and reconstructs it as XML.
160    * Used when reconstructing bean properties of type {@link XmlFormat#XMLTEXT}.
161    */
162   private String getElementAsString(XmlReader r) {
163      int t = r.getEventType();
164      if (t > 2)
165         throw new FormattedRuntimeException("Invalid event type on stream reader for elementToString() method: ''{0}''", XmlUtils.toReadableEvent(r));
166      rsb.setLength(0);
167      rsb.append("<").append(t == 1 ? "" : "/").append(r.getLocalName());
168      if (t == 1)
169         for (int i = 0; i < r.getAttributeCount(); i++)
170            rsb.append(' ').append(r.getAttributeName(i)).append('=').append('\'').append(r.getAttributeValue(i)).append('\'');
171      rsb.append('>');
172      return rsb.toString();
173   }
174
175   /**
176    * Parses the current element as text.
177    *
178    * @param r
179    * @return The parsed text.
180    * @throws Exception
181    */
182   protected String parseText(XmlReader r) throws Exception {
183      // Note that this is different than {@link #getText(XmlReader)} since it assumes that we're pointing to a
184      // whitespace element.
185
186      StringBuilder sb2 = getStringBuilder();
187
188      int depth = 0;
189      while (true) {
190         int et = r.getEventType();
191         if (et == START_ELEMENT) {
192            sb2.append(getElementAsString(r));
193            depth++;
194         } else if (et == CHARACTERS) {
195            sb2.append(getText(r));
196         } else if (et == END_ELEMENT) {
197            sb2.append(getElementAsString(r));
198            depth--;
199            if (depth <= 0)
200               break;
201         }
202         et = r.next();
203      }
204      String s = sb2.toString();
205      returnStringBuilder(sb2);
206      return s;
207   }
208
209   /**
210    * Returns <jk>true</jk> if the current element is a whitespace element.
211    *
212    * <p>
213    * For the XML parser, this always returns <jk>false</jk>.
214    * However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
215    *
216    * @param r The XML stream reader to read the current event from.
217    * @return <jk>true</jk> if the current element is a whitespace element.
218    */
219   protected boolean isWhitespaceElement(XmlReader r) {
220      return false;
221   }
222
223   /**
224    * Parses the current whitespace element.
225    *
226    * <p>
227    * For the XML parser, this always returns <jk>null</jk> since there is no concept of a whitespace element.
228    * However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
229    *
230    * @param r The XML stream reader to read the current event from.
231    * @return The whitespace character or characters.
232    * @throws XMLStreamException
233    * @throws Exception
234    */
235   protected String parseWhitespaceElement(XmlReader r) throws Exception {
236      return null;
237   }
238
239   @Override /* ParserSession */
240   protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception {
241      return parseAnything(type, null, getXmlReader(pipe), getOuter(), true, null);
242   }
243
244   @Override /* ReaderParserSession */
245   protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception {
246      ClassMeta cm = getClassMeta(m.getClass(), keyType, valueType);
247      return parseIntoMap(pipe, m, cm.getKeyType(), cm.getValueType());
248   }
249
250   @Override /* ReaderParserSession */
251   protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception {
252      ClassMeta cm = getClassMeta(c.getClass(), elementType);
253      return parseIntoCollection(pipe, c, cm.getElementType());
254   }
255
256   /**
257    * Workhorse method.
258    *
259    * @param eType The expected type of object.
260    * @param currAttr The current bean property name.
261    * @param r The reader.
262    * @param outer The outer object.
263    * @param isRoot If <jk>true</jk>, then we're serializing a root element in the document.
264    * @param pMeta The bean property metadata.
265    * @return The parsed object.
266    * @throws Exception
267    */
268   protected <T> T parseAnything(ClassMeta<T> eType, String currAttr, XmlReader r,
269         Object outer, boolean isRoot, BeanPropertyMeta pMeta) throws Exception {
270
271      if (eType == null)
272         eType = (ClassMeta<T>)object();
273      PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
274      BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
275      ClassMeta<?> sType = null;
276      if (builder != null)
277         sType = builder.getBuilderClassMeta(this);
278      else if (swap != null)
279         sType = swap.getSwapClassMeta(this);
280      else
281         sType = eType;
282      setCurrentClass(sType);
283
284      String wrapperAttr = (isRoot && isPreserveRootElement()) ? r.getName().getLocalPart() : null;
285      String typeAttr = r.getAttributeValue(null, getBeanTypePropertyName(eType));
286      int jsonType = getJsonType(typeAttr);
287      String elementName = getElementName(r);
288      if (jsonType == 0) {
289         if (elementName == null || elementName.equals(currAttr))
290            jsonType = UNKNOWN;
291         else {
292            typeAttr = elementName;
293            jsonType = getJsonType(elementName);
294         }
295      }
296
297      ClassMeta tcm = getClassMeta(typeAttr, pMeta, eType);
298      if (tcm == null && elementName != null && ! elementName.equals(currAttr))
299         tcm = getClassMeta(elementName, pMeta, eType);
300      if (tcm != null)
301         sType = eType = tcm;
302
303      Object o = null;
304
305      if (jsonType == NULL) {
306         r.nextTag();   // Discard end tag
307         return null;
308      }
309
310      if (sType.isObject()) {
311         if (jsonType == OBJECT) {
312            ObjectMap m = new ObjectMap(this);
313            parseIntoMap(r, m, string(), object(), pMeta);
314            if (wrapperAttr != null)
315               m = new ObjectMap(this).append(wrapperAttr, m);
316            o = cast(m, pMeta, eType);
317         } else if (jsonType == ARRAY)
318            o = parseIntoCollection(r, new ObjectList(this), null, pMeta);
319         else if (jsonType == STRING) {
320            o = getElementText(r);
321            if (sType.isChar())
322               o = parseCharacter(o);
323         }
324         else if (jsonType == NUMBER)
325            o = parseNumber(getElementText(r), null);
326         else if (jsonType == BOOLEAN)
327            o = Boolean.parseBoolean(getElementText(r));
328         else if (jsonType == UNKNOWN)
329            o = getUnknown(r);
330      } else if (sType.isBoolean()) {
331         o = Boolean.parseBoolean(getElementText(r));
332      } else if (sType.isCharSequence()) {
333         o = getElementText(r);
334      } else if (sType.isChar()) {
335         o = parseCharacter(getElementText(r));
336      } else if (sType.isMap()) {
337         Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(this));
338         o = parseIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta);
339         if (wrapperAttr != null)
340            o = new ObjectMap(this).append(wrapperAttr, m);
341      } else if (sType.isCollection()) {
342         Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance(outer) : new ObjectList(this));
343         o = parseIntoCollection(r, l, sType, pMeta);
344      } else if (sType.isNumber()) {
345         o = parseNumber(getElementText(r), (Class<? extends Number>)sType.getInnerClass());
346      } else if (builder != null || sType.canCreateNewBean(outer)) {
347         if (sType.getExtendedMeta(XmlClassMeta.class).getFormat() == COLLAPSED) {
348            String fieldName = r.getLocalName();
349            BeanMap<?> m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass());
350            BeanPropertyMeta bpm = m.getMeta().getExtendedMeta(XmlBeanMeta.class).getPropertyMeta(fieldName);
351            ClassMeta<?> cm = m.getMeta().getClassMeta();
352            Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, null);
353            setName(cm, value, currAttr);
354            bpm.set(m, currAttr, value);
355            o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean();
356         } else {
357            BeanMap m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass());
358            m = parseIntoBean(r, m);
359            o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean();
360         }
361      } else if (sType.isArray() || sType.isArgs()) {
362         ArrayList l = (ArrayList)parseIntoCollection(r, new ArrayList(), sType, pMeta);
363         o = toArray(sType, l);
364      } else if (sType.canCreateNewInstanceFromString(outer)) {
365         o = sType.newInstanceFromString(outer, getElementText(r));
366      } else if (sType.canCreateNewInstanceFromNumber(outer)) {
367         o = sType.newInstanceFromNumber(this, outer, parseNumber(getElementText(r), sType.getNewInstanceFromNumberClass()));
368      } else {
369         throw new ParseException(this,
370            "Class ''{0}'' could not be instantiated.  Reason: ''{1}'', property: ''{2}''",
371            sType.getInnerClass().getName(), sType.getNotABeanReason(), pMeta == null ? null : pMeta.getName());
372      }
373
374      if (swap != null && o != null)
375         o = swap.unswap(this, o, eType);
376
377      if (outer != null)
378         setParent(eType, o, outer);
379
380      return (T)o;
381   }
382
383   private <K,V> Map<K,V> parseIntoMap(XmlReader r, Map<K,V> m, ClassMeta<K> keyType,
384         ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws Exception {
385      int depth = 0;
386      for (int i = 0; i < r.getAttributeCount(); i++) {
387         String a = r.getAttributeLocalName(i);
388         // TODO - Need better handling of namespaces here.
389         if (! (a.equals(getBeanTypePropertyName(null)))) {
390            K key = trim(convertAttrToType(m, a, keyType));
391            V value = trim(convertAttrToType(m, r.getAttributeValue(i), valueType));
392            setName(valueType, value, key);
393            m.put(key, value);
394         }
395      }
396      do {
397         int event = r.nextTag();
398         String currAttr;
399         if (event == START_ELEMENT) {
400            depth++;
401            currAttr = getElementName(r);
402            K key = convertAttrToType(m, currAttr, keyType);
403            V value = parseAnything(valueType, currAttr, r, m, false, pMeta);
404            setName(valueType, value, currAttr);
405            if (valueType.isObject() && m.containsKey(key)) {
406               Object o = m.get(key);
407               if (o instanceof List)
408                  ((List)o).add(value);
409               else
410                  m.put(key, (V)new ObjectList(o, value).setBeanSession(this));
411            } else {
412               m.put(key, value);
413            }
414         } else if (event == END_ELEMENT) {
415            depth--;
416            return m;
417         }
418      } while (depth > 0);
419      return m;
420   }
421
422   private <E> Collection<E> parseIntoCollection(XmlReader r, Collection<E> l,
423         ClassMeta<?> type, BeanPropertyMeta pMeta) throws Exception {
424      int depth = 0;
425      int argIndex = 0;
426      do {
427         int event = r.nextTag();
428         if (event == START_ELEMENT) {
429            depth++;
430            ClassMeta<?> elementType = type == null ? object() : type.isArgs() ? type.getArg(argIndex++) : type.getElementType();
431            E value = (E)parseAnything(elementType, null, r, l, false, pMeta);
432            l.add(value);
433         } else if (event == END_ELEMENT) {
434            depth--;
435            return l;
436         }
437      } while (depth > 0);
438      return l;
439   }
440
441   private static int getJsonType(String s) {
442      if (s == null)
443         return UNKNOWN;
444      char c = s.charAt(0);
445      switch(c) {
446         case 'o': return (s.equals("object") ? OBJECT : UNKNOWN);
447         case 'a': return (s.equals("array") ? ARRAY : UNKNOWN);
448         case 's': return (s.equals("string") ? STRING : UNKNOWN);
449         case 'b': return (s.equals("boolean") ? BOOLEAN : UNKNOWN);
450         case 'n': {
451            c = s.charAt(2);
452            switch(c) {
453               case 'm': return (s.equals("number") ? NUMBER : UNKNOWN);
454               case 'l': return (s.equals("null") ? NULL : UNKNOWN);
455            }
456            //return NUMBER;
457         }
458      }
459      return UNKNOWN;
460   }
461
462   private <T> BeanMap<T> parseIntoBean(XmlReader r, BeanMap<T> m) throws Exception {
463      BeanMeta<?> bMeta = m.getMeta();
464      XmlBeanMeta xmlMeta = bMeta.getExtendedMeta(XmlBeanMeta.class);
465
466      for (int i = 0; i < r.getAttributeCount(); i++) {
467         String key = getAttributeName(r, i);
468         String val = r.getAttributeValue(i);
469         String ns = r.getAttributeNamespace(i);
470         BeanPropertyMeta bpm = xmlMeta.getPropertyMeta(key);
471         if (bpm == null) {
472            if (xmlMeta.getAttrsProperty() != null) {
473               xmlMeta.getAttrsProperty().add(m, key, key, val);
474            } else if (ns == null) {
475               onUnknownProperty(key, m);
476            }
477         } else {
478            bpm.set(m, key, val);
479         }
480      }
481
482      BeanPropertyMeta cp = xmlMeta.getContentProperty();
483      XmlFormat cpf = xmlMeta.getContentFormat();
484      boolean trim = cp == null || ! cpf.isOneOf(MIXED_PWS, TEXT_PWS);
485      ClassMeta<?> cpcm = (cp == null ? object() : cp.getClassMeta());
486      StringBuilder sb = null;
487      BeanRegistry breg = cp == null ? null : cp.getBeanRegistry();
488      LinkedList<Object> l = null;
489
490      int depth = 0;
491      do {
492         int event = r.next();
493         String currAttr;
494         // We only care about text in MIXED mode.
495         // Ignore if in ELEMENTS mode.
496         if (event == CHARACTERS) {
497            if (cp != null && cpf.isOneOf(MIXED, MIXED_PWS)) {
498               if (cpcm.isCollectionOrArray()) {
499                  if (l == null)
500                     l = new LinkedList<>();
501                  l.add(getText(r, false));
502               } else {
503                  cp.set(m, null, getText(r, trim));
504               }
505            } else if (cpf != ELEMENTS) {
506               String s = getText(r, trim);
507               if (s != null) {
508                  if (sb == null)
509                     sb = getStringBuilder();
510                  sb.append(s);
511               }
512            } else {
513               // Do nothing...we're in ELEMENTS mode.
514            }
515         } else if (event == START_ELEMENT) {
516            if (cp != null && cpf.isOneOf(TEXT, TEXT_PWS)) {
517               String s = parseText(r);
518               if (s != null) {
519                  if (sb == null)
520                     sb = getStringBuilder();
521                  sb.append(s);
522               }
523               depth--;
524            } else if (cpf == XMLTEXT) {
525               if (sb == null)
526                  sb = getStringBuilder();
527               sb.append(getElementAsString(r));
528               depth++;
529            } else if (cp != null && cpf.isOneOf(MIXED, MIXED_PWS)) {
530               if (isWhitespaceElement(r) && (breg == null || ! breg.hasName(r.getLocalName()))) {
531                  if (cpcm.isCollectionOrArray()) {
532                     if (l == null)
533                        l = new LinkedList<>();
534                     l.add(parseWhitespaceElement(r));
535                  } else {
536                     cp.set(m, null, parseWhitespaceElement(r));
537                  }
538               } else {
539                  if (cpcm.isCollectionOrArray()) {
540                     if (l == null)
541                        l = new LinkedList<>();
542                     l.add(parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
543                  } else {
544                     cp.set(m, null, parseAnything(cpcm, cp.getName(), r, m.getBean(false), false, cp));
545                  }
546               }
547            } else if (cp != null && cpf == ELEMENTS) {
548               cp.add(m, null, parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
549            } else {
550               currAttr = getElementName(r);
551               BeanPropertyMeta pMeta = xmlMeta.getPropertyMeta(currAttr);
552               if (pMeta == null) {
553                  onUnknownProperty(currAttr, m);
554                  skipCurrentTag(r);
555               } else {
556                  setCurrentProperty(pMeta);
557                  XmlFormat xf = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat();
558                  if (xf == COLLAPSED) {
559                     ClassMeta<?> et = pMeta.getClassMeta().getElementType();
560                     Object value = parseAnything(et, currAttr, r, m.getBean(false), false, pMeta);
561                     setName(et, value, currAttr);
562                     pMeta.add(m, currAttr, value);
563                  } else if (xf == ATTR)  {
564                     pMeta.set(m, currAttr, getAttributeValue(r, 0));
565                     r.nextTag();
566                  } else {
567                     ClassMeta<?> cm = pMeta.getClassMeta();
568                     Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, pMeta);
569                     setName(cm, value, currAttr);
570                     pMeta.set(m, currAttr, value);
571                  }
572                  setCurrentProperty(null);
573               }
574            }
575         } else if (event == END_ELEMENT) {
576            if (depth > 0) {
577               if (cpf == XMLTEXT) {
578                  if (sb == null)
579                     sb = getStringBuilder();
580                  sb.append(getElementAsString(r));
581               }
582               else
583                  throw new ParseException("End element found where one was not expected.  {0}", XmlUtils.toReadableEvent(r));
584            }
585            depth--;
586         } else if (event == COMMENT) {
587            // Ignore comments.
588         } else {
589            throw new ParseException("Unexpected event type: {0}", XmlUtils.toReadableEvent(r));
590         }
591      } while (depth >= 0);
592
593      if (sb != null && cp != null)
594         cp.set(m, null, sb.toString());
595      else if (l != null && cp != null)
596         cp.set(m, null, XmlUtils.collapseTextNodes(l));
597
598      returnStringBuilder(sb);
599      return m;
600   }
601
602   private static void skipCurrentTag(XmlReader r) throws XMLStreamException {
603      int depth = 1;
604      do {
605         int event = r.next();
606         if (event == START_ELEMENT)
607            depth++;
608         else if (event == END_ELEMENT)
609            depth--;
610      } while (depth > 0);
611   }
612
613   private Object getUnknown(XmlReader r) throws Exception {
614      if (r.getEventType() != START_ELEMENT) {
615         throw new ParseException(this, "Parser must be on START_ELEMENT to read next text.");
616      }
617      ObjectMap m = null;
618
619      // If this element has attributes, then it's always an ObjectMap.
620      if (r.getAttributeCount() > 0) {
621         m = new ObjectMap(this);
622         for (int i = 0; i < r.getAttributeCount(); i++) {
623            String key = getAttributeName(r, i);
624            String val = r.getAttributeValue(i);
625            if (! key.equals(getBeanTypePropertyName(null)))
626               m.put(key, val);
627         }
628      }
629      int eventType = r.next();
630      StringBuilder sb = getStringBuilder();
631      while (eventType != END_ELEMENT) {
632         if (eventType == CHARACTERS || eventType == CDATA || eventType == SPACE || eventType == ENTITY_REFERENCE) {
633            sb.append(r.getText());
634         } else if (eventType == PROCESSING_INSTRUCTION || eventType == COMMENT) {
635            // skipping
636         } else if (eventType == END_DOCUMENT) {
637            throw new ParseException(this, "Unexpected end of document when reading element text content");
638         } else if (eventType == START_ELEMENT) {
639            // Oops...this has an element in it.
640            // Parse it as a map.
641            if (m == null)
642               m = new ObjectMap(this);
643            int depth = 0;
644            do {
645               int event = (eventType == -1 ? r.nextTag() : eventType);
646               String currAttr;
647               if (event == START_ELEMENT) {
648                  depth++;
649                  currAttr = getElementName(r);
650                  String key = convertAttrToType(null, currAttr, string());
651                  Object value = parseAnything(object(), currAttr, r, null, false, null);
652                  if (m.containsKey(key)) {
653                     Object o = m.get(key);
654                     if (o instanceof ObjectList)
655                        ((ObjectList)o).add(value);
656                     else
657                        m.put(key, new ObjectList(o, value).setBeanSession(this));
658                  } else {
659                     m.put(key, value);
660                  }
661
662               } else if (event == END_ELEMENT) {
663                  depth--;
664                  break;
665               }
666               eventType = -1;
667            } while (depth > 0);
668            break;
669         } else {
670            throw new ParseException(this, "Unexpected event type ''{0}''", eventType);
671         }
672         eventType = r.next();
673      }
674      String s = sb.toString().trim();
675      returnStringBuilder(sb);
676      s = decodeString(s);
677      if (m != null) {
678         if (! s.isEmpty())
679            m.put("contents", s);
680         return m;
681      }
682      return s;
683   }
684   //-----------------------------------------------------------------------------------------------------------------
685   // Properties
686   //-----------------------------------------------------------------------------------------------------------------
687
688   /**
689    * Configuration property:  Enable validation.
690    *
691    * @see XmlParser#XML_validating
692    * @return
693    *    <jk>true</jk> if XML document will be validated.
694    */
695   protected final boolean isValidating() {
696      return ctx.isValidating();
697   }
698
699   /**
700    * Configuration property:  Preserve root element during generalized parsing.
701    *
702    * @see XmlParser#XML_preserveRootElement
703    * @return
704    *    <jk>true</jk> if when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
705    *    is the root element name.
706    */
707   protected final boolean isPreserveRootElement() {
708      return ctx.isPreserveRootElement();
709   }
710
711   /**
712    * Configuration property:  XML reporter.
713    *
714    * @see XmlParser#XML_reporter
715    * @return
716    *    The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
717    */
718   protected final XMLReporter getReporter() {
719      return ctx.getReporter();
720   }
721
722   /**
723    * Configuration property:  XML resolver.
724    *
725    * @see XmlParser#XML_resolver
726    * @return
727    *    The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
728    */
729   protected final XMLResolver getResolver() {
730      return ctx.getResolver();
731   }
732
733   /**
734    * Configuration property:  XML event allocator.
735    *
736    * @see XmlParser#XML_eventAllocator
737    * @return
738    *    The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
739    */
740   protected final XMLEventAllocator getEventAllocator() {
741      return ctx.getEventAllocator();
742   }
743}