1 /* Copyright 2005 Elliotte Rusty Harold
2 
3    This library is free software; you can redistribute it and/or modify
4    it under the terms of version 2.1 of the GNU Lesser General Public
5    License as published by the Free Software Foundation.
6 
7    This library is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU Lesser General Public License for more details.
11 
12    You should have received a copy of the GNU Lesser General Public
13    License along with this library; if not, write to the
14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
15    Boston, MA 02111-1307  USA
16 
17    You can contact Elliotte Rusty Harold by sending e-mail to
18    elharo@ibiblio.org. Please include the word "XOM" in the
19    subject line. The XOM home page is located at http://www.xom.nu/
20 */
21 
22 package nu.xom;
23 
24 /**
25  * <p>
26  * Interface between Jaxen and XOM.
27  * </p>
28  *
29  * @author Elliotte Rusty Harold
30  * @version 1.1.1b1
31  *
32  */
33 
34 import org.jaxen.DefaultNavigator;
35 import org.jaxen.FunctionCallException;
36 import org.jaxen.JaxenConstants;
37 import org.jaxen.JaxenException;
38 import org.jaxen.NamedAccessNavigator;
39 import org.jaxen.UnsupportedAxisException;
40 import org.jaxen.XPath;
41 import org.jaxen.util.SingleObjectIterator;
42 
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.ArrayList;
46 import java.util.Map;
47 import java.util.NoSuchElementException;
48 
49 
50 class JaxenNavigator extends DefaultNavigator implements NamedAccessNavigator {
51 
52 
53     private static final long serialVersionUID = 7008740797833836742L;
54 
55 
getSelfAxisIterator(Object contextNode)56     public Iterator getSelfAxisIterator(Object contextNode) {
57 
58         if (contextNode instanceof Text) {
59             // wrap text nodes in a list
60             Text node = (Text) contextNode;
61             ArrayList temp = new ArrayList();
62             ParentNode parent = node.getParent();
63             // parent is never null here due to DocumentFragment
64             int index = parent.indexOf(node);
65             int first = index;
66             int last = index;
67             while (first > 0 && parent.getChild(first-1).isText()) {
68                 first--;
69             }
70             while (last < parent.getChildCount()-1 && parent.getChild(last+1).isText()) {
71                 last++;
72             }
73             for (int i = first; i <= last; i++) {
74                 temp.add(parent.getChild(i));
75             }
76             contextNode = temp;
77         }
78         return new SingleObjectIterator(contextNode);
79 
80     }
81 
82 
getElementById(Object node, String id)83     public Object getElementById(Object node, String id) {
84 
85         Node original;
86         if (node instanceof ArrayList) {
87             original = (Node) ((List) node).get(0);
88         }
89         else {
90             original = (Node) node;
91         }
92         ParentNode parent;
93         if (original.isElement() || original.isDocument()) {
94             parent = (ParentNode) original;
95         }
96         else {
97             parent = original.getParent();
98         }
99 
100         // find highest parent node
101         ParentNode high = parent;
102         while (parent != null) {
103             high = parent;
104             parent = parent.getParent();
105         }
106 
107         // Now search down from the highest point for the requested ID
108         Element root;
109         if (high.isDocument()) {
110             root = ((Document) high).getRootElement();
111         }
112         else { // document fragment
113             Node first = high.getChild(0);
114             if (first.isElement()) {
115                 root = (Element) high.getChild(0);
116             }
117             else {
118                 return null;
119             }
120         }
121 
122         return findByID(root, id);
123 
124     }
125 
126 
127     // ????remove recursion
findByID(Element top, String id)128     public static Element findByID(Element top, String id) {
129 
130         if (hasID(top, id)) return top;
131         else {
132             Elements children = top.getChildElements();
133             for (int i = 0; i < children.size(); i++) {
134                 Element result = findByID(children.get(i), id);
135                 if (result != null) return result;
136             }
137         }
138         return null;
139 
140     }
141 
142 
hasID(Element top, String id)143     private static boolean hasID(Element top, String id) {
144 
145         int count = top.getAttributeCount();
146         for (int i = 0; i < count; i++) {
147             Attribute a = top.getAttribute(i);
148             if (Attribute.Type.ID == a.getType()) {
149                 // Do not need to fully normalize here
150                 // because if the value passed to the id() function
151                 // contains any spaces; then it is converted into a
152                 // search for multiple IDs, none of which have spaces
153                 return a.getValue().trim().equals(id);
154             }
155         }
156         return false;
157 
158     }
159 
160 
getNamespacePrefix(Object o)161     public String getNamespacePrefix(Object o) {
162         Namespace ns = (Namespace) o;
163         return ns.getPrefix();
164     }
165 
166 
getNamespaceStringValue(Object o)167     public String getNamespaceStringValue(Object o) {
168         Namespace ns = (Namespace) o;
169         return ns.getValue();
170     }
171 
172 
getNamespaceAxisIterator(Object contextNode)173     public Iterator getNamespaceAxisIterator(Object contextNode) {
174 
175         try {
176             Element element = (Element) contextNode;
177             // ???? can probably avoid this list copy
178             Map bindings = element.getNamespacePrefixesInScope();
179             Iterator iterator = bindings.entrySet().iterator();
180             List result = new ArrayList(bindings.size()+1);
181             result.add(new Namespace("xml",
182               "http://www.w3.org/XML/1998/namespace", element));
183 
184             while (iterator.hasNext()) {
185                 Map.Entry binding = (Map.Entry) iterator.next();
186                 String prefix = (String) binding.getKey();
187                 String uri = (String) binding.getValue();
188                 if (! "".equals(prefix) || ! "".equals(uri)) {
189                     Namespace ns = new Namespace(prefix, uri, element);
190                     result.add(ns);
191                 }
192             }
193             return result.iterator();
194         }
195         catch (ClassCastException ex) {
196             return JaxenConstants.EMPTY_ITERATOR;
197         }
198 
199     }
200 
201 
getParentAxisIterator(Object contextNode)202     public Iterator getParentAxisIterator(Object contextNode)  {
203 
204         Node parent = (Node) getParentNode(contextNode);
205         if (parent == null) return JaxenConstants.EMPTY_ITERATOR;
206         else return new SingleObjectIterator(parent);
207 
208     }
209 
210 
getDocumentNode(Object o)211     public Object getDocumentNode(Object o) {
212 
213         Node node = (Node) o;
214         return node.getRoot();
215 
216     }
217 
218 
getDocument(String url)219     public Object getDocument(String url) throws FunctionCallException {
220         throw new FunctionCallException("document() function not supported");
221     }
222 
getAttributeAxisIterator(Object contextNode)223     public Iterator getAttributeAxisIterator(Object contextNode) {
224 
225         try {
226             Element element = (Element) contextNode;
227             return element.attributeIterator();
228         }
229         catch (ClassCastException ex) {
230             return JaxenConstants.EMPTY_ITERATOR;
231         }
232 
233     }
234 
235 
getChildAxisIterator(Object o)236     public Iterator getChildAxisIterator(Object o) {
237 
238         if (o instanceof ParentNode) {
239             return new ChildIterator((ParentNode) o);
240         }
241         else {
242             return JaxenConstants.EMPTY_ITERATOR;
243         }
244 
245     }
246 
247 
getFollowingSiblingAxisIterator(Object o)248     public Iterator getFollowingSiblingAxisIterator(Object o) {
249 
250         Node start;
251         if (o instanceof ArrayList) {
252             List list = (ArrayList) o;
253             start = (Node) list.get(list.size()-1);
254         }
255         else {
256             start = (Node) o;
257         }
258         ParentNode parent = start.getParent();
259         if (parent == null) return JaxenConstants.EMPTY_ITERATOR;
260         int startPos = parent.indexOf(start) + 1;
261         return new ChildIterator(parent, startPos);
262 
263     }
264 
265 
getParentNode(Object o)266     public Object getParentNode(Object o) {
267 
268         Node n;
269         if (o instanceof ArrayList) {
270             n = (Node) ((List) o).get(0);
271         }
272         else {
273             n = (Node) o;
274         }
275         return n.getParent();
276 
277     }
278 
279 
getTextStringValue(Object o)280     public String getTextStringValue(Object o) {
281 
282         List texts = (List) o;
283         if (texts.size() == 1) {
284             return ((Text) texts.get(0)).getValue();
285         }
286         else {
287             StringBuffer result = new StringBuffer();
288             Iterator iterator = texts.iterator();
289             while (iterator.hasNext()) {
290                 Text text = (Text) iterator.next();
291                 result.append(text.getValue());
292             }
293             return result.toString();
294         }
295 
296     }
297 
298 
299     private static class ChildIterator implements Iterator {
300 
301         private final ParentNode parent;
302 
303         private int xomIndex = 0;
304         private final int xomCount;
305 
ChildIterator(ParentNode parent)306         ChildIterator(ParentNode parent) {
307             this.parent = parent;
308             this.xomCount = parent.getChildCount();
309         }
310 
311 
ChildIterator(ParentNode parent, int startNode)312         ChildIterator(ParentNode parent, int startNode) {
313             this.parent = parent;
314             this.xomIndex = startNode;
315             this.xomCount = parent.getChildCount();
316         }
317 
318 
hasNext()319         public boolean hasNext() {
320 
321             for (int i = xomIndex; i < xomCount; i++) {
322                 Node next = parent.getChild(i);
323                 if (next.isText()) {
324                     if (! ((Text) next).isEmpty()) {
325                         return true;
326                     }
327                 }
328                 else return true;
329             }
330             return false;
331 
332         }
333 
334 
next()335         public Object next() {
336 
337             Object result;
338             Node next = parent.getChild(xomIndex++);
339             if (next.isText()) {
340                 Text t = (Text) next;
341                 // Is this an empty text node?
342                 boolean empty = t.isEmpty();
343                 List texts = new ArrayList(1);
344                 texts.add(t);
345                 while (xomIndex < xomCount) {
346                     Node nextText = parent.getChild(xomIndex);
347                     if (! nextText.isText()) break;
348                     xomIndex++;
349                     texts.add(nextText);
350                     if (empty) {
351                         if (! ((Text) nextText).isEmpty()) {
352                             empty = false;
353                         }
354                     }
355                 }
356                 // need to make sure at least one of these texts is non-empty
357                 if (empty) return next();
358                 else result = texts;
359             }
360             else if (next.isDocType()) {
361                 return next();
362             }
363             else {
364                 result = next;
365             }
366             return result;
367 
368         }
369 
remove()370         public void remove() {
371             throw new UnsupportedOperationException();
372         }
373 
374     }
375 
376 
377     private static class NamedChildIterator implements Iterator {
378 
379         private final ParentNode parent;
380 
381         private int index = -1;
382         private final int xomCount;
383         private Element next;
384         private final String localName;
385         private final String URI;
386 
NamedChildIterator(ParentNode parent, String localName, String prefix, String namespaceURI)387         NamedChildIterator(ParentNode parent, String localName, String prefix, String namespaceURI) {
388             this.parent = parent;
389             this.xomCount = parent.getChildCount();
390             this.localName = localName;
391             if (namespaceURI == null) this.URI = "";
392             else this.URI = namespaceURI;
393 
394             findNext();
395         }
396 
findNext()397         private void findNext() {
398 
399             while (++index < xomCount) {
400                 Node next = parent.getChild(index);
401                 if (next.isElement()) {
402                     Element element = (Element) next;
403                     String elementNamespace = element.getNamespaceURI();
404                     if (elementNamespace.equals(URI)) {
405                         // I don't need to worry about the prefix here because XPath only iterates
406                         // by local name and namespace URI. The prefix doesn't matter.
407                         // This does assume that this class is non-public.
408                         if (element.getLocalName().equals(localName)) {
409                             this.next = element;
410                             return;
411                         }
412                     }
413                 }
414             }
415             next = null;
416         }
417 
hasNext()418         public boolean hasNext() {
419             return next != null;
420         }
421 
422 
next()423         public Object next() {
424 
425             if (next == null) throw new NoSuchElementException(); // Correct? Yes. Necessary????
426             Object result = next;
427             findNext();
428             return result;
429         }
430 
remove()431         public void remove() {
432             throw new UnsupportedOperationException();
433         }
434 
435     }
436 
437 
getElementNamespaceUri(Object element)438     public String getElementNamespaceUri(Object element) {
439         return ((Element) element).getNamespaceURI();
440     }
441 
442 
443     // In Jaxen, name means the local name only
getElementName(Object element)444     public String getElementName(Object element) {
445         return ((Element) element).getLocalName();
446     }
447 
getElementQName(Object element)448     public String getElementQName(Object element) {
449         return ((Element) element).getQualifiedName();
450     }
451 
452 
getAttributeNamespaceUri(Object attr)453     public String getAttributeNamespaceUri(Object attr) {
454         Attribute attribute = (Attribute) attr;
455         return attribute.getNamespaceURI();
456     }
457 
458 
459     // In Jaxen, name means the local name only
getAttributeName(Object attr)460     public String getAttributeName(Object attr) {
461         Attribute attribute = (Attribute) attr;
462         return attribute.getLocalName();
463     }
464 
465 
getAttributeQName(Object attr)466     public String getAttributeQName(Object attr) {
467         Attribute attribute = (Attribute) attr;
468         return attribute.getQualifiedName();
469     }
470 
getProcessingInstructionData(Object o)471     public String getProcessingInstructionData(Object o) {
472         ProcessingInstruction pi = (ProcessingInstruction) o;
473         return pi.getValue();
474     }
475 
476 
getProcessingInstructionTarget(Object o)477     public String getProcessingInstructionTarget(Object o) {
478         ProcessingInstruction pi = (ProcessingInstruction) o;
479         return pi.getTarget();
480     }
481 
482 
isDocument(Object object)483     public boolean isDocument(Object object) {
484         return object instanceof Document || object instanceof DocumentFragment;
485     }
486 
487 
isElement(Object object)488     public boolean isElement(Object object) {
489         return object instanceof Element;
490     }
491 
492 
isAttribute(Object object)493     public boolean isAttribute(Object object) {
494         return object instanceof Attribute;
495     }
496 
497 
isNamespace(Object object)498     public boolean isNamespace(Object object) {
499         return object instanceof Namespace;
500     }
501 
502 
isComment(Object object)503     public boolean isComment(Object object) {
504         return object instanceof Comment;
505     }
506 
507 
isText(Object object)508     public boolean isText(Object object) {
509         // ???? hack: need to use a separate special subclass of ArrayList I can identify.
510         // But may not be necessary since this is not a public API. I don't
511         // think there's any way to get a different ArrayList into this method.
512         if (object instanceof ArrayList) {
513             Iterator iterator = ((List) object).iterator();
514             while (iterator.hasNext()) {
515                 if (! (iterator.next() instanceof Text)) return false;
516             }
517             return true;
518         }
519         return false;
520     }
521 
522 
isProcessingInstruction(Object object)523     public boolean isProcessingInstruction(Object object) {
524         return object instanceof ProcessingInstruction;
525     }
526 
527 
getCommentStringValue(Object comment)528     public String getCommentStringValue(Object comment) {
529         return ((Comment) comment).getValue();
530     }
531 
532 
getElementStringValue(Object element)533     public String getElementStringValue(Object element) {
534         return ((Element) element).getValue();
535     }
536 
537 
getAttributeStringValue(Object attribute)538     public String getAttributeStringValue(Object attribute) {
539         return ((Attribute) attribute).getValue();
540     }
541 
542 
parseXPath(String expression)543     public XPath parseXPath(String expression) throws JaxenException {
544         return new JaxenConnector(expression);
545     }
546 
547 
getChildAxisIterator(Object parent, String localName, String namespacePrefix, String namespaceURI)548     public Iterator getChildAxisIterator(Object parent, String localName, String namespacePrefix, String namespaceURI)
549       throws UnsupportedAxisException {
550 
551         if (parent instanceof ParentNode) {
552             return new NamedChildIterator((ParentNode) parent, localName, namespacePrefix, namespaceURI);
553         }
554         return JaxenConstants.EMPTY_ITERATOR;
555 
556     }
557 
558 
getAttributeAxisIterator(Object contextNode, String localName, String namespacePrefix, String namespaceURI)559     public Iterator getAttributeAxisIterator(Object contextNode, String localName, String namespacePrefix, String namespaceURI)
560       throws UnsupportedAxisException {
561 
562         // I don't need to worry about the prefix here because XPath only iterates
563         // by local name and namespace URI. The prefix doesn't matter.
564         // This does assume that this class is non-public.
565         try {
566             Element element = (Element) contextNode;
567             Attribute result = null;
568             if (namespaceURI == null) {
569                 result = element.getAttribute(localName);
570             }
571             else {
572                 result = element.getAttribute(localName, namespaceURI);
573             }
574 
575             if (result == null) return JaxenConstants.EMPTY_ITERATOR;
576 
577             return new SingleObjectIterator(result);
578         }
579         catch (ClassCastException ex) {
580             return JaxenConstants.EMPTY_ITERATOR;
581         }
582 
583     }
584 
585 
586 }
587