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