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