1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.org.jvnet.staxex.util; 27 28 import org.w3c.dom.Attr; 29 import org.w3c.dom.Element; 30 import org.w3c.dom.NamedNodeMap; 31 import org.w3c.dom.Node; 32 import static org.w3c.dom.Node.*; 33 import org.w3c.dom.ProcessingInstruction; 34 import org.w3c.dom.Text; 35 36 import javax.xml.namespace.NamespaceContext; 37 import javax.xml.namespace.QName; 38 import javax.xml.stream.Location; 39 import javax.xml.stream.XMLStreamException; 40 import javax.xml.stream.XMLStreamReader; 41 import java.util.Collections; 42 import java.util.Iterator; 43 44 /** 45 * Create an {@link XMLStreamReader} on top of a DOM tree. 46 * 47 * <p> 48 * Since various libraries as well as users often create "incorrect" DOM node, 49 * this class spends a lot of efforts making sure that broken DOM trees are 50 * nevertheless interpreted correctly. 51 * 52 * <p> 53 * For example, if a DOM level 54 * 1 tree is passed, each method will attempt to return the correct value 55 * by using {@link Node#getNodeName()}. 56 * 57 * <p> 58 * Similarly, if DOM is missing explicit namespace declarations, 59 * this class attempts to emulate necessary declarations. 60 * 61 * 62 * @author Santiago.PericasGeertsen@sun.com 63 * @author Kohsuke Kawaguchi 64 */ 65 public class DOMStreamReader implements XMLStreamReader, NamespaceContext { 66 67 /** 68 * Current DOM node being traversed. 69 */ 70 protected Node _current; 71 72 /** 73 * Starting node of the subtree being traversed. 74 */ 75 private Node _start; 76 77 /** 78 * Named mapping for attributes and NS decls for the current node. 79 */ 80 private NamedNodeMap _namedNodeMap; 81 82 /** 83 * If the reader points at {@link #CHARACTERS the text node}, 84 * its whole value. 85 * 86 * <p> 87 * This is simply a cache of {@link Text#getWholeText()} of {@link #_current}, 88 * but when a large binary data sent as base64 text, this could get very much 89 * non-trivial. 90 */ 91 protected String wholeText; 92 93 /** 94 * List of attributes extracted from <code>_namedNodeMap</code>. 95 */ 96 private final FinalArrayList<Attr> _currentAttributes = new FinalArrayList<Attr>(); 97 98 /** 99 * {@link Scope} buffer. 100 */ 101 protected Scope[] scopes = new Scope[8]; 102 103 /** 104 * Depth of the current element. The first element gets depth==0. 105 * Also used as the index to {@link #scopes}. 106 */ 107 protected int depth = 0; 108 109 /** 110 * State of this reader. Any of the valid states defined in StAX' 111 * XMLStreamConstants class. 112 */ 113 protected int _state; 114 115 /** 116 * Namespace declarations on one element. 117 * 118 * Instances are reused. 119 */ 120 protected static final class Scope { 121 /** 122 * Scope for the parent element. 123 */ 124 final Scope parent; 125 126 /** 127 * List of namespace declarations extracted from <code>_namedNodeMap</code> 128 */ 129 final FinalArrayList<Attr> currentNamespaces = new FinalArrayList<Attr>(); 130 131 /** 132 * Additional namespace declarations obtained as a result of "fixing" DOM tree, 133 * which were not part of the original DOM tree. 134 * 135 * One entry occupies two spaces (prefix followed by URI.) 136 */ 137 final FinalArrayList<String> additionalNamespaces = new FinalArrayList<String>(); 138 139 Scope(Scope parent) { 140 this.parent = parent; 141 } 142 143 void reset() { 144 currentNamespaces.clear(); 145 additionalNamespaces.clear(); 146 } 147 148 int getNamespaceCount() { 149 return currentNamespaces.size()+additionalNamespaces.size()/2; 150 } 151 152 String getNamespacePrefix(int index) { 153 int sz = currentNamespaces.size(); 154 if(index< sz) { 155 Attr attr = currentNamespaces.get(index); 156 String result = attr.getLocalName(); 157 if (result == null) { 158 result = QName.valueOf(attr.getNodeName()).getLocalPart(); 159 } 160 return result.equals("xmlns") ? null : result; 161 } else { 162 return additionalNamespaces.get((index-sz)*2); 163 } 164 } 165 166 String getNamespaceURI(int index) { 167 int sz = currentNamespaces.size(); 168 if(index< sz) { 169 return currentNamespaces.get(index).getValue(); 170 } else { 171 return additionalNamespaces.get((index-sz)*2+1); 172 } 173 } 174 175 /** 176 * Returns the prefix bound to the given URI, or null. 177 * This method recurses to the parent. 178 */ 179 String getPrefix(String nsUri) { 180 for( Scope sp=this; sp!=null; sp=sp.parent ) { 181 for( int i=sp.currentNamespaces.size()-1; i>=0; i--) { 182 String result = getPrefixForAttr(sp.currentNamespaces.get(i),nsUri); 183 if(result!=null) 184 return result; 185 } 186 for( int i=sp.additionalNamespaces.size()-2; i>=0; i-=2 ) 187 if(sp.additionalNamespaces.get(i+1).equals(nsUri)) 188 return sp.additionalNamespaces.get(i); 189 } 190 return null; 191 } 192 193 /** 194 * Returns the namespace URI bound by the given prefix. 195 * 196 * @param prefix 197 * Prefix to look up. 198 */ 199 String getNamespaceURI(String prefix) { 200 String nsDeclName = prefix.length()==0 ? "xmlns" : "xmlns:"+prefix; 201 202 for( Scope sp=this; sp!=null; sp=sp.parent ) { 203 for( int i=sp.currentNamespaces.size()-1; i>=0; i--) { 204 Attr a = sp.currentNamespaces.get(i); 205 if(a.getNodeName().equals(nsDeclName)) 206 return a.getValue(); 207 } 208 for( int i=sp.additionalNamespaces.size()-2; i>=0; i-=2 ) 209 if(sp.additionalNamespaces.get(i).equals(prefix)) 210 return sp.additionalNamespaces.get(i+1); 211 } 212 return null; 213 } 214 } 215 216 217 public DOMStreamReader() { 218 } 219 220 public DOMStreamReader(Node node) { 221 setCurrentNode(node); 222 } 223 224 public void setCurrentNode(Node node) { 225 scopes[0] = new Scope(null); 226 depth=0; 227 228 _start = _current = node; 229 _state = START_DOCUMENT; 230 // verifyDOMIntegrity(node); 231 // displayDOM(node, System.out); 232 } 233 234 public void close() throws XMLStreamException { 235 } 236 237 /** 238 * Called when the current node is {@link Element} to look at attribute list 239 * (which contains both ns decl and attributes in DOM) and split them 240 * to attributes-proper and namespace decls. 241 */ 242 protected void splitAttributes() { 243 // Clear attribute and namespace lists 244 _currentAttributes.clear(); 245 246 Scope scope = allocateScope(); 247 248 _namedNodeMap = _current.getAttributes(); 249 if (_namedNodeMap != null) { 250 final int n = _namedNodeMap.getLength(); 251 for (int i = 0; i < n; i++) { 252 final Attr attr = (Attr) _namedNodeMap.item(i); 253 final String attrName = attr.getNodeName(); 254 if (attrName.startsWith("xmlns:") || attrName.equals("xmlns")) { // NS decl? 255 scope.currentNamespaces.add(attr); 256 } 257 else { 258 _currentAttributes.add(attr); 259 } 260 } 261 } 262 263 // verify that all the namespaces used in element and attributes are indeed available 264 ensureNs(_current); 265 for( int i=_currentAttributes.size()-1; i>=0; i-- ) { 266 Attr a = _currentAttributes.get(i); 267 if(fixNull(a.getNamespaceURI()).length()>0) 268 ensureNs(a); // no need to declare "" for attributes in the default namespace 269 } 270 } 271 272 /** 273 * Sub-routine of {@link #splitAttributes()}. 274 * 275 * <p> 276 * Makes sure that the namespace URI/prefix used in the given node is available, 277 * and if not, declare it on the current scope to "fix" it. 278 * 279 * It's often common to create DOM trees without putting namespace declarations, 280 * and this makes sure that such DOM tree will be properly marshalled. 281 */ 282 private void ensureNs(Node n) { 283 String prefix = fixNull(n.getPrefix()); 284 String uri = fixNull(n.getNamespaceURI()); 285 286 Scope scope = scopes[depth]; 287 288 String currentUri = scope.getNamespaceURI(prefix); 289 290 if(prefix.length()==0) { 291 currentUri = fixNull(currentUri); 292 if(currentUri.equals(uri)) 293 return; // declared correctly 294 } else { 295 if(currentUri!=null && currentUri.equals(uri)) 296 return; // declared correctly 297 } 298 299 if(prefix.equals("xml") || prefix.equals("xmlns")) 300 return; // implicitly declared namespaces 301 302 // needs to be declared 303 scope.additionalNamespaces.add(prefix); 304 scope.additionalNamespaces.add(uri); 305 } 306 307 /** 308 * Allocate new {@link Scope} for {@link #splitAttributes()}. 309 */ 310 private Scope allocateScope() { 311 if(scopes.length==++depth) { 312 Scope[] newBuf = new Scope[scopes.length*2]; 313 System.arraycopy(scopes,0,newBuf,0,scopes.length); 314 scopes = newBuf; 315 } 316 Scope scope = scopes[depth]; 317 if(scope==null) { 318 scope = scopes[depth] = new Scope(scopes[depth-1]); 319 } else { 320 scope.reset(); 321 } 322 return scope; 323 } 324 325 public int getAttributeCount() { 326 if (_state == START_ELEMENT) 327 return _currentAttributes.size(); 328 throw new IllegalStateException("DOMStreamReader: getAttributeCount() called in illegal state"); 329 } 330 331 /** 332 * Return an attribute's local name. Handle the case of DOM level 1 nodes. 333 */ 334 public String getAttributeLocalName(int index) { 335 if (_state == START_ELEMENT) { 336 String localName = _currentAttributes.get(index).getLocalName(); 337 return (localName != null) ? localName : 338 QName.valueOf(_currentAttributes.get(index).getNodeName()).getLocalPart(); 339 } 340 throw new IllegalStateException("DOMStreamReader: getAttributeLocalName() called in illegal state"); 341 } 342 343 /** 344 * Return an attribute's qname. Handle the case of DOM level 1 nodes. 345 */ 346 public QName getAttributeName(int index) { 347 if (_state == START_ELEMENT) { 348 Node attr = _currentAttributes.get(index); 349 String localName = attr.getLocalName(); 350 if (localName != null) { 351 String prefix = attr.getPrefix(); 352 String uri = attr.getNamespaceURI(); 353 return new QName(fixNull(uri), localName, fixNull(prefix)); 354 } 355 else { 356 return QName.valueOf(attr.getNodeName()); 357 } 358 } 359 throw new IllegalStateException("DOMStreamReader: getAttributeName() called in illegal state"); 360 } 361 362 public String getAttributeNamespace(int index) { 363 if (_state == START_ELEMENT) { 364 String uri = _currentAttributes.get(index).getNamespaceURI(); 365 return fixNull(uri); 366 } 367 throw new IllegalStateException("DOMStreamReader: getAttributeNamespace() called in illegal state"); 368 } 369 370 public String getAttributePrefix(int index) { 371 if (_state == START_ELEMENT) { 372 String prefix = _currentAttributes.get(index).getPrefix(); 373 return fixNull(prefix); 374 } 375 throw new IllegalStateException("DOMStreamReader: getAttributePrefix() called in illegal state"); 376 } 377 378 public String getAttributeType(int index) { 379 if (_state == START_ELEMENT) { 380 return "CDATA"; 381 } 382 throw new IllegalStateException("DOMStreamReader: getAttributeType() called in illegal state"); 383 } 384 385 public String getAttributeValue(int index) { 386 if (_state == START_ELEMENT) { 387 return _currentAttributes.get(index).getNodeValue(); 388 } 389 throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state"); 390 } 391 392 public String getAttributeValue(String namespaceURI, String localName) { 393 if (_state == START_ELEMENT) { 394 if (_namedNodeMap != null) { 395 Node attr = _namedNodeMap.getNamedItemNS(namespaceURI, localName); 396 return attr != null ? attr.getNodeValue() : null; 397 } 398 return null; 399 } 400 throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state"); 401 } 402 403 public String getCharacterEncodingScheme() { 404 return null; 405 } 406 407 public String getElementText() throws javax.xml.stream.XMLStreamException { 408 throw new RuntimeException("DOMStreamReader: getElementText() not implemented"); 409 } 410 411 public String getEncoding() { 412 return null; 413 } 414 415 public int getEventType() { 416 return _state; 417 } 418 419 /** 420 * Return an element's local name. Handle the case of DOM level 1 nodes. 421 */ 422 public String getLocalName() { 423 if (_state == START_ELEMENT || _state == END_ELEMENT) { 424 String localName = _current.getLocalName(); 425 return localName != null ? localName : 426 QName.valueOf(_current.getNodeName()).getLocalPart(); 427 } 428 else if (_state == ENTITY_REFERENCE) { 429 return _current.getNodeName(); 430 } 431 throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state"); 432 } 433 434 public Location getLocation() { 435 return DummyLocation.INSTANCE; 436 } 437 438 /** 439 * Return an element's qname. Handle the case of DOM level 1 nodes. 440 */ 441 public javax.xml.namespace.QName getName() { 442 if (_state == START_ELEMENT || _state == END_ELEMENT) { 443 String localName = _current.getLocalName(); 444 if (localName != null) { 445 String prefix = _current.getPrefix(); 446 String uri = _current.getNamespaceURI(); 447 return new QName(fixNull(uri), localName, fixNull(prefix)); 448 } 449 else { 450 return QName.valueOf(_current.getNodeName()); 451 } 452 } 453 throw new IllegalStateException("DOMStreamReader: getName() called in illegal state"); 454 } 455 456 public NamespaceContext getNamespaceContext() { 457 return this; 458 } 459 460 /** 461 * Verifies the current state to see if we can return the scope, and do so 462 * if appropriate. 463 * 464 * Used to implement a bunch of StAX API methods that have the same usage restriction. 465 */ 466 private Scope getCheckedScope() { 467 if (_state == START_ELEMENT || _state == END_ELEMENT) { 468 return scopes[depth]; 469 } 470 throw new IllegalStateException("DOMStreamReader: neither on START_ELEMENT nor END_ELEMENT"); 471 } 472 473 public int getNamespaceCount() { 474 return getCheckedScope().getNamespaceCount(); 475 } 476 477 public String getNamespacePrefix(int index) { 478 return getCheckedScope().getNamespacePrefix(index); 479 } 480 481 public String getNamespaceURI(int index) { 482 return getCheckedScope().getNamespaceURI(index); 483 } 484 485 public String getNamespaceURI() { 486 if (_state == START_ELEMENT || _state == END_ELEMENT) { 487 String uri = _current.getNamespaceURI(); 488 return fixNull(uri); 489 } 490 return null; 491 } 492 493 /** 494 * This method is not particularly fast, but shouldn't be called very 495 * often. If we start to use it more, we should keep track of the 496 * NS declarations using a NamespaceContext implementation instead. 497 */ 498 public String getNamespaceURI(String prefix) { 499 if (prefix == null) { 500 throw new IllegalArgumentException("DOMStreamReader: getNamespaceURI(String) call with a null prefix"); 501 } 502 else if (prefix.equals("xml")) { 503 return "http://www.w3.org/XML/1998/namespace"; 504 } 505 else if (prefix.equals("xmlns")) { 506 return "http://www.w3.org/2000/xmlns/"; 507 } 508 509 // check scopes 510 String nsUri = scopes[depth].getNamespaceURI(prefix); 511 if(nsUri!=null) return nsUri; 512 513 // then ancestors above start node 514 Node node = findRootElement(); 515 String nsDeclName = prefix.length()==0 ? "xmlns" : "xmlns:"+prefix; 516 while (node.getNodeType() != DOCUMENT_NODE) { 517 // Is ns declaration on this element? 518 NamedNodeMap namedNodeMap = node.getAttributes(); 519 Attr attr = (Attr) namedNodeMap.getNamedItem(nsDeclName); 520 if (attr != null) 521 return attr.getValue(); 522 node = node.getParentNode(); 523 } 524 return null; 525 } 526 527 public String getPrefix(String nsUri) { 528 if (nsUri == null) { 529 throw new IllegalArgumentException("DOMStreamReader: getPrefix(String) call with a null namespace URI"); 530 } 531 else if (nsUri.equals("http://www.w3.org/XML/1998/namespace")) { 532 return "xml"; 533 } 534 else if (nsUri.equals("http://www.w3.org/2000/xmlns/")) { 535 return "xmlns"; 536 } 537 538 // check scopes 539 String prefix = scopes[depth].getPrefix(nsUri); 540 if(prefix!=null) return prefix; 541 542 // then ancestors above start node 543 Node node = findRootElement(); 544 545 while (node.getNodeType() != DOCUMENT_NODE) { 546 // Is ns declaration on this element? 547 NamedNodeMap namedNodeMap = node.getAttributes(); 548 for( int i=namedNodeMap.getLength()-1; i>=0; i-- ) { 549 Attr attr = (Attr)namedNodeMap.item(i); 550 prefix = getPrefixForAttr(attr,nsUri); 551 if(prefix!=null) 552 return prefix; 553 } 554 node = node.getParentNode(); 555 } 556 return null; 557 } 558 559 /** 560 * Finds the root element node of the traversal. 561 */ 562 private Node findRootElement() { 563 int type; 564 565 Node node = _start; 566 while ((type = node.getNodeType()) != DOCUMENT_NODE 567 && type != ELEMENT_NODE) { 568 node = node.getParentNode(); 569 } 570 return node; 571 } 572 573 /** 574 * If the given attribute is a namespace declaration for the given namespace URI, 575 * return its prefix. Otherwise null. 576 */ 577 private static String getPrefixForAttr(Attr attr, String nsUri) { 578 String attrName = attr.getNodeName(); 579 if (!attrName.startsWith("xmlns:") && !attrName.equals("xmlns")) 580 return null; // not nsdecl 581 582 if(attr.getValue().equals(nsUri)) { 583 if(attrName.equals("xmlns")) 584 return ""; 585 String localName = attr.getLocalName(); 586 return (localName != null) ? localName : 587 QName.valueOf(attrName).getLocalPart(); 588 } 589 590 return null; 591 } 592 593 public Iterator getPrefixes(String nsUri) { 594 // This is an incorrect implementation, 595 // but AFAIK it's not used in the JAX-WS runtime 596 String prefix = getPrefix(nsUri); 597 if(prefix==null) return Collections.emptyList().iterator(); 598 else return Collections.singletonList(prefix).iterator(); 599 } 600 601 public String getPIData() { 602 if (_state == PROCESSING_INSTRUCTION) { 603 return ((ProcessingInstruction) _current).getData(); 604 } 605 return null; 606 } 607 608 public String getPITarget() { 609 if (_state == PROCESSING_INSTRUCTION) { 610 return ((ProcessingInstruction) _current).getTarget(); 611 } 612 return null; 613 } 614 615 public String getPrefix() { 616 if (_state == START_ELEMENT || _state == END_ELEMENT) { 617 String prefix = _current.getPrefix(); 618 return fixNull(prefix); 619 } 620 return null; 621 } 622 623 public Object getProperty(String str) throws IllegalArgumentException { 624 return null; 625 } 626 627 public String getText() { 628 if (_state == CHARACTERS) 629 return wholeText; 630 if(_state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) 631 return _current.getNodeValue(); 632 throw new IllegalStateException("DOMStreamReader: getTextLength() called in illegal state"); 633 } 634 635 public char[] getTextCharacters() { 636 return getText().toCharArray(); 637 } 638 639 public int getTextCharacters(int sourceStart, char[] target, int targetStart, 640 int targetLength) throws XMLStreamException { 641 String text = getText(); 642 int copiedSize = Math.min(targetLength, text.length() - sourceStart); 643 text.getChars(sourceStart, sourceStart + copiedSize, target, targetStart); 644 645 return copiedSize; 646 } 647 648 public int getTextLength() { 649 return getText().length(); 650 } 651 652 public int getTextStart() { 653 if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) { 654 return 0; 655 } 656 throw new IllegalStateException("DOMStreamReader: getTextStart() called in illegal state"); 657 } 658 659 public String getVersion() { 660 return null; 661 } 662 663 public boolean hasName() { 664 return (_state == START_ELEMENT || _state == END_ELEMENT); 665 } 666 667 public boolean hasNext() throws javax.xml.stream.XMLStreamException { 668 return (_state != END_DOCUMENT); 669 } 670 671 public boolean hasText() { 672 if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) { 673 return getText().trim().length() > 0; 674 } 675 return false; 676 } 677 678 public boolean isAttributeSpecified(int param) { 679 return false; 680 } 681 682 public boolean isCharacters() { 683 return (_state == CHARACTERS); 684 } 685 686 public boolean isEndElement() { 687 return (_state == END_ELEMENT); 688 } 689 690 public boolean isStandalone() { 691 return true; 692 } 693 694 public boolean isStartElement() { 695 return (_state == START_ELEMENT); 696 } 697 698 public boolean isWhiteSpace() { 699 if (_state == CHARACTERS || _state == CDATA) 700 return getText().trim().length()==0; 701 return false; 702 } 703 704 private static int mapNodeTypeToState(int nodetype) { 705 switch (nodetype) { 706 case CDATA_SECTION_NODE: 707 return CDATA; 708 case COMMENT_NODE: 709 return COMMENT; 710 case ELEMENT_NODE: 711 return START_ELEMENT; 712 case ENTITY_NODE: 713 return ENTITY_DECLARATION; 714 case ENTITY_REFERENCE_NODE: 715 return ENTITY_REFERENCE; 716 case NOTATION_NODE: 717 return NOTATION_DECLARATION; 718 case PROCESSING_INSTRUCTION_NODE: 719 return PROCESSING_INSTRUCTION; 720 case TEXT_NODE: 721 return CHARACTERS; 722 default: 723 throw new RuntimeException("DOMStreamReader: Unexpected node type"); 724 } 725 } 726 727 public int next() throws XMLStreamException { 728 while(true) { 729 int r = _next(); 730 switch (r) { 731 case CHARACTERS: 732 // if we are currently at text node, make sure that this is a meaningful text node. 733 Node prev = _current.getPreviousSibling(); 734 if(prev!=null && prev.getNodeType()==Node.TEXT_NODE) 735 continue; // nope. this is just a continuation of previous text that should be invisible 736 737 Text t = (Text)_current; 738 wholeText = t.getWholeText(); 739 if(wholeText.length()==0) 740 continue; // nope. this is empty text. 741 return CHARACTERS; 742 case START_ELEMENT: 743 splitAttributes(); 744 return START_ELEMENT; 745 default: 746 return r; 747 } 748 } 749 } 750 751 protected int _next() throws XMLStreamException { 752 Node child; 753 754 switch (_state) { 755 case END_DOCUMENT: 756 throw new IllegalStateException("DOMStreamReader: Calling next() at END_DOCUMENT"); 757 case START_DOCUMENT: 758 // Don't skip document element if this is a fragment 759 if (_current.getNodeType() == ELEMENT_NODE) { 760 return (_state = START_ELEMENT); 761 } 762 763 child = _current.getFirstChild(); 764 if (child == null) { 765 return (_state = END_DOCUMENT); 766 } 767 else { 768 _current = child; 769 return (_state = mapNodeTypeToState(_current.getNodeType())); 770 } 771 case START_ELEMENT: 772 child = _current.getFirstChild(); 773 if (child == null) { 774 return (_state = END_ELEMENT); 775 } 776 else { 777 _current = child; 778 return (_state = mapNodeTypeToState(_current.getNodeType())); 779 } 780 case END_ELEMENT: 781 case CHARACTERS: 782 case COMMENT: 783 case CDATA: 784 case ENTITY_REFERENCE: 785 case PROCESSING_INSTRUCTION: 786 if (_state == END_ELEMENT) depth--; 787 // If at the end of this fragment, then terminate traversal 788 if (_current == _start) { 789 return (_state = END_DOCUMENT); 790 } 791 792 Node sibling = _current.getNextSibling(); 793 if (sibling == null) { 794 _current = _current.getParentNode(); 795 // getParentNode() returns null for fragments 796 _state = (_current == null || _current.getNodeType() == DOCUMENT_NODE) ? 797 END_DOCUMENT : END_ELEMENT; 798 return _state; 799 } 800 else { 801 _current = sibling; 802 return (_state = mapNodeTypeToState(_current.getNodeType())); 803 } 804 case DTD: 805 case ATTRIBUTE: 806 case NAMESPACE: 807 default: 808 throw new RuntimeException("DOMStreamReader: Unexpected internal state"); 809 } 810 } 811 812 public int nextTag() throws javax.xml.stream.XMLStreamException { 813 int eventType = next(); 814 while (eventType == CHARACTERS && isWhiteSpace() 815 || eventType == CDATA && isWhiteSpace() 816 || eventType == SPACE 817 || eventType == PROCESSING_INSTRUCTION 818 || eventType == COMMENT) 819 { 820 eventType = next(); 821 } 822 if (eventType != START_ELEMENT && eventType != END_ELEMENT) { 823 throw new XMLStreamException("DOMStreamReader: Expected start or end tag"); 824 } 825 return eventType; 826 } 827 828 public void require(int type, String namespaceURI, String localName) 829 throws javax.xml.stream.XMLStreamException 830 { 831 if (type != _state) { 832 throw new XMLStreamException("DOMStreamReader: Required event type not found"); 833 } 834 if (namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) { 835 throw new XMLStreamException("DOMStreamReader: Required namespaceURI not found"); 836 } 837 if (localName != null && !localName.equals(getLocalName())) { 838 throw new XMLStreamException("DOMStreamReader: Required localName not found"); 839 } 840 } 841 842 public boolean standaloneSet() { 843 return true; 844 } 845 846 847 848 // -- Debugging ------------------------------------------------------ 849 /* 850 private static void displayDOM(Node node, java.io.OutputStream ostream) { 851 try { 852 System.out.println("\n====\n"); 853 XmlUtil.newTransformer().transform( 854 new DOMSource(node), new StreamResult(ostream)); 855 System.out.println("\n====\n"); 856 } 857 catch (Exception e) { 858 e.printStackTrace(); 859 } 860 } 861 862 private static void verifyDOMIntegrity(Node node) { 863 switch (node.getNodeType()) { 864 case ELEMENT_NODE: 865 case ATTRIBUTE_NODE: 866 867 // DOM level 1? 868 if (node.getLocalName() == null) { 869 System.out.println("WARNING: DOM level 1 node found"); 870 System.out.println(" -> node.getNodeName() = " + node.getNodeName()); 871 System.out.println(" -> node.getNamespaceURI() = " + node.getNamespaceURI()); 872 System.out.println(" -> node.getLocalName() = " + node.getLocalName()); 873 System.out.println(" -> node.getPrefix() = " + node.getPrefix()); 874 } 875 876 if (node.getNodeType() == ATTRIBUTE_NODE) return; 877 878 NamedNodeMap attrs = node.getAttributes(); 879 for (int i = 0; i < attrs.getLength(); i++) { 880 verifyDOMIntegrity(attrs.item(i)); 881 } 882 case DOCUMENT_NODE: 883 NodeList children = node.getChildNodes(); 884 for (int i = 0; i < children.getLength(); i++) { 885 verifyDOMIntegrity(children.item(i)); 886 } 887 } 888 } 889 */ 890 891 private static String fixNull(String s) { 892 if(s==null) return ""; 893 else return s; 894 } 895 }