001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.ws.streaming;
038:
039: import com.sun.istack.FinalArrayList;
040: import com.sun.istack.NotNull;
041: import com.sun.istack.XMLStreamException2;
042: import com.sun.xml.ws.util.xml.DummyLocation;
043: import com.sun.xml.ws.util.xml.XmlUtil;
044: import org.w3c.dom.Attr;
045: import org.w3c.dom.Element;
046: import org.w3c.dom.NamedNodeMap;
047: import org.w3c.dom.Node;
048: import static org.w3c.dom.Node.*;
049: import org.w3c.dom.NodeList;
050: import org.w3c.dom.ProcessingInstruction;
051: import org.w3c.dom.Text;
052:
053: import javax.xml.namespace.NamespaceContext;
054: import javax.xml.namespace.QName;
055: import javax.xml.stream.Location;
056: import javax.xml.stream.XMLStreamException;
057: import javax.xml.stream.XMLStreamReader;
058: import javax.xml.transform.dom.DOMSource;
059: import javax.xml.transform.stream.StreamResult;
060: import java.util.Collections;
061: import java.util.Iterator;
062:
063: /**
064: * Create an {@link XMLStreamReader} on top of a DOM tree.
065: *
066: * <p>
067: * Since various libraries as well as users often create "incorrect" DOM node,
068: * this class spends a lot of efforts making sure that broken DOM trees are
069: * nevertheless interpreted correctly.
070: *
071: * <p>
072: * For example, if a DOM level
073: * 1 tree is passed, each method will attempt to return the correct value
074: * by using {@link Node#getNodeName()}.
075: *
076: * <p>
077: * Similarly, if DOM is missing explicit namespace declarations,
078: * this class attempts to emulate necessary declarations.
079: *
080: *
081: * @author Santiago.PericasGeertsen@sun.com
082: * @author Kohsuke Kawaguchi
083: */
084: public final class DOMStreamReader implements XMLStreamReader,
085: NamespaceContext {
086:
087: /**
088: * Current DOM node being traversed.
089: */
090: private Node _current;
091:
092: /**
093: * Starting node of the subtree being traversed.
094: */
095: private Node _start;
096:
097: /**
098: * Named mapping for attributes and NS decls for the current node.
099: */
100: private NamedNodeMap _namedNodeMap;
101:
102: /**
103: * If the reader points at {@link #CHARACTERS the text node},
104: * its whole value.
105: *
106: * <p>
107: * This is simply a cache of {@link Text#getWholeText()} of {@link #_current},
108: * but when a large binary data sent as base64 text, this could get very much
109: * non-trivial.
110: */
111: private String wholeText;
112:
113: /**
114: * List of attributes extracted from <code>_namedNodeMap</code>.
115: */
116: private final FinalArrayList<Attr> _currentAttributes = new FinalArrayList<Attr>();
117:
118: /**
119: * {@link Scope} buffer.
120: */
121: private Scope[] scopes = new Scope[8];
122:
123: /**
124: * Depth of the current element. The first element gets depth==0.
125: * Also used as the index to {@link #scopes}.
126: */
127: private int depth = 0;
128:
129: /**
130: * Flag indicating if {@link #_namedNodeMap} is already split into
131: * {@link #_currentAttributes} and {@link Scope#currentNamespaces}.
132: */
133: boolean _needAttributesSplit;
134:
135: /**
136: * State of this reader. Any of the valid states defined in StAX'
137: * XMLStreamConstants class.
138: */
139: int _state;
140:
141: /**
142: * Namespace declarations on one element.
143: *
144: * Instances are reused.
145: */
146: private static final class Scope {
147: /**
148: * Scope for the parent element.
149: */
150: final Scope parent;
151:
152: /**
153: * List of namespace declarations extracted from <code>_namedNodeMap</code>
154: */
155: final FinalArrayList<Attr> currentNamespaces = new FinalArrayList<Attr>();
156:
157: /**
158: * Additional namespace declarations obtained as a result of "fixing" DOM tree,
159: * which were not part of the original DOM tree.
160: *
161: * One entry occupies two spaces (prefix followed by URI.)
162: */
163: final FinalArrayList<String> additionalNamespaces = new FinalArrayList<String>();
164:
165: Scope(Scope parent) {
166: this .parent = parent;
167: }
168:
169: void reset() {
170: currentNamespaces.clear();
171: additionalNamespaces.clear();
172: }
173:
174: int getNamespaceCount() {
175: return currentNamespaces.size()
176: + additionalNamespaces.size() / 2;
177: }
178:
179: String getNamespacePrefix(int index) {
180: int sz = currentNamespaces.size();
181: if (index < sz) {
182: Attr attr = currentNamespaces.get(index);
183: String result = attr.getLocalName();
184: if (result == null) {
185: result = QName.valueOf(attr.getNodeName())
186: .getLocalPart();
187: }
188: return result.equals("xmlns") ? null : result;
189: } else {
190: return additionalNamespaces.get((index - sz) * 2);
191: }
192: }
193:
194: String getNamespaceURI(int index) {
195: int sz = currentNamespaces.size();
196: if (index < sz) {
197: return currentNamespaces.get(index).getValue();
198: } else {
199: return additionalNamespaces.get((index - sz) * 2 + 1);
200: }
201: }
202:
203: /**
204: * Returns the prefix bound to the given URI, or null.
205: * This method recurses to the parent.
206: */
207: String getPrefix(String nsUri) {
208: for (Scope sp = this ; sp != null; sp = sp.parent) {
209: for (int i = sp.currentNamespaces.size() - 1; i >= 0; i--) {
210: String result = getPrefixForAttr(
211: sp.currentNamespaces.get(i), nsUri);
212: if (result != null)
213: return result;
214: }
215: for (int i = sp.additionalNamespaces.size() - 2; i >= 0; i -= 2)
216: if (sp.additionalNamespaces.get(i + 1)
217: .equals(nsUri))
218: return sp.additionalNamespaces.get(i);
219: }
220: return null;
221: }
222:
223: /**
224: * Returns the namespace URI bound by the given prefix.
225: *
226: * @param prefix
227: * Prefix to look up.
228: */
229: String getNamespaceURI(@NotNull
230: String prefix) {
231: String nsDeclName = prefix.length() == 0 ? "xmlns"
232: : "xmlns:" + prefix;
233:
234: for (Scope sp = this ; sp != null; sp = sp.parent) {
235: for (int i = sp.currentNamespaces.size() - 1; i >= 0; i--) {
236: Attr a = sp.currentNamespaces.get(i);
237: if (a.getNodeName().equals(nsDeclName))
238: return a.getValue();
239: }
240: for (int i = sp.additionalNamespaces.size() - 2; i >= 0; i -= 2)
241: if (sp.additionalNamespaces.get(i).equals(prefix))
242: return sp.additionalNamespaces.get(i + 1);
243: }
244: return null;
245: }
246: }
247:
248: public DOMStreamReader() {
249: }
250:
251: public DOMStreamReader(Node node) {
252: setCurrentNode(node);
253: }
254:
255: public void setCurrentNode(Node node) {
256: scopes[0] = new Scope(null);
257: depth = 0;
258:
259: _start = _current = node;
260: _state = START_DOCUMENT;
261: // verifyDOMIntegrity(node);
262: // displayDOM(node, System.out);
263: }
264:
265: public void close() throws XMLStreamException {
266: }
267:
268: /**
269: * Called when the current node is {@link Element} to look at attribute list
270: * (which contains both ns decl and attributes in DOM) and split them
271: * to attributes-proper and namespace decls.
272: */
273: private void splitAttributes() {
274: if (!_needAttributesSplit)
275: return;
276: _needAttributesSplit = false;
277:
278: // Clear attribute and namespace lists
279: _currentAttributes.clear();
280:
281: Scope scope = allocateScope();
282:
283: _namedNodeMap = _current.getAttributes();
284: if (_namedNodeMap != null) {
285: final int n = _namedNodeMap.getLength();
286: for (int i = 0; i < n; i++) {
287: final Attr attr = (Attr) _namedNodeMap.item(i);
288: final String attrName = attr.getNodeName();
289: if (attrName.startsWith("xmlns:")
290: || attrName.equals("xmlns")) { // NS decl?
291: scope.currentNamespaces.add(attr);
292: } else {
293: _currentAttributes.add(attr);
294: }
295: }
296: }
297:
298: // verify that all the namespaces used in element and attributes are indeed available
299: ensureNs(_current);
300: for (int i = _currentAttributes.size() - 1; i >= 0; i--) {
301: Attr a = _currentAttributes.get(i);
302: ensureNs(a);
303: }
304: }
305:
306: /**
307: * Sub-routine of {@link #splitAttributes()}.
308: *
309: * <p>
310: * Makes sure that the namespace URI/prefix used in the given node is available,
311: * and if not, declare it on the current scope to "fix" it.
312: *
313: * It's often common to create DOM trees without putting namespace declarations,
314: * and this makes sure that such DOM tree will be properly marshalled.
315: */
316: private void ensureNs(Node n) {
317: String prefix = fixNull(n.getPrefix());
318: String uri = fixNull(n.getNamespaceURI());
319:
320: Scope scope = scopes[depth];
321:
322: String currentUri = scope.getNamespaceURI(prefix);
323:
324: if (prefix.length() == 0) {
325: currentUri = fixNull(currentUri);
326: if (currentUri.equals(uri))
327: return; // declared correctly
328: } else {
329: if (currentUri != null && currentUri.equals(uri))
330: return; // declared correctly
331: }
332:
333: // needs to be declared
334:
335: scope.additionalNamespaces.add(prefix);
336: scope.additionalNamespaces.add(uri);
337: }
338:
339: /**
340: * Allocate new {@link Scope} for {@link #splitAttributes()}.
341: */
342: private Scope allocateScope() {
343: if (scopes.length == depth) {
344: Scope[] newBuf = new Scope[scopes.length * 2];
345: System.arraycopy(scopes, 0, newBuf, 0, scopes.length);
346: scopes = newBuf;
347: }
348: Scope scope = scopes[depth];
349: if (scope == null) {
350: scope = scopes[depth] = new Scope(scopes[depth - 1]);
351: } else {
352: scope.reset();
353: }
354: return scope;
355: }
356:
357: public int getAttributeCount() {
358: if (_state == START_ELEMENT) {
359: splitAttributes();
360: return _currentAttributes.size();
361: }
362: throw new IllegalStateException(
363: "DOMStreamReader: getAttributeCount() called in illegal state");
364: }
365:
366: /**
367: * Return an attribute's local name. Handle the case of DOM level 1 nodes.
368: */
369: public String getAttributeLocalName(int index) {
370: if (_state == START_ELEMENT) {
371: splitAttributes();
372:
373: String localName = _currentAttributes.get(index)
374: .getLocalName();
375: return (localName != null) ? localName : QName.valueOf(
376: _currentAttributes.get(index).getNodeName())
377: .getLocalPart();
378: }
379: throw new IllegalStateException(
380: "DOMStreamReader: getAttributeLocalName() called in illegal state");
381: }
382:
383: /**
384: * Return an attribute's qname. Handle the case of DOM level 1 nodes.
385: */
386: public QName getAttributeName(int index) {
387: if (_state == START_ELEMENT) {
388: splitAttributes();
389:
390: Node attr = _currentAttributes.get(index);
391: String localName = attr.getLocalName();
392: if (localName != null) {
393: String prefix = attr.getPrefix();
394: String uri = attr.getNamespaceURI();
395: return new QName(fixNull(uri), localName,
396: fixNull(prefix));
397: } else {
398: return QName.valueOf(attr.getNodeName());
399: }
400: }
401: throw new IllegalStateException(
402: "DOMStreamReader: getAttributeName() called in illegal state");
403: }
404:
405: public String getAttributeNamespace(int index) {
406: if (_state == START_ELEMENT) {
407: splitAttributes();
408: String uri = _currentAttributes.get(index)
409: .getNamespaceURI();
410: return fixNull(uri);
411: }
412: throw new IllegalStateException(
413: "DOMStreamReader: getAttributeNamespace() called in illegal state");
414: }
415:
416: public String getAttributePrefix(int index) {
417: if (_state == START_ELEMENT) {
418: splitAttributes();
419: String prefix = _currentAttributes.get(index).getPrefix();
420: return fixNull(prefix);
421: }
422: throw new IllegalStateException(
423: "DOMStreamReader: getAttributePrefix() called in illegal state");
424: }
425:
426: public String getAttributeType(int index) {
427: if (_state == START_ELEMENT) {
428: return "CDATA";
429: }
430: throw new IllegalStateException(
431: "DOMStreamReader: getAttributeType() called in illegal state");
432: }
433:
434: public String getAttributeValue(int index) {
435: if (_state == START_ELEMENT) {
436: splitAttributes();
437: return _currentAttributes.get(index).getNodeValue();
438: }
439: throw new IllegalStateException(
440: "DOMStreamReader: getAttributeValue() called in illegal state");
441: }
442:
443: public String getAttributeValue(String namespaceURI,
444: String localName) {
445: if (_state == START_ELEMENT) {
446: splitAttributes();
447: if (_namedNodeMap != null) {
448: Node attr = _namedNodeMap.getNamedItemNS(namespaceURI,
449: localName);
450: return attr != null ? attr.getNodeValue() : null;
451: }
452: return null;
453: }
454: throw new IllegalStateException(
455: "DOMStreamReader: getAttributeValue() called in illegal state");
456: }
457:
458: public String getCharacterEncodingScheme() {
459: return null;
460: }
461:
462: public String getElementText()
463: throws javax.xml.stream.XMLStreamException {
464: throw new RuntimeException(
465: "DOMStreamReader: getElementText() not implemented");
466: }
467:
468: public String getEncoding() {
469: return null;
470: }
471:
472: public int getEventType() {
473: return _state;
474: }
475:
476: /**
477: * Return an element's local name. Handle the case of DOM level 1 nodes.
478: */
479: public String getLocalName() {
480: if (_state == START_ELEMENT || _state == END_ELEMENT) {
481: String localName = _current.getLocalName();
482: return localName != null ? localName : QName.valueOf(
483: _current.getNodeName()).getLocalPart();
484: } else if (_state == ENTITY_REFERENCE) {
485: return _current.getNodeName();
486: }
487: throw new IllegalStateException(
488: "DOMStreamReader: getAttributeValue() called in illegal state");
489: }
490:
491: public Location getLocation() {
492: return DummyLocation.INSTANCE;
493: }
494:
495: /**
496: * Return an element's qname. Handle the case of DOM level 1 nodes.
497: */
498: public javax.xml.namespace.QName getName() {
499: if (_state == START_ELEMENT || _state == END_ELEMENT) {
500: String localName = _current.getLocalName();
501: if (localName != null) {
502: String prefix = _current.getPrefix();
503: String uri = _current.getNamespaceURI();
504: return new QName(fixNull(uri), localName,
505: fixNull(prefix));
506: } else {
507: return QName.valueOf(_current.getNodeName());
508: }
509: }
510: throw new IllegalStateException(
511: "DOMStreamReader: getName() called in illegal state");
512: }
513:
514: public NamespaceContext getNamespaceContext() {
515: return this ;
516: }
517:
518: /**
519: * Verifies the current state to see if we can return the scope, and do so
520: * if appropriate.
521: *
522: * Used to implement a bunch of StAX API methods that have the same usage restriction.
523: */
524: private Scope getCheckedScope() {
525: if (_state == START_ELEMENT || _state == END_ELEMENT) {
526: splitAttributes();
527: return scopes[depth];
528: }
529: throw new IllegalStateException(
530: "DOMStreamReader: neither on START_ELEMENT nor END_ELEMENT");
531: }
532:
533: public int getNamespaceCount() {
534: return getCheckedScope().getNamespaceCount();
535: }
536:
537: public String getNamespacePrefix(int index) {
538: return getCheckedScope().getNamespacePrefix(index);
539: }
540:
541: public String getNamespaceURI(int index) {
542: return getCheckedScope().getNamespaceURI(index);
543: }
544:
545: public String getNamespaceURI() {
546: if (_state == START_ELEMENT || _state == END_ELEMENT) {
547: String uri = _current.getNamespaceURI();
548: return fixNull(uri);
549: }
550: return null;
551: }
552:
553: /**
554: * This method is not particularly fast, but shouldn't be called very
555: * often. If we start to use it more, we should keep track of the
556: * NS declarations using a NamespaceContext implementation instead.
557: */
558: public String getNamespaceURI(String prefix) {
559: if (prefix == null) {
560: throw new IllegalArgumentException(
561: "DOMStreamReader: getNamespaceURI(String) call with a null prefix");
562: } else if (prefix.equals("xml")) {
563: return "http://www.w3.org/XML/1998/namespace";
564: } else if (prefix.equals("xmlns")) {
565: return "http://www.w3.org/2000/xmlns/";
566: }
567:
568: // check scopes
569: splitAttributes();
570: String nsUri = scopes[depth].getNamespaceURI(prefix);
571: if (nsUri != null)
572: return nsUri;
573:
574: // then ancestors above start node
575: Node node = findRootElement();
576: String nsDeclName = prefix.length() == 0 ? "xmlns" : "xmlns:"
577: + prefix;
578: while (node.getNodeType() != DOCUMENT_NODE) {
579: // Is ns declaration on this element?
580: NamedNodeMap namedNodeMap = node.getAttributes();
581: Attr attr = (Attr) namedNodeMap.getNamedItem(nsDeclName);
582: if (attr != null)
583: return attr.getValue();
584: node = node.getParentNode();
585: }
586: return null;
587: }
588:
589: public String getPrefix(String nsUri) {
590: if (nsUri == null) {
591: throw new IllegalArgumentException(
592: "DOMStreamReader: getPrefix(String) call with a null namespace URI");
593: } else if (nsUri.equals("http://www.w3.org/XML/1998/namespace")) {
594: return "xml";
595: } else if (nsUri.equals("http://www.w3.org/2000/xmlns/")) {
596: return "xmlns";
597: }
598:
599: // check scopes
600: splitAttributes();
601: String prefix = scopes[depth].getPrefix(nsUri);
602: if (prefix != null)
603: return prefix;
604:
605: // then ancestors above start node
606: Node node = findRootElement();
607:
608: while (node.getNodeType() != DOCUMENT_NODE) {
609: // Is ns declaration on this element?
610: NamedNodeMap namedNodeMap = node.getAttributes();
611: for (int i = namedNodeMap.getLength() - 1; i >= 0; i--) {
612: Attr attr = (Attr) namedNodeMap.item(i);
613: prefix = getPrefixForAttr(attr, nsUri);
614: if (prefix != null)
615: return prefix;
616: }
617: node = node.getParentNode();
618: }
619: return null;
620: }
621:
622: /**
623: * Finds the root element node of the traversal.
624: */
625: private Node findRootElement() {
626: int type;
627:
628: Node node = _start;
629: while ((type = node.getNodeType()) != DOCUMENT_NODE
630: && type != ELEMENT_NODE) {
631: node = node.getParentNode();
632: }
633: return node;
634: }
635:
636: /**
637: * If the given attribute is a namespace declaration for the given namespace URI,
638: * return its prefix. Otherwise null.
639: */
640: private static String getPrefixForAttr(Attr attr, String nsUri) {
641: String attrName = attr.getNodeName();
642: if (!attrName.startsWith("xmlns:") && !attrName.equals("xmlns"))
643: return null; // not nsdecl
644:
645: if (attr.getValue().equals(nsUri)) {
646: if (attrName.equals("xmlns"))
647: return "";
648: String localName = attr.getLocalName();
649: return (localName != null) ? localName : QName.valueOf(
650: attrName).getLocalPart();
651: }
652:
653: return null;
654: }
655:
656: public Iterator getPrefixes(String nsUri) {
657: // This is an incorrect implementation,
658: // but AFAIK it's not used in the JAX-WS runtime
659: String prefix = getPrefix(nsUri);
660: if (prefix == null)
661: return Collections.emptyList().iterator();
662: else
663: return Collections.singletonList(prefix).iterator();
664: }
665:
666: public String getPIData() {
667: if (_state == PROCESSING_INSTRUCTION) {
668: return ((ProcessingInstruction) _current).getData();
669: }
670: return null;
671: }
672:
673: public String getPITarget() {
674: if (_state == PROCESSING_INSTRUCTION) {
675: return ((ProcessingInstruction) _current).getTarget();
676: }
677: return null;
678: }
679:
680: public String getPrefix() {
681: if (_state == START_ELEMENT || _state == END_ELEMENT) {
682: String prefix = _current.getPrefix();
683: return fixNull(prefix);
684: }
685: return null;
686: }
687:
688: public Object getProperty(String str)
689: throws IllegalArgumentException {
690: return null;
691: }
692:
693: public String getText() {
694: if (_state == CHARACTERS)
695: return wholeText;
696: if (_state == CDATA || _state == COMMENT
697: || _state == ENTITY_REFERENCE)
698: return _current.getNodeValue();
699: throw new IllegalStateException(
700: "DOMStreamReader: getTextLength() called in illegal state");
701: }
702:
703: public char[] getTextCharacters() {
704: return getText().toCharArray();
705: }
706:
707: public int getTextCharacters(int sourceStart, char[] target,
708: int targetStart, int targetLength)
709: throws XMLStreamException {
710: String text = getText();
711: int copiedSize = Math.min(targetLength, text.length()
712: - sourceStart);
713: text.getChars(sourceStart, sourceStart + copiedSize, target,
714: targetStart);
715:
716: return copiedSize;
717: }
718:
719: public int getTextLength() {
720: return getText().length();
721: }
722:
723: public int getTextStart() {
724: if (_state == CHARACTERS || _state == CDATA
725: || _state == COMMENT || _state == ENTITY_REFERENCE) {
726: return 0;
727: }
728: throw new IllegalStateException(
729: "DOMStreamReader: getTextStart() called in illegal state");
730: }
731:
732: public String getVersion() {
733: return null;
734: }
735:
736: public boolean hasName() {
737: return (_state == START_ELEMENT || _state == END_ELEMENT);
738: }
739:
740: public boolean hasNext() throws javax.xml.stream.XMLStreamException {
741: return (_state != END_DOCUMENT);
742: }
743:
744: public boolean hasText() {
745: if (_state == CHARACTERS || _state == CDATA
746: || _state == COMMENT || _state == ENTITY_REFERENCE) {
747: return getText().trim().length() > 0;
748: }
749: return false;
750: }
751:
752: public boolean isAttributeSpecified(int param) {
753: return false;
754: }
755:
756: public boolean isCharacters() {
757: return (_state == CHARACTERS);
758: }
759:
760: public boolean isEndElement() {
761: return (_state == END_ELEMENT);
762: }
763:
764: public boolean isStandalone() {
765: return true;
766: }
767:
768: public boolean isStartElement() {
769: return (_state == START_ELEMENT);
770: }
771:
772: public boolean isWhiteSpace() {
773: if (_state == CHARACTERS || _state == CDATA)
774: return getText().trim().length() == 0;
775: return false;
776: }
777:
778: private static int mapNodeTypeToState(int nodetype) {
779: switch (nodetype) {
780: case CDATA_SECTION_NODE:
781: return CDATA;
782: case COMMENT_NODE:
783: return COMMENT;
784: case ELEMENT_NODE:
785: return START_ELEMENT;
786: case ENTITY_NODE:
787: return ENTITY_DECLARATION;
788: case ENTITY_REFERENCE_NODE:
789: return ENTITY_REFERENCE;
790: case NOTATION_NODE:
791: return NOTATION_DECLARATION;
792: case PROCESSING_INSTRUCTION_NODE:
793: return PROCESSING_INSTRUCTION;
794: case TEXT_NODE:
795: return CHARACTERS;
796: default:
797: throw new RuntimeException(
798: "DOMStreamReader: Unexpected node type");
799: }
800: }
801:
802: public int next() throws XMLStreamException {
803: while (true) {
804: int r = _next();
805: if (r != CHARACTERS)
806: return r;
807:
808: // if we are currently at text node, make sure that this is a meaningful text node.
809: Node prev = _current.getPreviousSibling();
810: if (prev != null && prev.getNodeType() == Node.TEXT_NODE)
811: continue; // nope. this is just a continuation of previous text that should be invisible
812:
813: Text t = (Text) _current;
814: wholeText = t.getWholeText();
815: if (wholeText.length() == 0)
816: continue; // nope. this is empty text.
817: return CHARACTERS;
818: }
819: }
820:
821: private int _next() throws XMLStreamException {
822: Node child;
823:
824: // Indicate that attributes still need processing
825: _needAttributesSplit = true;
826:
827: switch (_state) {
828: case END_DOCUMENT:
829: throw new IllegalStateException(
830: "DOMStreamReader: Calling next() at END_DOCUMENT");
831: case START_DOCUMENT:
832: // Don't skip document element if this is a fragment
833: if (_current.getNodeType() == ELEMENT_NODE) {
834: return (_state = START_ELEMENT);
835: }
836:
837: child = _current.getFirstChild();
838: if (child == null) {
839: return (_state = END_DOCUMENT);
840: } else {
841: _current = child;
842: return (_state = mapNodeTypeToState(_current
843: .getNodeType()));
844: }
845: case START_ELEMENT:
846: depth++;
847:
848: child = _current.getFirstChild();
849: if (child == null) {
850: return (_state = END_ELEMENT);
851: } else {
852: _current = child;
853: return (_state = mapNodeTypeToState(_current
854: .getNodeType()));
855: }
856: case END_ELEMENT:
857: depth--;
858: // fall through next
859: case CHARACTERS:
860: case COMMENT:
861: case CDATA:
862: case ENTITY_REFERENCE:
863: case PROCESSING_INSTRUCTION:
864: // If at the end of this fragment, then terminate traversal
865: if (_current == _start) {
866: return (_state = END_DOCUMENT);
867: }
868:
869: Node sibling = _current.getNextSibling();
870: if (sibling == null) {
871: _current = _current.getParentNode();
872: // getParentNode() returns null for fragments
873: _state = (_current == null || _current.getNodeType() == DOCUMENT_NODE) ? END_DOCUMENT
874: : END_ELEMENT;
875: return _state;
876: } else {
877: _current = sibling;
878: return (_state = mapNodeTypeToState(_current
879: .getNodeType()));
880: }
881: case DTD:
882: case ATTRIBUTE:
883: case NAMESPACE:
884: default:
885: throw new RuntimeException(
886: "DOMStreamReader: Unexpected internal state");
887: }
888: }
889:
890: public int nextTag() throws javax.xml.stream.XMLStreamException {
891: int eventType = next();
892: while (eventType == CHARACTERS && isWhiteSpace()
893: || eventType == CDATA && isWhiteSpace()
894: || eventType == SPACE
895: || eventType == PROCESSING_INSTRUCTION
896: || eventType == COMMENT) {
897: eventType = next();
898: }
899: if (eventType != START_ELEMENT && eventType != END_ELEMENT) {
900: throw new XMLStreamException2(
901: "DOMStreamReader: Expected start or end tag");
902: }
903: return eventType;
904: }
905:
906: public void require(int type, String namespaceURI, String localName)
907: throws javax.xml.stream.XMLStreamException {
908: if (type != _state) {
909: throw new XMLStreamException2(
910: "DOMStreamReader: Required event type not found");
911: }
912: if (namespaceURI != null
913: && !namespaceURI.equals(getNamespaceURI())) {
914: throw new XMLStreamException2(
915: "DOMStreamReader: Required namespaceURI not found");
916: }
917: if (localName != null && !localName.equals(getLocalName())) {
918: throw new XMLStreamException2(
919: "DOMStreamReader: Required localName not found");
920: }
921: }
922:
923: public boolean standaloneSet() {
924: return true;
925: }
926:
927: // -- Debugging ------------------------------------------------------
928:
929: private static void displayDOM(Node node,
930: java.io.OutputStream ostream) {
931: try {
932: System.out.println("\n====\n");
933: XmlUtil.newTransformer().transform(new DOMSource(node),
934: new StreamResult(ostream));
935: System.out.println("\n====\n");
936: } catch (Exception e) {
937: e.printStackTrace();
938: }
939: }
940:
941: private static void verifyDOMIntegrity(Node node) {
942: switch (node.getNodeType()) {
943: case ELEMENT_NODE:
944: case ATTRIBUTE_NODE:
945:
946: // DOM level 1?
947: if (node.getLocalName() == null) {
948: System.out.println("WARNING: DOM level 1 node found");
949: System.out.println(" -> node.getNodeName() = "
950: + node.getNodeName());
951: System.out.println(" -> node.getNamespaceURI() = "
952: + node.getNamespaceURI());
953: System.out.println(" -> node.getLocalName() = "
954: + node.getLocalName());
955: System.out.println(" -> node.getPrefix() = "
956: + node.getPrefix());
957: }
958:
959: if (node.getNodeType() == ATTRIBUTE_NODE)
960: return;
961:
962: NamedNodeMap attrs = node.getAttributes();
963: for (int i = 0; i < attrs.getLength(); i++) {
964: verifyDOMIntegrity(attrs.item(i));
965: }
966: case DOCUMENT_NODE:
967: NodeList children = node.getChildNodes();
968: for (int i = 0; i < children.getLength(); i++) {
969: verifyDOMIntegrity(children.item(i));
970: }
971: }
972: }
973:
974: private static String fixNull(String s) {
975: if (s == null)
976: return "";
977: else
978: return s;
979: }
980: }
|