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 291 if (sType.isOptional()) 292 return (T)Optional.ofNullable(parseAnything(eType.getElementType(), currAttr, r, outer, isRoot, pMeta)); 293 294 setCurrentClass(sType); 295 296 String wrapperAttr = (isRoot && isPreserveRootElement()) ? r.getName().getLocalPart() : null; 297 String typeAttr = r.getAttributeValue(null, getBeanTypePropertyName(eType)); 298 int jsonType = getJsonType(typeAttr); 299 String elementName = getElementName(r); 300 if (jsonType == 0) { 301 if (elementName == null || elementName.equals(currAttr)) 302 jsonType = UNKNOWN; 303 else { 304 typeAttr = elementName; 305 jsonType = getJsonType(elementName); 306 } 307 } 308 309 ClassMeta tcm = getClassMeta(typeAttr, pMeta, eType); 310 if (tcm == null && elementName != null && ! elementName.equals(currAttr)) 311 tcm = getClassMeta(elementName, pMeta, eType); 312 if (tcm != null) 313 sType = eType = tcm; 314 315 Object o = null; 316 317 if (jsonType == NULL) { 318 r.nextTag(); // Discard end tag 319 return null; 320 } 321 322 if (sType.isObject()) { 323 if (jsonType == OBJECT) { 324 ObjectMap m = new ObjectMap(this); 325 parseIntoMap(r, m, string(), object(), pMeta); 326 if (wrapperAttr != null) 327 m = new ObjectMap(this).append(wrapperAttr, m); 328 o = cast(m, pMeta, eType); 329 } else if (jsonType == ARRAY) 330 o = parseIntoCollection(r, new ObjectList(this), null, pMeta); 331 else if (jsonType == STRING) { 332 o = getElementText(r); 333 if (sType.isChar()) 334 o = parseCharacter(o); 335 } 336 else if (jsonType == NUMBER) 337 o = parseNumber(getElementText(r), null); 338 else if (jsonType == BOOLEAN) 339 o = Boolean.parseBoolean(getElementText(r)); 340 else if (jsonType == UNKNOWN) 341 o = getUnknown(r); 342 } else if (sType.isBoolean()) { 343 o = Boolean.parseBoolean(getElementText(r)); 344 } else if (sType.isCharSequence()) { 345 o = getElementText(r); 346 } else if (sType.isChar()) { 347 o = parseCharacter(getElementText(r)); 348 } else if (sType.isMap()) { 349 Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(this)); 350 o = parseIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta); 351 if (wrapperAttr != null) 352 o = new ObjectMap(this).append(wrapperAttr, m); 353 } else if (sType.isCollection()) { 354 Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance(outer) : new ObjectList(this)); 355 o = parseIntoCollection(r, l, sType, pMeta); 356 } else if (sType.isNumber()) { 357 o = parseNumber(getElementText(r), (Class<? extends Number>)sType.getInnerClass()); 358 } else if (builder != null || sType.canCreateNewBean(outer)) { 359 if (sType.getExtendedMeta(XmlClassMeta.class).getFormat() == COLLAPSED) { 360 String fieldName = r.getLocalName(); 361 BeanMap<?> m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass()); 362 BeanPropertyMeta bpm = m.getMeta().getExtendedMeta(XmlBeanMeta.class).getPropertyMeta(fieldName); 363 ClassMeta<?> cm = m.getMeta().getClassMeta(); 364 Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, null); 365 setName(cm, value, currAttr); 366 bpm.set(m, currAttr, value); 367 o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean(); 368 } else { 369 BeanMap m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass()); 370 m = parseIntoBean(r, m); 371 o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean(); 372 } 373 } else if (sType.isArray() || sType.isArgs()) { 374 ArrayList l = (ArrayList)parseIntoCollection(r, new ArrayList(), sType, pMeta); 375 o = toArray(sType, l); 376 } else if (sType.canCreateNewInstanceFromString(outer)) { 377 o = sType.newInstanceFromString(outer, getElementText(r)); 378 } else { 379 throw new ParseException(this, 380 "Class ''{0}'' could not be instantiated. Reason: ''{1}'', property: ''{2}''", 381 sType.getInnerClass().getName(), sType.getNotABeanReason(), pMeta == null ? null : pMeta.getName()); 382 } 383 384 if (swap != null && o != null) 385 o = unswap(swap, o, eType); 386 387 if (outer != null) 388 setParent(eType, o, outer); 389 390 return (T)o; 391 } 392 393 private <K,V> Map<K,V> parseIntoMap(XmlReader r, Map<K,V> m, ClassMeta<K> keyType, 394 ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException, XMLStreamException { 395 int depth = 0; 396 for (int i = 0; i < r.getAttributeCount(); i++) { 397 String a = r.getAttributeLocalName(i); 398 // TODO - Need better handling of namespaces here. 399 if (! (a.equals(getBeanTypePropertyName(null)))) { 400 K key = trim(convertAttrToType(m, a, keyType)); 401 V value = trim(convertAttrToType(m, r.getAttributeValue(i), valueType)); 402 setName(valueType, value, key); 403 m.put(key, value); 404 } 405 } 406 do { 407 int event = r.nextTag(); 408 String currAttr; 409 if (event == START_ELEMENT) { 410 depth++; 411 currAttr = getElementName(r); 412 K key = convertAttrToType(m, currAttr, keyType); 413 V value = parseAnything(valueType, currAttr, r, m, false, pMeta); 414 setName(valueType, value, currAttr); 415 if (valueType.isObject() && m.containsKey(key)) { 416 Object o = m.get(key); 417 if (o instanceof List) 418 ((List)o).add(value); 419 else 420 m.put(key, (V)new ObjectList(o, value).setBeanSession(this)); 421 } else { 422 m.put(key, value); 423 } 424 } else if (event == END_ELEMENT) { 425 depth--; 426 return m; 427 } 428 } while (depth > 0); 429 return m; 430 } 431 432 private <E> Collection<E> parseIntoCollection(XmlReader r, Collection<E> l, 433 ClassMeta<?> type, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException, XMLStreamException { 434 int depth = 0; 435 int argIndex = 0; 436 do { 437 int event = r.nextTag(); 438 if (event == START_ELEMENT) { 439 depth++; 440 ClassMeta<?> elementType = type == null ? object() : type.isArgs() ? type.getArg(argIndex++) : type.getElementType(); 441 E value = (E)parseAnything(elementType, null, r, l, false, pMeta); 442 l.add(value); 443 } else if (event == END_ELEMENT) { 444 depth--; 445 return l; 446 } 447 } while (depth > 0); 448 return l; 449 } 450 451 private static int getJsonType(String s) { 452 if (s == null) 453 return UNKNOWN; 454 char c = s.charAt(0); 455 switch(c) { 456 case 'o': return (s.equals("object") ? OBJECT : UNKNOWN); 457 case 'a': return (s.equals("array") ? ARRAY : UNKNOWN); 458 case 's': return (s.equals("string") ? STRING : UNKNOWN); 459 case 'b': return (s.equals("boolean") ? BOOLEAN : UNKNOWN); 460 case 'n': { 461 c = s.charAt(2); 462 switch(c) { 463 case 'm': return (s.equals("number") ? NUMBER : UNKNOWN); 464 case 'l': return (s.equals("null") ? NULL : UNKNOWN); 465 } 466 //return NUMBER; 467 } 468 } 469 return UNKNOWN; 470 } 471 472 private <T> BeanMap<T> parseIntoBean(XmlReader r, BeanMap<T> m) throws IOException, ParseException, ExecutableException, XMLStreamException { 473 BeanMeta<?> bMeta = m.getMeta(); 474 XmlBeanMeta xmlMeta = bMeta.getExtendedMeta(XmlBeanMeta.class); 475 476 for (int i = 0; i < r.getAttributeCount(); i++) { 477 String key = getAttributeName(r, i); 478 String val = r.getAttributeValue(i); 479 String ns = r.getAttributeNamespace(i); 480 BeanPropertyMeta bpm = xmlMeta.getPropertyMeta(key); 481 if (bpm == null) { 482 if (xmlMeta.getAttrsProperty() != null) { 483 xmlMeta.getAttrsProperty().add(m, key, key, val); 484 } else if (ns == null) { 485 onUnknownProperty(key, m); 486 } 487 } else { 488 bpm.set(m, key, val); 489 } 490 } 491 492 BeanPropertyMeta cp = xmlMeta.getContentProperty(); 493 XmlFormat cpf = xmlMeta.getContentFormat(); 494 boolean trim = cp == null || ! cpf.isOneOf(MIXED_PWS, TEXT_PWS); 495 ClassMeta<?> cpcm = (cp == null ? object() : cp.getClassMeta()); 496 StringBuilder sb = null; 497 BeanRegistry breg = cp == null ? null : cp.getBeanRegistry(); 498 LinkedList<Object> l = null; 499 500 int depth = 0; 501 do { 502 int event = r.next(); 503 String currAttr; 504 // We only care about text in MIXED mode. 505 // Ignore if in ELEMENTS mode. 506 if (event == CHARACTERS) { 507 if (cp != null && cpf.isOneOf(MIXED, MIXED_PWS)) { 508 if (cpcm.isCollectionOrArray()) { 509 if (l == null) 510 l = new LinkedList<>(); 511 l.add(getText(r, false)); 512 } else { 513 cp.set(m, null, getText(r, trim)); 514 } 515 } else if (cpf != ELEMENTS) { 516 String s = getText(r, trim); 517 if (s != null) { 518 if (sb == null) 519 sb = getStringBuilder(); 520 sb.append(s); 521 } 522 } else { 523 // Do nothing...we're in ELEMENTS mode. 524 } 525 } else if (event == START_ELEMENT) { 526 if (cp != null && cpf.isOneOf(TEXT, TEXT_PWS)) { 527 String s = parseText(r); 528 if (s != null) { 529 if (sb == null) 530 sb = getStringBuilder(); 531 sb.append(s); 532 } 533 depth--; 534 } else if (cpf == XMLTEXT) { 535 if (sb == null) 536 sb = getStringBuilder(); 537 sb.append(getElementAsString(r)); 538 depth++; 539 } else if (cp != null && cpf.isOneOf(MIXED, MIXED_PWS)) { 540 if (isWhitespaceElement(r) && (breg == null || ! breg.hasName(r.getLocalName()))) { 541 if (cpcm.isCollectionOrArray()) { 542 if (l == null) 543 l = new LinkedList<>(); 544 l.add(parseWhitespaceElement(r)); 545 } else { 546 cp.set(m, null, parseWhitespaceElement(r)); 547 } 548 } else { 549 if (cpcm.isCollectionOrArray()) { 550 if (l == null) 551 l = new LinkedList<>(); 552 l.add(parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp)); 553 } else { 554 cp.set(m, null, parseAnything(cpcm, cp.getName(), r, m.getBean(false), false, cp)); 555 } 556 } 557 } else if (cp != null && cpf == ELEMENTS) { 558 cp.add(m, null, parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp)); 559 } else { 560 currAttr = getElementName(r); 561 BeanPropertyMeta pMeta = xmlMeta.getPropertyMeta(currAttr); 562 if (pMeta == null) { 563 onUnknownProperty(currAttr, m); 564 skipCurrentTag(r); 565 } else { 566 setCurrentProperty(pMeta); 567 XmlFormat xf = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat(); 568 if (xf == COLLAPSED) { 569 ClassMeta<?> et = pMeta.getClassMeta().getElementType(); 570 Object value = parseAnything(et, currAttr, r, m.getBean(false), false, pMeta); 571 setName(et, value, currAttr); 572 pMeta.add(m, currAttr, value); 573 } else if (xf == ATTR) { 574 pMeta.set(m, currAttr, getAttributeValue(r, 0)); 575 r.nextTag(); 576 } else { 577 ClassMeta<?> cm = pMeta.getClassMeta(); 578 Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, pMeta); 579 setName(cm, value, currAttr); 580 pMeta.set(m, currAttr, value); 581 } 582 setCurrentProperty(null); 583 } 584 } 585 } else if (event == END_ELEMENT) { 586 if (depth > 0) { 587 if (cpf == XMLTEXT) { 588 if (sb == null) 589 sb = getStringBuilder(); 590 sb.append(getElementAsString(r)); 591 } 592 else 593 throw new ParseException("End element found where one was not expected. {0}", XmlUtils.toReadableEvent(r)); 594 } 595 depth--; 596 } else if (event == COMMENT) { 597 // Ignore comments. 598 } else { 599 throw new ParseException("Unexpected event type: {0}", XmlUtils.toReadableEvent(r)); 600 } 601 } while (depth >= 0); 602 603 if (sb != null && cp != null) 604 cp.set(m, null, sb.toString()); 605 else if (l != null && cp != null) 606 cp.set(m, null, XmlUtils.collapseTextNodes(l)); 607 608 returnStringBuilder(sb); 609 return m; 610 } 611 612 private static void skipCurrentTag(XmlReader r) throws XMLStreamException { 613 int depth = 1; 614 do { 615 int event = r.next(); 616 if (event == START_ELEMENT) 617 depth++; 618 else if (event == END_ELEMENT) 619 depth--; 620 } while (depth > 0); 621 } 622 623 private Object getUnknown(XmlReader r) throws IOException, ParseException, ExecutableException, XMLStreamException { 624 if (r.getEventType() != START_ELEMENT) { 625 throw new ParseException(this, "Parser must be on START_ELEMENT to read next text."); 626 } 627 ObjectMap m = null; 628 629 // If this element has attributes, then it's always an ObjectMap. 630 if (r.getAttributeCount() > 0) { 631 m = new ObjectMap(this); 632 for (int i = 0; i < r.getAttributeCount(); i++) { 633 String key = getAttributeName(r, i); 634 String val = r.getAttributeValue(i); 635 if (! key.equals(getBeanTypePropertyName(null))) 636 m.put(key, val); 637 } 638 } 639 int eventType = r.next(); 640 StringBuilder sb = getStringBuilder(); 641 while (eventType != END_ELEMENT) { 642 if (eventType == CHARACTERS || eventType == CDATA || eventType == SPACE || eventType == ENTITY_REFERENCE) { 643 sb.append(r.getText()); 644 } else if (eventType == PROCESSING_INSTRUCTION || eventType == COMMENT) { 645 // skipping 646 } else if (eventType == END_DOCUMENT) { 647 throw new ParseException(this, "Unexpected end of document when reading element text content"); 648 } else if (eventType == START_ELEMENT) { 649 // Oops...this has an element in it. 650 // Parse it as a map. 651 if (m == null) 652 m = new ObjectMap(this); 653 int depth = 0; 654 do { 655 int event = (eventType == -1 ? r.nextTag() : eventType); 656 String currAttr; 657 if (event == START_ELEMENT) { 658 depth++; 659 currAttr = getElementName(r); 660 String key = convertAttrToType(null, currAttr, string()); 661 Object value = parseAnything(object(), currAttr, r, null, false, null); 662 if (m.containsKey(key)) { 663 Object o = m.get(key); 664 if (o instanceof ObjectList) 665 ((ObjectList)o).add(value); 666 else 667 m.put(key, new ObjectList(o, value).setBeanSession(this)); 668 } else { 669 m.put(key, value); 670 } 671 672 } else if (event == END_ELEMENT) { 673 depth--; 674 break; 675 } 676 eventType = -1; 677 } while (depth > 0); 678 break; 679 } else { 680 throw new ParseException(this, "Unexpected event type ''{0}''", eventType); 681 } 682 eventType = r.next(); 683 } 684 String s = sb.toString().trim(); 685 returnStringBuilder(sb); 686 s = decodeString(s); 687 if (m != null) { 688 if (! s.isEmpty()) 689 m.put("contents", s); 690 return m; 691 } 692 return s; 693 } 694 695 //----------------------------------------------------------------------------------------------------------------- 696 // Properties 697 //----------------------------------------------------------------------------------------------------------------- 698 699 /** 700 * Configuration property: XML event allocator. 701 * 702 * @see XmlParser#XML_eventAllocator 703 * @return 704 * The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one. 705 */ 706 protected final XMLEventAllocator getEventAllocator() { 707 return ctx.getEventAllocator(); 708 } 709 710 /** 711 * Configuration property: Preserve root element during generalized parsing. 712 * 713 * @see XmlParser#XML_preserveRootElement 714 * @return 715 * <jk>true</jk> if when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key 716 * is the root element name. 717 */ 718 protected final boolean isPreserveRootElement() { 719 return ctx.isPreserveRootElement(); 720 } 721 722 /** 723 * Configuration property: XML reporter. 724 * 725 * @see XmlParser#XML_reporter 726 * @return 727 * The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one. 728 */ 729 protected final XMLReporter getReporter() { 730 return ctx.getReporter(); 731 } 732 733 /** 734 * Configuration property: XML resolver. 735 * 736 * @see XmlParser#XML_resolver 737 * @return 738 * The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one. 739 */ 740 protected final XMLResolver getResolver() { 741 return ctx.getResolver(); 742 } 743 744 /** 745 * Configuration property: Enable validation. 746 * 747 * @see XmlParser#XML_validating 748 * @return 749 * <jk>true</jk> if XML document will be validated. 750 */ 751 protected final boolean isValidating() { 752 return ctx.isValidating(); 753 } 754 755 //----------------------------------------------------------------------------------------------------------------- 756 // Other methods 757 //----------------------------------------------------------------------------------------------------------------- 758 759 @Override /* Session */ 760 public ObjectMap toMap() { 761 return super.toMap() 762 .append("XmlParserSession", new DefaultFilteringObjectMap() 763 ); 764 } 765}