Skip to content

Package: DOMStreamReader$Scope

DOMStreamReader$Scope

nameinstructionbranchcomplexitylinemethod
DOMStreamReader.Scope(DOMStreamReader.Scope)
M: 16 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
getNamespaceCount()
M: 10 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getNamespacePrefix(int)
M: 41 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
getNamespaceURI(String)
M: 68 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%
getNamespaceURI(int)
M: 26 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getPrefix(String)
M: 58 C: 0
0%
M: 10 C: 0
0%
M: 6 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
reset()
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2022 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: */
321: @Override
322: public String getAttributeLocalName(int index) {
323: if (_state == START_ELEMENT) {
324: String localName = _currentAttributes.get(index).getLocalName();
325: return (localName != null) ? localName :
326: QName.valueOf(_currentAttributes.get(index).getNodeName()).getLocalPart();
327: }
328: throw new IllegalStateException("DOMStreamReader: getAttributeLocalName() called in illegal state");
329: }
330:
331: /**
332: * Return an attribute's qname. Handle the case of DOM level 1 nodes.
333: */
334: @Override
335: public QName getAttributeName(int index) {
336: if (_state == START_ELEMENT) {
337: Node attr = _currentAttributes.get(index);
338: String localName = attr.getLocalName();
339: if (localName != null) {
340: String prefix = attr.getPrefix();
341: String uri = attr.getNamespaceURI();
342: return new QName(fixNull(uri), localName, fixNull(prefix));
343: }
344: else {
345: return QName.valueOf(attr.getNodeName());
346: }
347: }
348: throw new IllegalStateException("DOMStreamReader: getAttributeName() called in illegal state");
349: }
350:
351: @Override
352: public String getAttributeNamespace(int index) {
353: if (_state == START_ELEMENT) {
354: String uri = _currentAttributes.get(index).getNamespaceURI();
355: return fixNull(uri);
356: }
357: throw new IllegalStateException("DOMStreamReader: getAttributeNamespace() called in illegal state");
358: }
359:
360: @Override
361: public String getAttributePrefix(int index) {
362: if (_state == START_ELEMENT) {
363: String prefix = _currentAttributes.get(index).getPrefix();
364: return fixNull(prefix);
365: }
366: throw new IllegalStateException("DOMStreamReader: getAttributePrefix() called in illegal state");
367: }
368:
369: @Override
370: public String getAttributeType(int index) {
371: if (_state == START_ELEMENT) {
372: return "CDATA";
373: }
374: throw new IllegalStateException("DOMStreamReader: getAttributeType() called in illegal state");
375: }
376:
377: @Override
378: public String getAttributeValue(int index) {
379: if (_state == START_ELEMENT) {
380: return _currentAttributes.get(index).getNodeValue();
381: }
382: throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
383: }
384:
385: @Override
386: public String getAttributeValue(String namespaceURI, String localName) {
387: if (_state == START_ELEMENT) {
388: if (_namedNodeMap != null) {
389: Node attr = _namedNodeMap.getNamedItemNS(namespaceURI, localName);
390: return attr != null ? attr.getNodeValue() : null;
391: }
392: return null;
393: }
394: throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
395: }
396:
397: @Override
398: public String getCharacterEncodingScheme() {
399: return null;
400: }
401:
402: @Override
403: public String getElementText() throws javax.xml.stream.XMLStreamException {
404: throw new RuntimeException("DOMStreamReader: getElementText() not implemented");
405: }
406:
407: @Override
408: public String getEncoding() {
409: return null;
410: }
411:
412: @Override
413: public int getEventType() {
414: return _state;
415: }
416:
417: /**
418: * Return an element's local name.Handle the case of DOM level 1 nodes.
419: */
420: @Override
421: public String getLocalName() {
422: if (_state == START_ELEMENT || _state == END_ELEMENT) {
423: String localName = _current.getLocalName();
424: return localName != null ? localName :
425: QName.valueOf(_current.getNodeName()).getLocalPart();
426: }
427: else if (_state == ENTITY_REFERENCE) {
428: return _current.getNodeName();
429: }
430: throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
431: }
432:
433: @Override
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: @Override
442: public javax.xml.namespace.QName getName() {
443: if (_state == START_ELEMENT || _state == END_ELEMENT) {
444: String localName = _current.getLocalName();
445: if (localName != null) {
446: String prefix = _current.getPrefix();
447: String uri = _current.getNamespaceURI();
448: return new QName(fixNull(uri), localName, fixNull(prefix));
449: }
450: else {
451: return QName.valueOf(_current.getNodeName());
452: }
453: }
454: throw new IllegalStateException("DOMStreamReader: getName() called in illegal state");
455: }
456:
457: @Override
458: public NamespaceContext getNamespaceContext() {
459: return this;
460: }
461:
462: /**
463: * Verifies the current state to see if we can return the scope, and do so
464: * if appropriate.
465: *
466: * Used to implement a bunch of StAX API methods that have the same usage restriction.
467: */
468: private Scope getCheckedScope() {
469: if (_state == START_ELEMENT || _state == END_ELEMENT) {
470: return scopes[depth];
471: }
472: throw new IllegalStateException("DOMStreamReader: neither on START_ELEMENT nor END_ELEMENT");
473: }
474:
475: @Override
476: public int getNamespaceCount() {
477: return getCheckedScope().getNamespaceCount();
478: }
479:
480: @Override
481: public String getNamespacePrefix(int index) {
482: return getCheckedScope().getNamespacePrefix(index);
483: }
484:
485: @Override
486: public String getNamespaceURI(int index) {
487: return getCheckedScope().getNamespaceURI(index);
488: }
489:
490: @Override
491: public String getNamespaceURI() {
492: if (_state == START_ELEMENT || _state == END_ELEMENT) {
493: String uri = _current.getNamespaceURI();
494: return fixNull(uri);
495: }
496: return null;
497: }
498:
499: /**
500: * This method is not particularly fast, but shouldn't be called very
501: * often.If we start to use it more, we should keep track of the
502: NS declarations using a NamespaceContext implementation instead.
503: */
504: @Override
505: public String getNamespaceURI(String prefix) {
506: if (prefix == null) {
507: throw new IllegalArgumentException("DOMStreamReader: getNamespaceURI(String) call with a null prefix");
508: }
509: else if (prefix.equals("xml")) {
510: return "http://www.w3.org/XML/1998/namespace";
511: }
512: else if (prefix.equals("xmlns")) {
513: return "http://www.w3.org/2000/xmlns/";
514: }
515:
516: // check scopes
517: String nsUri = scopes[depth].getNamespaceURI(prefix);
518: if(nsUri!=null) return nsUri;
519:
520: // then ancestors above start node
521: Node node = findRootElement();
522: String nsDeclName = prefix.length()==0 ? "xmlns" : "xmlns:"+prefix;
523: while (node.getNodeType() != DOCUMENT_NODE) {
524: // Is ns declaration on this element?
525: NamedNodeMap namedNodeMap = node.getAttributes();
526: Attr attr = (Attr) namedNodeMap.getNamedItem(nsDeclName);
527: if (attr != null)
528: return attr.getValue();
529: node = node.getParentNode();
530: }
531: return null;
532: }
533:
534: @Override
535: public String getPrefix(String nsUri) {
536: if (nsUri == null) {
537: throw new IllegalArgumentException("DOMStreamReader: getPrefix(String) call with a null namespace URI");
538: }
539: else if (nsUri.equals("http://www.w3.org/XML/1998/namespace")) {
540: return "xml";
541: }
542: else if (nsUri.equals("http://www.w3.org/2000/xmlns/")) {
543: return "xmlns";
544: }
545:
546: // check scopes
547: String prefix = scopes[depth].getPrefix(nsUri);
548: if(prefix!=null) return prefix;
549:
550: // then ancestors above start node
551: Node node = findRootElement();
552:
553: while (node.getNodeType() != DOCUMENT_NODE) {
554: // Is ns declaration on this element?
555: NamedNodeMap namedNodeMap = node.getAttributes();
556: for( int i=namedNodeMap.getLength()-1; i>=0; i-- ) {
557: Attr attr = (Attr)namedNodeMap.item(i);
558: prefix = getPrefixForAttr(attr,nsUri);
559: if(prefix!=null)
560: return prefix;
561: }
562: node = node.getParentNode();
563: }
564: return null;
565: }
566:
567: /**
568: * Finds the root element node of the traversal.
569: */
570: private Node findRootElement() {
571: int type;
572:
573: Node node = _start;
574: while ((type = node.getNodeType()) != DOCUMENT_NODE
575: && type != ELEMENT_NODE) {
576: node = node.getParentNode();
577: }
578: return node;
579: }
580:
581: /**
582: * If the given attribute is a namespace declaration for the given namespace URI,
583: * return its prefix. Otherwise null.
584: */
585: private static String getPrefixForAttr(Attr attr, String nsUri) {
586: String attrName = attr.getNodeName();
587: if (!attrName.startsWith("xmlns:") && !attrName.equals("xmlns"))
588: return null; // not nsdecl
589:
590: if(attr.getValue().equals(nsUri)) {
591: if(attrName.equals("xmlns"))
592: return "";
593: String localName = attr.getLocalName();
594: return (localName != null) ? localName :
595: QName.valueOf(attrName).getLocalPart();
596: }
597:
598: return null;
599: }
600:
601: @Override
602: public Iterator<String> getPrefixes(String nsUri) {
603: // This is an incorrect implementation,
604: // but AFAIK it's not used in the JAX-WS runtime
605: String prefix = getPrefix(nsUri);
606: if(prefix==null) return Collections.<String>emptyList().iterator();
607: else return Collections.singletonList(prefix).iterator();
608: }
609:
610: @Override
611: public String getPIData() {
612: if (_state == PROCESSING_INSTRUCTION) {
613: return ((ProcessingInstruction) _current).getData();
614: }
615: return null;
616: }
617:
618: @Override
619: public String getPITarget() {
620: if (_state == PROCESSING_INSTRUCTION) {
621: return ((ProcessingInstruction) _current).getTarget();
622: }
623: return null;
624: }
625:
626: @Override
627: public String getPrefix() {
628: if (_state == START_ELEMENT || _state == END_ELEMENT) {
629: String prefix = _current.getPrefix();
630: return fixNull(prefix);
631: }
632: return null;
633: }
634:
635: @Override
636: public Object getProperty(String str) throws IllegalArgumentException {
637: return null;
638: }
639:
640: @Override
641: public String getText() {
642: if (_state == CHARACTERS)
643: return wholeText;
644: if(_state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE)
645: return _current.getNodeValue();
646: throw new IllegalStateException("DOMStreamReader: getTextLength() called in illegal state");
647: }
648:
649: @Override
650: public char[] getTextCharacters() {
651: return getText().toCharArray();
652: }
653:
654: @Override
655: public int getTextCharacters(int sourceStart, char[] target, int targetStart,
656: int targetLength) throws XMLStreamException {
657: String text = getText();
658: int copiedSize = Math.min(targetLength, text.length() - sourceStart);
659: text.getChars(sourceStart, sourceStart + copiedSize, target, targetStart);
660:
661: return copiedSize;
662: }
663:
664: @Override
665: public int getTextLength() {
666: return getText().length();
667: }
668:
669: @Override
670: public int getTextStart() {
671: if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) {
672: return 0;
673: }
674: throw new IllegalStateException("DOMStreamReader: getTextStart() called in illegal state");
675: }
676:
677: @Override
678: public String getVersion() {
679: return null;
680: }
681:
682: @Override
683: public boolean hasName() {
684: return (_state == START_ELEMENT || _state == END_ELEMENT);
685: }
686:
687: @Override
688: public boolean hasNext() throws javax.xml.stream.XMLStreamException {
689: return (_state != END_DOCUMENT);
690: }
691:
692: @Override
693: public boolean hasText() {
694: if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) {
695: return getText().trim().length() > 0;
696: }
697: return false;
698: }
699:
700: @Override
701: public boolean isAttributeSpecified(int param) {
702: return false;
703: }
704:
705: @Override
706: public boolean isCharacters() {
707: return (_state == CHARACTERS);
708: }
709:
710: @Override
711: public boolean isEndElement() {
712: return (_state == END_ELEMENT);
713: }
714:
715: @Override
716: public boolean isStandalone() {
717: return true;
718: }
719:
720: @Override
721: public boolean isStartElement() {
722: return (_state == START_ELEMENT);
723: }
724:
725: @Override
726: public boolean isWhiteSpace() {
727: if (_state == CHARACTERS || _state == CDATA)
728: return getText().trim().length()==0;
729: return false;
730: }
731:
732: private static int mapNodeTypeToState(int nodetype) {
733: switch (nodetype) {
734: case CDATA_SECTION_NODE:
735: return CDATA;
736: case COMMENT_NODE:
737: return COMMENT;
738: case ELEMENT_NODE:
739: return START_ELEMENT;
740: case ENTITY_NODE:
741: return ENTITY_DECLARATION;
742: case ENTITY_REFERENCE_NODE:
743: return ENTITY_REFERENCE;
744: case NOTATION_NODE:
745: return NOTATION_DECLARATION;
746: case PROCESSING_INSTRUCTION_NODE:
747: return PROCESSING_INSTRUCTION;
748: case TEXT_NODE:
749: return CHARACTERS;
750: default:
751: throw new RuntimeException("DOMStreamReader: Unexpected node type");
752: }
753: }
754:
755: @Override
756: public int next() throws XMLStreamException {
757: while(true) {
758: int r = _next();
759: switch (r) {
760: case CHARACTERS:
761: // if we are currently at text node, make sure that this is a meaningful text node.
762: Node prev = _current.getPreviousSibling();
763: if(prev!=null && prev.getNodeType()==Node.TEXT_NODE)
764: continue; // nope. this is just a continuation of previous text that should be invisible
765:
766: Text t = (Text)_current;
767: wholeText = t.getWholeText();
768: if(wholeText.length()==0)
769: continue; // nope. this is empty text.
770: return CHARACTERS;
771: case START_ELEMENT:
772: splitAttributes();
773: return START_ELEMENT;
774: default:
775: return r;
776: }
777: }
778: }
779:
780: protected int _next() throws XMLStreamException {
781: Node child;
782:
783: switch (_state) {
784: case END_DOCUMENT:
785: throw new IllegalStateException("DOMStreamReader: Calling next() at END_DOCUMENT");
786: case START_DOCUMENT:
787: // Don't skip document element if this is a fragment
788: if (_current.getNodeType() == ELEMENT_NODE) {
789: return (_state = START_ELEMENT);
790: }
791:
792: child = _current.getFirstChild();
793: if (child == null) {
794: return (_state = END_DOCUMENT);
795: }
796: else {
797: _current = child;
798: return (_state = mapNodeTypeToState(_current.getNodeType()));
799: }
800: case START_ELEMENT:
801: child = _current.getFirstChild();
802: if (child == null) {
803: return (_state = END_ELEMENT);
804: }
805: else {
806: _current = child;
807: return (_state = mapNodeTypeToState(_current.getNodeType()));
808: }
809: case END_ELEMENT:
810: case CHARACTERS:
811: case COMMENT:
812: case CDATA:
813: case ENTITY_REFERENCE:
814: case PROCESSING_INSTRUCTION:
815: if (_state == END_ELEMENT) depth--;
816: // If at the end of this fragment, then terminate traversal
817: if (_current == _start) {
818: return (_state = END_DOCUMENT);
819: }
820:
821: Node sibling = _current.getNextSibling();
822: if (sibling == null) {
823: _current = _current.getParentNode();
824: // getParentNode() returns null for fragments
825: _state = (_current == null || _current.getNodeType() == DOCUMENT_NODE) ?
826: END_DOCUMENT : END_ELEMENT;
827: return _state;
828: }
829: else {
830: _current = sibling;
831: return (_state = mapNodeTypeToState(_current.getNodeType()));
832: }
833: case DTD:
834: case ATTRIBUTE:
835: case NAMESPACE:
836: default:
837: throw new RuntimeException("DOMStreamReader: Unexpected internal state");
838: }
839: }
840:
841: @Override
842: public int nextTag() throws javax.xml.stream.XMLStreamException {
843: int eventType = next();
844: while (eventType == CHARACTERS && isWhiteSpace()
845: || eventType == CDATA && isWhiteSpace()
846: || eventType == SPACE
847: || eventType == PROCESSING_INSTRUCTION
848: || eventType == COMMENT)
849: {
850: eventType = next();
851: }
852: if (eventType != START_ELEMENT && eventType != END_ELEMENT) {
853: throw new XMLStreamException("DOMStreamReader: Expected start or end tag");
854: }
855: return eventType;
856: }
857:
858: @Override
859: public void require(int type, String namespaceURI, String localName)
860: throws javax.xml.stream.XMLStreamException
861: {
862: if (type != _state) {
863: throw new XMLStreamException("DOMStreamReader: Required event type not found");
864: }
865: if (namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) {
866: throw new XMLStreamException("DOMStreamReader: Required namespaceURI not found");
867: }
868: if (localName != null && !localName.equals(getLocalName())) {
869: throw new XMLStreamException("DOMStreamReader: Required localName not found");
870: }
871: }
872:
873: @Override
874: public boolean standaloneSet() {
875: return true;
876: }
877:
878:
879:
880: // -- Debugging ------------------------------------------------------
881: /*
882: private static void displayDOM(Node node, java.io.OutputStream ostream) {
883: try {
884: System.out.println("\n====\n");
885: XmlUtil.newTransformer().transform(
886: new DOMSource(node), new StreamResult(ostream));
887: System.out.println("\n====\n");
888: }
889: catch (Exception e) {
890: e.printStackTrace();
891: }
892: }
893:
894: private static void verifyDOMIntegrity(Node node) {
895: switch (node.getNodeType()) {
896: case ELEMENT_NODE:
897: case ATTRIBUTE_NODE:
898:
899: // DOM level 1?
900: if (node.getLocalName() == null) {
901: System.out.println("WARNING: DOM level 1 node found");
902: System.out.println(" -> node.getNodeName() = " + node.getNodeName());
903: System.out.println(" -> node.getNamespaceURI() = " + node.getNamespaceURI());
904: System.out.println(" -> node.getLocalName() = " + node.getLocalName());
905: System.out.println(" -> node.getPrefix() = " + node.getPrefix());
906: }
907:
908: if (node.getNodeType() == ATTRIBUTE_NODE) return;
909:
910: NamedNodeMap attrs = node.getAttributes();
911: for (int i = 0; i < attrs.getLength(); i++) {
912: verifyDOMIntegrity(attrs.item(i));
913: }
914: case DOCUMENT_NODE:
915: NodeList children = node.getChildNodes();
916: for (int i = 0; i < children.getLength(); i++) {
917: verifyDOMIntegrity(children.item(i));
918: }
919: }
920: }
921: */
922:
923: private static String fixNull(String s) {
924: if(s==null) return "";
925: else return s;
926: }
927: }