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