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}