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 }