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