001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jxpath.ri.model.dom;
017:
018: import java.util.HashMap;
019: import java.util.Locale;
020: import java.util.Map;
021:
022: import org.apache.commons.jxpath.AbstractFactory;
023: import org.apache.commons.jxpath.JXPathContext;
024: import org.apache.commons.jxpath.JXPathException;
025: import org.apache.commons.jxpath.Pointer;
026: import org.apache.commons.jxpath.ri.Compiler;
027: import org.apache.commons.jxpath.ri.QName;
028: import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
029: import org.apache.commons.jxpath.ri.compiler.NodeTest;
030: import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
031: import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
032: import org.apache.commons.jxpath.ri.model.beans.NullPointer;
033: import org.apache.commons.jxpath.ri.model.NodeIterator;
034: import org.apache.commons.jxpath.ri.model.NodePointer;
035: import org.apache.commons.jxpath.util.TypeUtils;
036: import org.w3c.dom.Attr;
037: import org.w3c.dom.Comment;
038: import org.w3c.dom.Document;
039: import org.w3c.dom.Element;
040: import org.w3c.dom.NamedNodeMap;
041: import org.w3c.dom.Node;
042: import org.w3c.dom.NodeList;
043: import org.w3c.dom.ProcessingInstruction;
044:
045: /**
046: * A Pointer that points to a DOM node.
047: *
048: * @author Dmitri Plotnikov
049: * @version $Revision: 1.24 $ $Date: 2004/06/29 22:58:17 $
050: */
051: public class DOMNodePointer extends NodePointer {
052: private Node node;
053: private Map namespaces;
054: private String defaultNamespace;
055: private String id;
056:
057: public static final String XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
058: public static final String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
059:
060: public DOMNodePointer(Node node, Locale locale) {
061: super (null, locale);
062: this .node = node;
063: }
064:
065: public DOMNodePointer(Node node, Locale locale, String id) {
066: super (null, locale);
067: this .node = node;
068: this .id = id;
069: }
070:
071: public DOMNodePointer(NodePointer parent, Node node) {
072: super (parent);
073: this .node = node;
074: }
075:
076: public boolean testNode(NodeTest test) {
077: return testNode(node, test);
078: }
079:
080: public static boolean testNode(Node node, NodeTest test) {
081: if (test == null) {
082: return true;
083: } else if (test instanceof NodeNameTest) {
084: if (node.getNodeType() != Node.ELEMENT_NODE) {
085: return false;
086: }
087:
088: NodeNameTest nodeNameTest = (NodeNameTest) test;
089: QName testName = nodeNameTest.getNodeName();
090: String namespaceURI = nodeNameTest.getNamespaceURI();
091: boolean wildcard = nodeNameTest.isWildcard();
092: String testPrefix = testName.getPrefix();
093: if (wildcard && testPrefix == null) {
094: return true;
095: }
096:
097: if (wildcard
098: || testName.getName().equals(
099: DOMNodePointer.getLocalName(node))) {
100: String nodeNS = DOMNodePointer.getNamespaceURI(node);
101: return equalStrings(namespaceURI, nodeNS);
102: }
103: } else if (test instanceof NodeTypeTest) {
104: int nodeType = node.getNodeType();
105: switch (((NodeTypeTest) test).getNodeType()) {
106: case Compiler.NODE_TYPE_NODE:
107: return nodeType == Node.ELEMENT_NODE;
108: case Compiler.NODE_TYPE_TEXT:
109: return nodeType == Node.CDATA_SECTION_NODE
110: || nodeType == Node.TEXT_NODE;
111: case Compiler.NODE_TYPE_COMMENT:
112: return nodeType == Node.COMMENT_NODE;
113: case Compiler.NODE_TYPE_PI:
114: return nodeType == Node.PROCESSING_INSTRUCTION_NODE;
115: }
116: return false;
117: } else if (test instanceof ProcessingInstructionTest) {
118: if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
119: String testPI = ((ProcessingInstructionTest) test)
120: .getTarget();
121: String nodePI = ((ProcessingInstruction) node)
122: .getTarget();
123: return testPI.equals(nodePI);
124: }
125: }
126: return false;
127: }
128:
129: private static boolean equalStrings(String s1, String s2) {
130: if (s1 == null && s2 != null) {
131: return false;
132: }
133: if (s1 != null && s2 == null) {
134: return false;
135: }
136:
137: if (s1 != null && !s1.trim().equals(s2.trim())) {
138: return false;
139: }
140:
141: return true;
142: }
143:
144: public QName getName() {
145: String ln = null;
146: String ns = null;
147: int type = node.getNodeType();
148: if (type == Node.ELEMENT_NODE) {
149: ns = DOMNodePointer.getPrefix(node);
150: ln = DOMNodePointer.getLocalName(node);
151: } else if (type == Node.PROCESSING_INSTRUCTION_NODE) {
152: ln = ((ProcessingInstruction) node).getTarget();
153: }
154: return new QName(ns, ln);
155: }
156:
157: public String getNamespaceURI() {
158: return getNamespaceURI(node);
159: }
160:
161: public NodeIterator childIterator(NodeTest test, boolean reverse,
162: NodePointer startWith) {
163: return new DOMNodeIterator(this , test, reverse, startWith);
164: }
165:
166: public NodeIterator attributeIterator(QName name) {
167: return new DOMAttributeIterator(this , name);
168: }
169:
170: public NodePointer namespacePointer(String prefix) {
171: return new NamespacePointer(this , prefix);
172: }
173:
174: public NodeIterator namespaceIterator() {
175: return new DOMNamespaceIterator(this );
176: }
177:
178: public String getNamespaceURI(String prefix) {
179: if (prefix == null || prefix.equals("")) {
180: return getDefaultNamespaceURI();
181: }
182:
183: if (prefix.equals("xml")) {
184: return XML_NAMESPACE_URI;
185: }
186:
187: if (prefix.equals("xmlns")) {
188: return XMLNS_NAMESPACE_URI;
189: }
190:
191: String namespace = null;
192: if (namespaces == null) {
193: namespaces = new HashMap();
194: } else {
195: namespace = (String) namespaces.get(prefix);
196: }
197:
198: if (namespace == null) {
199: String qname = "xmlns:" + prefix;
200: Node aNode = node;
201: if (aNode instanceof Document) {
202: aNode = ((Document) aNode).getDocumentElement();
203: }
204: while (aNode != null) {
205: if (aNode.getNodeType() == Node.ELEMENT_NODE) {
206: Attr attr = ((Element) aNode)
207: .getAttributeNode(qname);
208: if (attr != null) {
209: namespace = attr.getValue();
210: break;
211: }
212: }
213: aNode = aNode.getParentNode();
214: }
215: if (namespace == null || namespace.equals("")) {
216: namespace = NodePointer.UNKNOWN_NAMESPACE;
217: }
218: }
219:
220: namespaces.put(prefix, namespace);
221: // TBD: We are supposed to resolve relative URIs to absolute ones.
222: return namespace;
223: }
224:
225: private String getNamespaceURI(String prefix, String namespace) {
226: String qname = "xmlns:" + prefix;
227: Node aNode = node;
228: if (aNode instanceof Document) {
229: aNode = ((Document) aNode).getDocumentElement();
230: }
231: while (aNode != null) {
232: if (aNode.getNodeType() == Node.ELEMENT_NODE) {
233: Attr attr = ((Element) aNode).getAttributeNode(qname);
234: if (attr != null) {
235: namespace = attr.getValue();
236: break;
237: }
238: }
239: aNode = aNode.getParentNode();
240: }
241: return namespace;
242: }
243:
244: public String getDefaultNamespaceURI() {
245: if (defaultNamespace == null) {
246: Node aNode = node;
247: while (aNode != null) {
248: if (aNode.getNodeType() == Node.ELEMENT_NODE) {
249: Attr attr = ((Element) aNode)
250: .getAttributeNode("xmlns");
251: if (attr != null) {
252: defaultNamespace = attr.getValue();
253: break;
254: }
255: }
256: aNode = aNode.getParentNode();
257: }
258: }
259: if (defaultNamespace == null) {
260: defaultNamespace = "";
261: }
262: // TBD: We are supposed to resolve relative URIs to absolute ones.
263: return defaultNamespace.equals("") ? null : defaultNamespace;
264: }
265:
266: public Object getBaseValue() {
267: return node;
268: }
269:
270: public Object getImmediateNode() {
271: return node;
272: }
273:
274: public boolean isActual() {
275: return true;
276: }
277:
278: public boolean isCollection() {
279: return false;
280: }
281:
282: public int getLength() {
283: return 1;
284: }
285:
286: public boolean isLeaf() {
287: return !node.hasChildNodes();
288: }
289:
290: /**
291: * Returns true if the xml:lang attribute for the current node
292: * or its parent has the specified prefix <i>lang</i>.
293: * If no node has this prefix, calls <code>super.isLanguage(lang)</code>.
294: */
295: public boolean isLanguage(String lang) {
296: String current = getLanguage();
297: if (current == null) {
298: return super .isLanguage(lang);
299: }
300: return current.toUpperCase().startsWith(lang.toUpperCase());
301: }
302:
303: protected String getLanguage() {
304: Node n = node;
305: while (n != null) {
306: if (n.getNodeType() == Node.ELEMENT_NODE) {
307: Element e = (Element) n;
308: String attr = e.getAttribute("xml:lang");
309: if (attr != null && !attr.equals("")) {
310: return attr;
311: }
312: }
313: n = n.getParentNode();
314: }
315: return null;
316: }
317:
318: /**
319: * Sets contents of the node to the specified value. If the value is
320: * a String, the contents of the node are replaced with this text.
321: * If the value is an Element or Document, the children of the
322: * node are replaced with the children of the passed node.
323: */
324: public void setValue(Object value) {
325: if (node.getNodeType() == Node.TEXT_NODE
326: || node.getNodeType() == Node.CDATA_SECTION_NODE) {
327: String string = (String) TypeUtils.convert(value,
328: String.class);
329: if (string != null && !string.equals("")) {
330: node.setNodeValue(string);
331: } else {
332: node.getParentNode().removeChild(node);
333: }
334: } else {
335: NodeList children = node.getChildNodes();
336: int count = children.getLength();
337: for (int i = count; --i >= 0;) {
338: Node child = children.item(i);
339: node.removeChild(child);
340: }
341:
342: if (value instanceof Node) {
343: Node valueNode = (Node) value;
344: if (valueNode instanceof Element
345: || valueNode instanceof Document) {
346: children = valueNode.getChildNodes();
347: for (int i = 0; i < children.getLength(); i++) {
348: Node child = children.item(i);
349: node.appendChild(child.cloneNode(true));
350: }
351: } else {
352: node.appendChild(valueNode.cloneNode(true));
353: }
354: } else {
355: String string = (String) TypeUtils.convert(value,
356: String.class);
357: if (string != null && !string.equals("")) {
358: Node textNode = node.getOwnerDocument()
359: .createTextNode(string);
360: node.appendChild(textNode);
361: }
362: }
363: }
364: }
365:
366: public NodePointer createChild(JXPathContext context, QName name,
367: int index) {
368: if (index == WHOLE_COLLECTION) {
369: index = 0;
370: }
371: boolean success = getAbstractFactory(context).createObject(
372: context, this , node, name.toString(), index);
373: if (success) {
374: NodeTest nodeTest;
375: String prefix = name.getPrefix();
376: if (prefix != null) {
377: String namespaceURI = context.getNamespaceURI(prefix);
378: nodeTest = new NodeNameTest(name, namespaceURI);
379: } else {
380: nodeTest = new NodeNameTest(name);
381: }
382:
383: NodeIterator it = childIterator(nodeTest, false, null);
384: if (it != null && it.setPosition(index + 1)) {
385: return it.getNodePointer();
386: }
387: }
388: throw new JXPathException(
389: "Factory could not create a child node for path: "
390: + asPath() + "/" + name + "[" + (index + 1)
391: + "]");
392: }
393:
394: public NodePointer createChild(JXPathContext context, QName name,
395: int index, Object value) {
396: NodePointer ptr = createChild(context, name, index);
397: ptr.setValue(value);
398: return ptr;
399: }
400:
401: public NodePointer createAttribute(JXPathContext context, QName name) {
402: if (!(node instanceof Element)) {
403: return super .createAttribute(context, name);
404: }
405: Element element = (Element) node;
406: String prefix = name.getPrefix();
407: if (prefix != null) {
408: String ns = getNamespaceURI(prefix);
409: if (ns == null) {
410: throw new JXPathException("Unknown namespace prefix: "
411: + prefix);
412: }
413: element.setAttributeNS(ns, name.toString(), "");
414: } else {
415: if (!element.hasAttribute(name.getName())) {
416: element.setAttribute(name.getName(), "");
417: }
418: }
419: NodeIterator it = attributeIterator(name);
420: it.setPosition(1);
421: return it.getNodePointer();
422: }
423:
424: public void remove() {
425: Node parent = node.getParentNode();
426: if (parent == null) {
427: throw new JXPathException("Cannot remove root DOM node");
428: }
429: parent.removeChild(node);
430: }
431:
432: public String asPath() {
433: if (id != null) {
434: return "id('" + escape(id) + "')";
435: }
436:
437: StringBuffer buffer = new StringBuffer();
438: if (parent != null) {
439: buffer.append(parent.asPath());
440: }
441: switch (node.getNodeType()) {
442: case Node.ELEMENT_NODE:
443: // If the parent pointer is not a DOMNodePointer, it is
444: // the parent's responsibility to produce the node test part
445: // of the path
446: if (parent instanceof DOMNodePointer) {
447: if (buffer.length() == 0
448: || buffer.charAt(buffer.length() - 1) != '/') {
449: buffer.append('/');
450: }
451: String nsURI = getNamespaceURI();
452: String ln = DOMNodePointer.getLocalName(node);
453:
454: if (nsURI == null) {
455: buffer.append(ln);
456: buffer.append('[');
457: buffer.append(getRelativePositionByName()).append(
458: ']');
459: } else {
460: String prefix = getNamespaceResolver().getPrefix(
461: nsURI);
462: if (prefix != null) {
463: buffer.append(prefix);
464: buffer.append(':');
465: buffer.append(ln);
466: buffer.append('[');
467: buffer.append(getRelativePositionByName());
468: buffer.append(']');
469: } else {
470: buffer.append("node()");
471: buffer.append('[');
472: buffer.append(getRelativePositionOfElement());
473: buffer.append(']');
474: }
475: }
476: }
477: break;
478: case Node.TEXT_NODE:
479: case Node.CDATA_SECTION_NODE:
480: buffer.append("/text()");
481: buffer.append('[');
482: buffer.append(getRelativePositionOfTextNode()).append(']');
483: break;
484: case Node.PROCESSING_INSTRUCTION_NODE:
485: String target = ((ProcessingInstruction) node).getTarget();
486: buffer.append("/processing-instruction(\'");
487: buffer.append(target).append("')");
488: buffer.append('[');
489: buffer.append(getRelativePositionOfPI(target)).append(']');
490: break;
491: case Node.DOCUMENT_NODE:
492: // That'll be empty
493: }
494: return buffer.toString();
495: }
496:
497: private String escape(String string) {
498: int index = string.indexOf('\'');
499: while (index != -1) {
500: string = string.substring(0, index) + "'"
501: + string.substring(index + 1);
502: index = string.indexOf('\'');
503: }
504: index = string.indexOf('\"');
505: while (index != -1) {
506: string = string.substring(0, index) + """
507: + string.substring(index + 1);
508: index = string.indexOf('\"');
509: }
510: return string;
511: }
512:
513: private int getRelativePositionByName() {
514: int count = 1;
515: Node n = node.getPreviousSibling();
516: while (n != null) {
517: if (n.getNodeType() == Node.ELEMENT_NODE) {
518: String nm = n.getNodeName();
519: if (nm.equals(node.getNodeName())) {
520: count++;
521: }
522: }
523: n = n.getPreviousSibling();
524: }
525: return count;
526: }
527:
528: private int getRelativePositionOfElement() {
529: int count = 1;
530: Node n = node.getPreviousSibling();
531: while (n != null) {
532: if (n.getNodeType() == Node.ELEMENT_NODE) {
533: count++;
534: }
535: n = n.getPreviousSibling();
536: }
537: return count;
538: }
539:
540: private int getRelativePositionOfTextNode() {
541: int count = 1;
542: Node n = node.getPreviousSibling();
543: while (n != null) {
544: if (n.getNodeType() == Node.TEXT_NODE
545: || n.getNodeType() == Node.CDATA_SECTION_NODE) {
546: count++;
547: }
548: n = n.getPreviousSibling();
549: }
550: return count;
551: }
552:
553: private int getRelativePositionOfPI(String target) {
554: int count = 1;
555: Node n = node.getPreviousSibling();
556: while (n != null) {
557: if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
558: && ((ProcessingInstruction) n).getTarget().equals(
559: target)) {
560: count++;
561: }
562: n = n.getPreviousSibling();
563: }
564: return count;
565: }
566:
567: public int hashCode() {
568: return System.identityHashCode(node);
569: }
570:
571: public boolean equals(Object object) {
572: if (object == this ) {
573: return true;
574: }
575:
576: if (!(object instanceof DOMNodePointer)) {
577: return false;
578: }
579:
580: DOMNodePointer other = (DOMNodePointer) object;
581: return node == other.node;
582: }
583:
584: public static String getPrefix(Node node) {
585: String prefix = node.getPrefix();
586: if (prefix != null) {
587: return prefix;
588: }
589:
590: String name = node.getNodeName();
591: int index = name.lastIndexOf(':');
592: if (index == -1) {
593: return null;
594: }
595:
596: return name.substring(0, index);
597: }
598:
599: public static String getLocalName(Node node) {
600: String localName = node.getLocalName();
601: if (localName != null) {
602: return localName;
603: }
604:
605: String name = node.getNodeName();
606: int index = name.lastIndexOf(':');
607: if (index == -1) {
608: return name;
609: }
610:
611: return name.substring(index + 1);
612: }
613:
614: public static String getNamespaceURI(Node node) {
615: if (node instanceof Document) {
616: node = ((Document) node).getDocumentElement();
617: }
618:
619: Element element = (Element) node;
620:
621: String uri = element.getNamespaceURI();
622: if (uri != null) {
623: return uri;
624: }
625:
626: String qname;
627: String prefix = getPrefix(node);
628: if (prefix == null) {
629: qname = "xmlns";
630: } else {
631: qname = "xmlns:" + prefix;
632: }
633:
634: Node aNode = node;
635: while (aNode != null) {
636: if (aNode.getNodeType() == Node.ELEMENT_NODE) {
637: Attr attr = ((Element) aNode).getAttributeNode(qname);
638: if (attr != null) {
639: return attr.getValue();
640: }
641: }
642: aNode = aNode.getParentNode();
643: }
644: return null;
645: }
646:
647: public Object getValue() {
648: return stringValue(node);
649: }
650:
651: private String stringValue(Node node) {
652: int nodeType = node.getNodeType();
653: if (nodeType == Node.COMMENT_NODE) {
654: String text = ((Comment) node).getData();
655: return text == null ? "" : text.trim();
656: } else if (nodeType == Node.TEXT_NODE
657: || nodeType == Node.CDATA_SECTION_NODE) {
658: String text = node.getNodeValue();
659: return text == null ? "" : text.trim();
660: } else if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
661: String text = ((ProcessingInstruction) node).getData();
662: return text == null ? "" : text.trim();
663: } else {
664: NodeList list = node.getChildNodes();
665: StringBuffer buf = new StringBuffer(16);
666: for (int i = 0; i < list.getLength(); i++) {
667: Node child = list.item(i);
668: if (child.getNodeType() == Node.TEXT_NODE) {
669: buf.append(child.getNodeValue());
670: } else {
671: buf.append(stringValue(child));
672: }
673: }
674: return buf.toString().trim();
675: }
676: }
677:
678: /**
679: * Locates a node by ID.
680: */
681: public Pointer getPointerByID(JXPathContext context, String id) {
682: Document document;
683: if (node.getNodeType() == Node.DOCUMENT_NODE) {
684: document = (Document) node;
685: } else {
686: document = node.getOwnerDocument();
687: }
688: Element element = document.getElementById(id);
689: if (element != null) {
690: return new DOMNodePointer(element, getLocale(), id);
691: } else {
692: return new NullPointer(getLocale(), id);
693: }
694: }
695:
696: private AbstractFactory getAbstractFactory(JXPathContext context) {
697: AbstractFactory factory = context.getFactory();
698: if (factory == null) {
699: throw new JXPathException(
700: "Factory is not set on the JXPathContext - "
701: + "cannot create path: " + asPath());
702: }
703: return factory;
704: }
705:
706: public int compareChildNodePointers(NodePointer pointer1,
707: NodePointer pointer2) {
708: Node node1 = (Node) pointer1.getBaseValue();
709: Node node2 = (Node) pointer2.getBaseValue();
710: if (node1 == node2) {
711: return 0;
712: }
713:
714: int t1 = node1.getNodeType();
715: int t2 = node2.getNodeType();
716: if (t1 == Node.ATTRIBUTE_NODE && t2 != Node.ATTRIBUTE_NODE) {
717: return -1;
718: } else if (t1 != Node.ATTRIBUTE_NODE
719: && t2 == Node.ATTRIBUTE_NODE) {
720: return 1;
721: } else if (t1 == Node.ATTRIBUTE_NODE
722: && t2 == Node.ATTRIBUTE_NODE) {
723: NamedNodeMap map = ((Node) getNode()).getAttributes();
724: int length = map.getLength();
725: for (int i = 0; i < length; i++) {
726: Node n = map.item(i);
727: if (n == node1) {
728: return -1;
729: } else if (n == node2) {
730: return 1;
731: }
732: }
733: return 0; // Should not happen
734: }
735:
736: Node current = node.getFirstChild();
737: while (current != null) {
738: if (current == node1) {
739: return -1;
740: } else if (current == node2) {
741: return 1;
742: }
743: current = current.getNextSibling();
744: }
745:
746: return 0;
747: }
748: }
|