001: package net.sf.saxon.dom;
002:
003: import net.sf.saxon.functions.DeepEqual;
004: import net.sf.saxon.om.*;
005: import net.sf.saxon.pattern.NodeKindTest;
006: import net.sf.saxon.sort.AtomicComparer;
007: import net.sf.saxon.sort.CodepointCollator;
008: import net.sf.saxon.style.StandardNames;
009: import net.sf.saxon.trans.XPathException;
010: import net.sf.saxon.type.Type;
011: import org.w3c.dom.*;
012:
013: import java.util.ArrayList;
014: import java.util.List;
015:
016: /**
017: * This class implements the DOM Node interface as a wrapper around a Saxon NodeInfo object
018: */
019:
020: public abstract class NodeOverNodeInfo implements Node {
021:
022: protected NodeInfo node;
023:
024: public NodeInfo getUnderlyingNodeInfo() {
025: return node;
026: }
027:
028: public void setUnderlyingNodeInfo(NodeInfo node) {
029: this .node = node;
030: }
031:
032: public static NodeOverNodeInfo wrap(NodeInfo node) {
033: NodeOverNodeInfo n = null;
034: if (node == null) {
035: return null;
036: }
037: switch (node.getNodeKind()) {
038: case Type.DOCUMENT:
039: n = new DocumentOverNodeInfo();
040: break;
041: case Type.ELEMENT:
042: n = new ElementOverNodeInfo();
043: break;
044: case Type.ATTRIBUTE:
045: n = new AttrOverNodeInfo();
046: break;
047: case Type.TEXT:
048: case Type.COMMENT:
049: n = new TextOverNodeInfo();
050: break;
051: case Type.PROCESSING_INSTRUCTION:
052: n = new PIOverNodeInfo();
053: break;
054: case Type.NAMESPACE:
055: throw new IllegalArgumentException(
056: "Cannot wrap a namespace node as a DOM Node");
057: }
058: n.node = node;
059: return n;
060: }
061:
062: /**
063: * Determine whether this is the same node as another node. DOM Level 3 method.
064: * @return true if this Node object and the supplied Node object represent the
065: * same node in the tree.
066: */
067:
068: public final boolean isSameNode(Node other) {
069: if (other instanceof NodeOverNodeInfo) {
070: return node.isSameNodeInfo(((NodeOverNodeInfo) other).node);
071: } else {
072: return false;
073: }
074: }
075:
076: /**
077: * Get the base URI for the node. Default implementation for child nodes gets
078: * the base URI of the parent node.
079: */
080:
081: public String getBaseURI() {
082: return node.getBaseURI();
083: }
084:
085: /**
086: * Get the name of this node, following the DOM rules
087: * @return The name of the node. For an element this is the element name, for an attribute
088: * it is the attribute name, as a QName. Other node types return conventional names such
089: * as "#text" or "#comment"
090: */
091:
092: public String getNodeName() {
093: switch (node.getNodeKind()) {
094: case Type.DOCUMENT:
095: return "#document";
096: case Type.ELEMENT:
097: return node.getDisplayName();
098: case Type.ATTRIBUTE:
099: return node.getDisplayName();
100: case Type.TEXT:
101: return "#text";
102: case Type.COMMENT:
103: return "#comment";
104: case Type.PROCESSING_INSTRUCTION:
105: return node.getLocalPart();
106: case Type.NAMESPACE:
107: return node.getLocalPart();
108: default:
109: return "#unknown";
110: }
111: }
112:
113: /**
114: * Get the local name of this node, following the DOM rules
115: * @return The local name of the node. For an element this is the element name, for an attribute
116: * it is the attribute name, as a QName. Other node types return conventional names such
117: * as "#text" or "#comment"
118: */
119:
120: public String getLocalName() {
121: switch (node.getNodeKind()) {
122: case Type.ELEMENT:
123: case Type.ATTRIBUTE:
124: return node.getLocalPart();
125: case Type.DOCUMENT:
126: case Type.TEXT:
127: case Type.COMMENT:
128: case Type.PROCESSING_INSTRUCTION:
129: case Type.NAMESPACE:
130: default:
131: return null;
132: }
133: }
134:
135: /**
136: * Determine whether this (attribute) node is an ID. This method is introduced
137: * in DOM Level 3. The current implementation is simplistic; it returns true if the
138: * type annotation of the node is xs:ID (subtypes not allowed).
139: */
140:
141: public boolean isId() {
142: return (node.getTypeAnnotation() & NamePool.FP_MASK) == StandardNames.XS_ID;
143: }
144:
145: /**
146: * Determine whether the node has any children.
147: * @return <code>true</code> if this node has any attributes,
148: * <code>false</code> otherwise.
149: */
150:
151: public boolean hasChildNodes() {
152: return node.iterateAxis(Axis.CHILD).next() != null;
153: }
154:
155: /**
156: * Returns whether this node has any attributes.
157: * @return <code>true</code> if this node has any attributes,
158: * <code>false</code> otherwise.
159: * @since DOM Level 2
160: */
161:
162: public boolean hasAttributes() {
163: return node.iterateAxis(Axis.ATTRIBUTE).next() != null;
164: }
165:
166: /**
167: * Get the type of this node (DOM method). Note, the numbers assigned to node kinds
168: * in Saxon (see module Type) are the same as those assigned in the DOM
169: */
170:
171: public short getNodeType() {
172: return (short) node.getNodeKind();
173: }
174:
175: /**
176: * Find the parent node of this node (DOM method).
177: * @return The Node object describing the containing element or root node.
178: */
179:
180: public Node getParentNode() {
181: return wrap(node.getParent());
182: }
183:
184: /**
185: * Get the previous sibling of the node (DOM method)
186: * @return The previous sibling node. Returns null if the current node is the first
187: * child of its parent.
188: */
189:
190: public Node getPreviousSibling() {
191: return wrap((NodeInfo) node.iterateAxis(Axis.PRECEDING_SIBLING)
192: .next());
193: }
194:
195: /**
196: * Get next sibling node (DOM method)
197: * @return The next sibling node. Returns null if the current node is the last
198: * child of its parent.
199: */
200:
201: public Node getNextSibling() {
202: return wrap((NodeInfo) node.iterateAxis(Axis.FOLLOWING_SIBLING)
203: .next());
204: }
205:
206: /**
207: * Get first child (DOM method)
208: * @return the first child node of this node, or null if it has no children
209: */
210:
211: public Node getFirstChild() {
212: return wrap((NodeInfo) node.iterateAxis(Axis.CHILD).next());
213: }
214:
215: /**
216: * Get last child (DOM method)
217: * @return last child of this node, or null if it has no children
218: */
219:
220: public Node getLastChild() {
221: AxisIterator children = node.iterateAxis(Axis.CHILD);
222: NodeInfo last = null;
223: while (true) {
224: NodeInfo next = (NodeInfo) children.next();
225: if (next == null) {
226: return wrap(last);
227: } else {
228: last = next;
229: }
230: }
231: }
232:
233: /**
234: * Get the outermost element. (DOM method)
235: * @return the Element for the outermost element of the document. If the document is
236: * not well-formed, this returns the first element child of the root if there is one, otherwise
237: * null.
238: */
239:
240: public Element getDocumentElement() {
241: NodeInfo root = node.getDocumentRoot();
242: if (root == null) {
243: return null;
244: }
245: AxisIterator children = root.iterateAxis(Axis.CHILD,
246: NodeKindTest.ELEMENT);
247: return (Element) wrap((NodeInfo) children.next());
248: }
249:
250: /**
251: * Get the node value as defined in the DOM.
252: * This is not necessarily the same as the XPath string-value.
253: */
254:
255: public String getNodeValue() {
256: switch (node.getNodeKind()) {
257: case Type.DOCUMENT:
258: case Type.ELEMENT:
259: return null;
260: case Type.ATTRIBUTE:
261: case Type.TEXT:
262: case Type.COMMENT:
263: case Type.PROCESSING_INSTRUCTION:
264: case Type.NAMESPACE:
265: return node.getStringValue();
266: default:
267: return null;
268: }
269: }
270:
271: /**
272: * Set the node value. DOM method: always fails
273: */
274:
275: public void setNodeValue(String nodeValue) throws DOMException {
276: disallowUpdate();
277: }
278:
279: /**
280: * Return a <code>NodeList</code> that contains all children of this node. If
281: * there are no children, this is a <code>NodeList</code> containing no
282: * nodes. DOM Method.
283: */
284:
285: public NodeList getChildNodes() {
286: try {
287: List nodes = new ArrayList(10);
288: SequenceIterator iter = node.iterateAxis(Axis.CHILD);
289: while (true) {
290: NodeInfo node = (NodeInfo) iter.next();
291: if (node == null)
292: break;
293: nodes.add(NodeOverNodeInfo.wrap(node));
294: }
295: return new DOMNodeList(nodes);
296: } catch (XPathException err) {
297: return null;
298: // can't happen
299: }
300: }
301:
302: /**
303: * Return a <code>NamedNodeMap</code> containing the attributes of this node (if
304: * it is an <code>Element</code> ) or <code>null</code> otherwise. (DOM method)
305: */
306:
307: public NamedNodeMap getAttributes() {
308: if (node.getNodeKind() == Type.ELEMENT) {
309: return new DOMAttributeMap(node);
310: } else {
311: return null;
312: }
313: }
314:
315: /**
316: * Return the <code>Document</code> object associated with this node. (DOM method)
317: */
318:
319: public Document getOwnerDocument() {
320: return (Document) wrap(node.getDocumentRoot());
321: }
322:
323: /**
324: * Insert the node <code>newChild</code> before the existing child node
325: * <code>refChild</code>. DOM method: always fails.
326: * @param newChild The node to insert.
327: * @param refChild The reference node, i.e., the node before which the
328: * new node must be inserted.
329: * @return The node being inserted.
330: * @exception org.w3c.dom.DOMException
331: * NO_MODIFICATION_ALLOWED_ERR: Always raised.
332: */
333:
334: public Node insertBefore(Node newChild, Node refChild)
335: throws DOMException {
336: disallowUpdate();
337: return null;
338: }
339:
340: /**
341: * Replace the child node <code>oldChild</code> with
342: * <code>newChild</code> in the list of children, and returns the
343: * <code>oldChild</code> node. Always fails.
344: * @param newChild The new node to put in the child list.
345: * @param oldChild The node being replaced in the list.
346: * @return The node replaced.
347: * @exception org.w3c.dom.DOMException
348: * NO_MODIFICATION_ALLOWED_ERR: Always raised.
349: */
350:
351: public Node replaceChild(Node newChild, Node oldChild)
352: throws DOMException {
353: disallowUpdate();
354: return null;
355: }
356:
357: /**
358: * Remove the child node indicated by <code>oldChild</code> from the
359: * list of children, and returns it. DOM method: always fails.
360: * @param oldChild The node being removed.
361: * @return The node removed.
362: * @exception org.w3c.dom.DOMException
363: * NO_MODIFICATION_ALLOWED_ERR: Always raised.
364: */
365:
366: public Node removeChild(Node oldChild) throws DOMException {
367: disallowUpdate();
368: return null;
369: }
370:
371: /**
372: * Adds the node <code>newChild</code> to the end of the list of children
373: * of this node. DOM method: always fails.
374: * @param newChild The node to add.
375: * @return The node added.
376: * @exception org.w3c.dom.DOMException
377: * <br> NO_MODIFICATION_ALLOWED_ERR: Always raised.
378: */
379:
380: public Node appendChild(Node newChild) throws DOMException {
381: disallowUpdate();
382: return null;
383: }
384:
385: /**
386: * Returns a duplicate of this node, i.e., serves as a generic copy
387: * constructor for nodes. The duplicate node has no parent. Not
388: * implemented: always returns null. (Because trees are read-only, there
389: * would be no way of using the resulting node.)
390: * @param deep If <code>true</code> , recursively clone the subtree under
391: * the specified node; if <code>false</code> , clone only the node
392: * itself (and its attributes, if it is an <code>Element</code> ).
393: * @return The duplicate node.
394: */
395:
396: public Node cloneNode(boolean deep) {
397: // Not implemented
398: return null;
399: }
400:
401: /**
402: * Puts all <code>Text</code> nodes in the full depth of the sub-tree
403: * underneath this <code>Node</code>, including attribute nodes, into a
404: * "normal" form where only structure (e.g., elements, comments,
405: * processing instructions, CDATA sections, and entity references)
406: * separates <code>Text</code> nodes, i.e., there are neither adjacent
407: * <code>Text</code> nodes nor empty <code>Text</code> nodes.
408: * @since DOM Level 2
409: */
410:
411: public void normalize() {
412: // null operation; nodes are always normalized
413: }
414:
415: /**
416: * Tests whether the DOM implementation implements a specific feature and
417: * that feature is supported by this node.
418: * @param feature The name of the feature to test. This is the same name
419: * which can be passed to the method <code>hasFeature</code> on
420: * <code>DOMImplementation</code> .
421: * @param version This is the version number of the feature to test. In
422: * Level 2, version 1, this is the string "2.0". If the version is not
423: * specified, supporting any version of the feature will cause the
424: * method to return <code>true</code> .
425: * @return Returns <code>true</code> if the specified feature is supported
426: * on this node, <code>false</code> otherwise.
427: * @since DOM Level 2
428: */
429:
430: public boolean isSupported(String feature, String version) {
431: return feature.equalsIgnoreCase("xml");
432: }
433:
434: /**
435: * Alternative to isSupported(), defined in a draft DOM spec
436: */
437:
438: public boolean supports(String feature, String version) {
439: return isSupported(feature, version);
440: }
441:
442: /**
443: * The namespace URI of this node, or <code>null</code> if it is
444: * unspecified. DOM method.
445: * <br> This is not a computed value that is the result of a namespace
446: * lookup based on an examination of the namespace declarations in scope.
447: * It is merely the namespace URI given at creation time.
448: * <br> For nodes of any type other than <code>ELEMENT_NODE</code> and
449: * <code>ATTRIBUTE_NODE</code> and nodes created with a DOM Level 1
450: * method, such as <code>createElement</code> from the
451: * <code>Document</code> interface, this is always <code>null</code> .
452: * Per the Namespaces in XML Specification an attribute does not
453: * inherit its namespace from the element it is attached to. If an
454: * attribute is not explicitly given a namespace, it simply has no
455: * namespace.
456: * @since DOM Level 2
457: */
458:
459: public String getNamespaceURI() {
460: String uri = node.getURI();
461: return ("".equals(uri) ? null : uri);
462: }
463:
464: /**
465: * The namespace prefix of this node, or <code>null</code> if it is
466: * unspecified.
467: * <br>For nodes of any type other than <code>ELEMENT_NODE</code> and
468: * <code>ATTRIBUTE_NODE</code> and nodes created with a DOM Level 1
469: * method, such as <code>createElement</code> from the
470: * <code>Document</code> interface, this is always <code>null</code>.
471: *
472: * @since DOM Level 2
473: */
474:
475: public String getPrefix() {
476: String p = node.getNamePool().getPrefix(node.getNameCode());
477: return ("".equals(p) ? null : p);
478: }
479:
480: /**
481: * Set the namespace prefix of this node. Always fails.
482: */
483:
484: public void setPrefix(String prefix) throws DOMException {
485: disallowUpdate();
486: }
487:
488: /**
489: * Compare the position of the (other) node in document order with the reference node (this node).
490: * DOM Level 3 method.
491: * @param other the other node.
492: * @return -1 (this node is first), 0 (same node), +1 (other node is first)
493: * @throws org.w3c.dom.DOMException
494: */
495:
496: public short compareDocumentPosition(Node other)
497: throws DOMException {
498: final short DOCUMENT_POSITION_DISCONNECTED = 0x01;
499: final short DOCUMENT_POSITION_PRECEDING = 0x02;
500: final short DOCUMENT_POSITION_FOLLOWING = 0x04;
501: final short DOCUMENT_POSITION_CONTAINS = 0x08;
502: final short DOCUMENT_POSITION_CONTAINED_BY = 0x10;
503: if (!(other instanceof NodeOverNodeInfo)) {
504: return DOCUMENT_POSITION_DISCONNECTED;
505: }
506: int c = node.compareOrder(((NodeOverNodeInfo) other).node);
507: if (c == 0) {
508: return (short) 0;
509: } else if (c == -1) {
510: short d = compareDocumentPosition(other.getParentNode());
511: short result = DOCUMENT_POSITION_FOLLOWING;
512: if (d == 0 || (d & DOCUMENT_POSITION_CONTAINED_BY) != 0) {
513: d |= DOCUMENT_POSITION_CONTAINED_BY;
514: }
515: return result;
516: } else if (c == +1) {
517: short d = ((NodeOverNodeInfo) getParentNode())
518: .compareDocumentPosition(other);
519: short result = DOCUMENT_POSITION_PRECEDING;
520: if (d == 0 || (d & DOCUMENT_POSITION_CONTAINS) != 0) {
521: d |= DOCUMENT_POSITION_CONTAINS;
522: }
523: return result;
524: } else {
525: throw new AssertionError();
526: }
527: }
528:
529: /**
530: * Get the text content of a node. This is a DOM Level 3 method. The definition
531: * is the same as the definition of the string value of a node in XPath, except
532: * in the case of document nodes.
533: * @return the string value of the node, or null in the case of document nodes.
534: * @throws org.w3c.dom.DOMException
535: */
536:
537: public String getTextContent() throws DOMException {
538: if (node.getNodeKind() == Type.DOCUMENT) {
539: return null;
540: } else {
541: return node.getStringValue();
542: }
543: }
544:
545: /**
546: * Set the text content of a node. DOM Level 3 method, not supported.
547: * @param textContent
548: * @throws org.w3c.dom.DOMException
549: */
550:
551: public void setTextContent(String textContent) throws DOMException {
552: disallowUpdate();
553: }
554:
555: /**
556: * Get the (first) prefix assigned to a specified namespace URI, or null
557: * if the namespace is not in scope. DOM Level 3 method.
558: * @param namespaceURI the namespace whose prefix is required
559: * @return the corresponding prefix
560: */
561:
562: public String lookupPrefix(String namespaceURI) {
563: AxisIterator iter = node.iterateAxis(Axis.NAMESPACE);
564: while (true) {
565: NodeInfo ns = (NodeInfo) iter.next();
566: if (ns == null) {
567: return null;
568: }
569: if (ns.getStringValue().equals(namespaceURI)) {
570: return ns.getLocalPart();
571: }
572: }
573: }
574:
575: /**
576: * Test whether a particular namespace is the default namespace.
577: * DOM Level 3 method.
578: * @param namespaceURI the namespace to be tested
579: * @return true if this is the default namespace
580: */
581:
582: public boolean isDefaultNamespace(String namespaceURI) {
583: return namespaceURI.equals(lookupNamespaceURI(""));
584: }
585:
586: /**
587: * Find the URI corresponding to a given in-scope prefix
588: * @param prefix The namespace prefix whose namespace URI is required.
589: * @return the corresponding namespace URI, or null if the prefix is
590: * not declared.
591: */
592:
593: public String lookupNamespaceURI(String prefix) {
594: AxisIterator iter = node.iterateAxis(Axis.NAMESPACE);
595: while (true) {
596: NodeInfo ns = (NodeInfo) iter.next();
597: if (ns == null) {
598: return null;
599: }
600: if (ns.getLocalPart().equals(prefix)) {
601: return ns.getStringValue();
602: }
603: }
604: }
605:
606: /**
607: * Compare whether two nodes have the same content. This is a DOM Level 3 method; the implementation
608: * uses the same algorithm as the XPath deep-equals() function.
609: * @param arg The node to be compared. This must wrap a Saxon NodeInfo.
610: * @return true if the two nodes are deep-equal.
611: */
612:
613: public boolean isEqualNode(Node arg) {
614: if (!(arg instanceof NodeOverNodeInfo)) {
615: throw new IllegalArgumentException(
616: "Other Node must wrap a Saxon NodeInfo");
617: }
618: return DeepEqual.deepEquals(SingletonIterator
619: .makeIterator(node), SingletonIterator
620: .makeIterator(((NodeOverNodeInfo) arg).node),
621: new AtomicComparer(CodepointCollator.getInstance(),
622: node.getConfiguration()), node
623: .getConfiguration(), 0);
624: }
625:
626: /**
627: * Get a feature of this node. DOM Level 3 method, always returns null.
628: * @param feature the required feature
629: * @param version the version of the required feature
630: * @return the value of the feature. Always null in this implementation
631: */
632:
633: public Object getFeature(String feature, String version) {
634: return null;
635: }
636:
637: /**
638: * Set user data. Always throws UnsupportedOperationException in this implementation
639: * @param key
640: * @param data
641: * @param handler
642: * @return This implementation always throws an exception
643: */
644:
645: public Object setUserData(String key, Object data,
646: UserDataHandler handler) {
647: disallowUpdate();
648: return null;
649: }
650:
651: /**
652: * Get user data associated with this node. DOM Level 3 method, always returns
653: * null in this implementation
654: * @param key identifies the user data required
655: * @return always null in this implementation
656: */
657: public Object getUserData(String key) {
658: return null;
659: }
660:
661: /**
662: * Internal method used to indicate that update operations are not allowed
663: */
664:
665: protected static void disallowUpdate() throws DOMException {
666: throw new UnsupportedOperationException(
667: "The Saxon DOM cannot be updated");
668: }
669:
670: }
671:
672: //
673: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
674: // you may not use this file except in compliance with the License. You may obtain a copy of the
675: // License at http://www.mozilla.org/MPL/
676: //
677: // Software distributed under the License is distributed on an "AS IS" basis,
678: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
679: // See the License for the specific language governing rights and limitations under the License.
680: //
681: // The Original Code is: all this file.
682: //
683: // The Initial Developer of the Original Code is Michael H. Kay.
684: //
685: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
686: //
687: // Contributor(s): none.
688: //
|