View Javadoc

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