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;
17  
18  import org.apache.commons.jxpath.AbstractFactory;
19  import org.apache.commons.jxpath.IdentityManager;
20  import org.apache.commons.jxpath.JXPathContext;
21  import org.apache.commons.jxpath.JXPathException;
22  import org.apache.commons.jxpath.JXPathTestCase;
23  import org.apache.commons.jxpath.Pointer;
24  import org.apache.commons.jxpath.Variables;
25  import org.apache.commons.jxpath.xml.DocumentContainer;
26  
27  /***
28   * Abstract superclass for pure XPath 1.0.  Subclasses
29   * apply the same XPaths to contexts using different models:
30   * DOM, JDOM etc.
31   *
32   * @author Dmitri Plotnikov
33   * @version $Revision: 1.23 $ $Date: 2004/06/30 00:29:13 $
34   */
35  
36  public abstract class XMLModelTestCase extends JXPathTestCase {
37      protected JXPathContext context;
38  
39      /***
40       * Construct a new instance of this test case.
41       *
42       * @param name Name of the test case
43       */
44      public XMLModelTestCase(String name) {
45          super(name);
46      }
47  
48      public void setUp() {
49          if (context == null) {
50              DocumentContainer docCtr = createDocumentContainer();
51              context = createContext();
52              Variables vars = context.getVariables();
53              vars.declareVariable("document", docCtr.getValue());
54              vars.declareVariable("container", docCtr);
55              vars.declareVariable(
56                  "element",
57                  context.getPointer("vendor/location/address/street").getNode());
58          }
59      }
60  
61      protected abstract String getModel();
62  
63      protected DocumentContainer createDocumentContainer() {
64          return new DocumentContainer(
65                  JXPathTestCase.class.getResource("Vendor.xml"),
66                  getModel());
67      }
68      
69      protected abstract AbstractFactory getAbstractFactory();
70          
71      protected JXPathContext createContext() {
72          JXPathContext context =
73              JXPathContext.newContext(createDocumentContainer());
74          context.setFactory(getAbstractFactory());
75          context.registerNamespace("product", "productNS");
76          return context;
77      }
78  
79      /***
80       * An XML signature is used to determine if we have the right result
81       * after a modification of XML by JXPath.  It is basically a piece
82       * of simplified XML.
83       */
84      protected abstract String getXMLSignature(
85          Object node,
86          boolean elements,
87          boolean attributes,
88          boolean text,
89          boolean pi);
90  
91      protected void assertXMLSignature(
92          JXPathContext context,
93          String path,
94          String signature,
95          boolean elements,
96          boolean attributes,
97          boolean text,
98          boolean pi) 
99      {
100         Object node = context.getPointer(path).getNode();
101         String sig = getXMLSignature(node, elements, attributes, text, pi);
102         assertEquals("XML Signature mismatch: ", signature, sig);
103     }
104 
105     // ------------------------------------------------ Individual Test Methods
106 
107     public void testDocumentOrder() {
108         assertDocumentOrder(
109             context,
110             "vendor/location",
111             "vendor/location/address/street",
112             -1);
113 
114         assertDocumentOrder(
115             context,
116             "vendor/location[@id = '100']",
117             "vendor/location[@id = '101']",
118             -1);
119 
120         assertDocumentOrder(
121             context,
122             "vendor//price:amount",
123             "vendor/location",
124             1);
125     }
126     
127 
128     public void testSetValue() {
129         assertXPathSetValue(
130             context,
131             "vendor/location[@id = '100']",
132             "New Text");
133 
134         assertXMLSignature(
135             context,
136             "vendor/location[@id = '100']",
137             "<E>New Text</E>",
138             false,
139             false,
140             true,
141             false);
142 
143         assertXPathSetValue(
144             context,
145             "vendor/location[@id = '101']",
146             "Replacement Text");
147 
148         assertXMLSignature(
149             context,
150             "vendor/location[@id = '101']",
151             "<E>Replacement Text</E>",
152             false,
153             false,
154             true,
155             false);
156     }
157 
158     /***
159      * Test JXPathContext.createPath() with various arguments
160      */
161     public void testCreatePath() {
162         // Create a DOM element
163         assertXPathCreatePath(
164             context,
165             "/vendor[1]/location[3]",
166             "",
167             "/vendor[1]/location[3]");
168 
169         // Create a DOM element with contents
170         assertXPathCreatePath(
171             context,
172             "/vendor[1]/location[3]/address/street",
173             "",
174             "/vendor[1]/location[3]/address[1]/street[1]");
175 
176         // Create a DOM attribute
177         assertXPathCreatePath(
178             context,
179             "/vendor[1]/location[2]/@manager",
180             "",
181             "/vendor[1]/location[2]/@manager");
182 
183         assertXPathCreatePath(
184             context,
185             "/vendor[1]/location[1]/@name",
186             "local",
187             "/vendor[1]/location[1]/@name");
188 
189          assertXPathCreatePathAndSetValue(
190             context,
191             "/vendor[1]/location[4]/@manager",
192             "",
193             "/vendor[1]/location[4]/@manager");
194          
195          context.registerNamespace("price", "priceNS");
196          
197          // Create a DOM element
198          assertXPathCreatePath(
199              context,
200              "/vendor[1]/price:foo/price:bar",
201              "",
202              "/vendor[1]/price:foo[1]/price:bar[1]");
203     }
204 
205     /***
206      * Test JXPath.createPathAndSetValue() with various arguments
207      */
208     public void testCreatePathAndSetValue() {
209         // Create a XML element
210         assertXPathCreatePathAndSetValue(
211             context,
212             "vendor/location[3]",
213             "",
214             "/vendor[1]/location[3]");
215 
216         // Create a DOM element with contents
217         assertXPathCreatePathAndSetValue(
218             context,
219             "vendor/location[3]/address/street",
220             "Lemon Circle",
221             "/vendor[1]/location[3]/address[1]/street[1]");
222 
223         // Create an attribute
224         assertXPathCreatePathAndSetValue(
225             context,
226             "vendor/location[2]/@manager",
227             "John Doe",
228             "/vendor[1]/location[2]/@manager");
229 
230         assertXPathCreatePathAndSetValue(
231             context,
232             "vendor/location[1]/@manager",
233             "John Doe",
234             "/vendor[1]/location[1]/@manager");
235         
236         assertXPathCreatePathAndSetValue(
237             context,
238             "/vendor[1]/location[4]/@manager",
239             "James Dow",
240             "/vendor[1]/location[4]/@manager");
241         
242         assertXPathCreatePathAndSetValue(
243             context,
244             "vendor/product/product:name/attribute::price:language",
245             "English",
246             "/vendor[1]/product[1]/product:name[1]/@price:language");
247         
248         context.registerNamespace("price", "priceNS");
249         
250         // Create a DOM element
251         assertXPathCreatePathAndSetValue(
252             context,
253             "/vendor[1]/price:foo/price:bar",
254             "123.20",
255             "/vendor[1]/price:foo[1]/price:bar[1]");
256     }
257 
258     /***
259      * Test JXPathContext.removePath() with various arguments
260      */
261     public void testRemovePath() {
262         // Remove XML nodes
263         context.removePath("vendor/location[@id = '101']//street/text()");
264         assertEquals(
265             "Remove DOM text",
266             "",
267             context.getValue("vendor/location[@id = '101']//street"));
268 
269         context.removePath("vendor/location[@id = '101']//street");
270         assertEquals(
271             "Remove DOM element",
272             new Double(0),
273             context.getValue("count(vendor/location[@id = '101']//street)"));
274 
275         context.removePath("vendor/location[@id = '100']/@name");
276         assertEquals(
277             "Remove DOM attribute",
278             new Double(0),
279             context.getValue("count(vendor/location[@id = '100']/@name)"));
280     }
281 
282     public void testID() {
283         context.setIdentityManager(new IdentityManager() {
284             public Pointer getPointerByID(JXPathContext context, String id) {
285                 NodePointer ptr = (NodePointer) context.getPointer("/");
286                 ptr = ptr.getValuePointer(); // Unwrap the container
287                 return ptr.getPointerByID(context, id);
288             }
289         });
290 
291         assertXPathValueAndPointer(
292             context,
293             "id(101)//street",
294             "Tangerine Drive",
295             "id('101')/address[1]/street[1]");
296 
297         assertXPathPointerLenient(
298             context,
299             "id(105)/address/street",
300             "id(105)/address/street");
301     }
302 
303     public void testAxisChild() {
304         assertXPathValue(
305             context,
306             "vendor/location/address/street",
307             "Orchard Road");
308 
309         // child:: - first child does not match, need to search
310         assertXPathValue(
311             context,
312             "vendor/location/address/city",
313             "Fruit Market");
314         
315         // local-name(qualified)
316         assertXPathValue(
317             context,
318             "local-name(vendor/product/price:amount)",
319             "amount");
320         
321         // local-name(non-qualified)
322         assertXPathValue(context, "local-name(vendor/location)", "location");
323 
324         // name (qualified)
325         assertXPathValue(
326             context,
327             "name(vendor/product/price:amount)",
328             "value:amount");
329 
330         // name (non-qualified)
331         assertXPathValue(
332             context,
333             "name(vendor/location)",
334             "location");
335 
336         // namespace-uri (qualified)
337         assertXPathValue(
338             context,
339             "namespace-uri(vendor/product/price:amount)",
340             "priceNS");
341 
342         // default namespace does not affect search
343         assertXPathValue(context, "vendor/product/prix", "934.99");
344         
345         assertXPathValue(context, "/vendor/contact[@name='jim']", "Jim");
346         
347         boolean nsv = false;
348         try {
349             context.setLenient(false);
350             context.getValue("/vendor/contact[@name='jane']");
351         }
352         catch (JXPathException ex) {
353             nsv = true;
354         }
355         assertTrue("No such value: /vendor/contact[@name='jim']", nsv);
356                 
357         nsv = false;
358         try {
359             context.setLenient(false);
360             context.getValue("/vendor/contact[@name='jane']/*");
361         }
362         catch (JXPathException ex) {
363             nsv = true;
364         }
365         assertTrue("No such value: /vendor/contact[@name='jane']/*", nsv);
366         
367         // child:: with a wildcard
368         assertXPathValue(
369             context,
370             "count(vendor/product/price:*)",
371             new Double(2));
372 
373         // child:: with the default namespace
374         assertXPathValue(context, "count(vendor/product/*)", new Double(4));
375 
376         // child:: with a qualified name
377         assertXPathValue(context, "vendor/product/price:amount", "45.95");
378     }
379 
380     public void testAxisChildIndexPredicate() {
381         assertXPathValue(
382             context,
383             "vendor/location[2]/address/street",
384             "Tangerine Drive");
385     }
386 
387     public void testAxisDescendant() {
388         // descendant::
389         assertXPathValue(context, "//street", "Orchard Road");
390 
391         // descendent:: with a namespace and wildcard
392         assertXPathValue(context, "count(//price:*)", new Double(2));
393 
394         assertXPathValueIterator(context, "vendor//saleEnds", list("never"));
395 
396         assertXPathValueIterator(context, "vendor//promotion", list(""));
397 
398         assertXPathValueIterator(
399             context,
400             "vendor//saleEnds[../@stores = 'all']",
401             list("never"));
402 
403         assertXPathValueIterator(
404             context,
405             "vendor//promotion[../@stores = 'all']",
406             list(""));
407     }
408     
409 //    public void testAxisDescendantDocumentOrder() {
410 //        Iterator iter = context.iteratePointers("//*");
411 //        while (iter.hasNext()) {
412 //            System.err.println(iter.next());
413 //        }
414 //    }
415 
416     public void testAxisParent() {
417         // parent::
418         assertXPathPointer(
419             context,
420             "//street/..",
421             "/vendor[1]/location[1]/address[1]");
422 
423         // parent:: (note reverse document order)
424         assertXPathPointerIterator(
425             context,
426             "//street/..",
427             list(
428                 "/vendor[1]/location[2]/address[1]",
429                 "/vendor[1]/location[1]/address[1]"));
430 
431         // parent:: with a namespace and wildcard
432         assertXPathValue(
433             context,
434             "vendor/product/price:sale/saleEnds/parent::price:*" + "/saleEnds",
435             "never");
436     }
437 
438     public void testAxisFollowingSibling() {
439         // following-sibling::
440         assertXPathValue(
441             context,
442             "vendor/location[.//employeeCount = 10]/"
443                 + "following-sibling::location//street",
444             "Tangerine Drive");
445 
446         // following-sibling:: produces the correct pointer
447         assertXPathPointer(
448             context,
449             "vendor/location[.//employeeCount = 10]/"
450                 + "following-sibling::location//street",
451             "/vendor[1]/location[2]/address[1]/street[1]");
452     }
453 
454     public void testAxisPrecedingSibling() {
455         // preceding-sibling:: produces the correct pointer
456         assertXPathPointer(
457             context,
458             "//location[2]/preceding-sibling::location//street",
459             "/vendor[1]/location[1]/address[1]/street[1]");
460     }
461 
462     public void testAxisAttribute() {
463         // attribute::
464         assertXPathValue(context, "vendor/location/@id", "100");
465 
466         // attribute:: produces the correct pointer
467         assertXPathPointer(
468             context,
469             "vendor/location/@id",
470             "/vendor[1]/location[1]/@id");
471 
472         // iterate over attributes
473         assertXPathValueIterator(
474             context,
475             "vendor/location/@id",
476             list("100", "101"));
477 
478         // Using different prefixes for the same namespace
479         assertXPathValue(
480             context,
481             "vendor/product/price:amount/@price:discount",
482             "10%");
483         
484         // namespace uri for an attribute
485         assertXPathValue(
486             context,
487             "namespace-uri(vendor/product/price:amount/@price:discount)",
488             "priceNS");
489 
490         // local name of an attribute
491         assertXPathValue(
492             context,
493             "local-name(vendor/product/price:amount/@price:discount)",
494             "discount");
495 
496         // name for an attribute
497         assertXPathValue(
498             context,
499             "name(vendor/product/price:amount/@price:discount)",
500             "price:discount");
501 
502         // attribute:: with the default namespace
503         assertXPathValue(
504             context,
505             "vendor/product/price:amount/@discount",
506             "20%");
507 
508         // namespace uri of an attribute with the default namespace
509         assertXPathValue(
510             context,
511             "namespace-uri(vendor/product/price:amount/@discount)",
512             "");
513 
514         // local name of an attribute with the default namespace
515         assertXPathValue(
516             context,
517             "local-name(vendor/product/price:amount/@discount)",
518             "discount");
519 
520         // name of an attribute with the default namespace
521         assertXPathValue(
522             context,
523             "name(vendor/product/price:amount/@discount)",
524             "discount");
525 
526         // attribute:: with a namespace and wildcard
527         assertXPathValueIterator(
528             context,
529             "vendor/product/price:amount/@price:*",
530             list("10%"));
531 
532         // attribute:: with a wildcard
533         assertXPathValueIterator(
534             context,
535             "vendor/location[1]/@*",
536             set("100", "", "local"));
537 
538         // attribute:: with default namespace and wildcard
539         assertXPathValueIterator(
540             context,
541             "vendor/product/price:amount/@*",
542             list("20%"));
543 
544         // Empty attribute
545         assertXPathValue(context, "vendor/location/@manager", "");
546 
547         // Missing attribute
548         assertXPathValueLenient(context, "vendor/location/@missing", null);
549 
550         // Missing attribute with namespace
551         assertXPathValueLenient(context, "vendor/location/@miss:missing", null);
552 
553         // Using attribute in a predicate
554         assertXPathValue(
555             context,
556             "vendor/location[@id='101']//street",
557             "Tangerine Drive");
558         
559         assertXPathValueIterator(
560             context,
561             "/vendor/location[1]/@*[name()!= 'manager']", list("100",
562             "local"));
563     }
564 
565     public void testAxisNamespace() {
566         // namespace::
567         assertXPathValueAndPointer(
568             context,
569             "vendor/product/prix/namespace::price",
570             "priceNS",
571             "/vendor[1]/product[1]/prix[1]/namespace::price");
572 
573         // namespace::*
574         assertXPathValue(
575             context,
576             "count(vendor/product/namespace::*)",
577             new Double(3));
578 
579         // name of namespace
580         assertXPathValue(
581             context,
582             "name(vendor/product/prix/namespace::price)",
583             "price");
584 
585         // local name of namespace
586         assertXPathValue(
587             context,
588             "local-name(vendor/product/prix/namespace::price)",
589             "price");
590     }
591 
592     public void testAxisAncestor() {
593         // ancestor::
594         assertXPathValue(
595             context,
596             "vendor/product/price:sale/saleEnds/"
597                 + "ancestor::price:sale/saleEnds",
598             "never");
599 
600         // ancestor:: with a wildcard
601         assertXPathValue(
602             context,
603             "vendor/product/price:sale/saleEnds/ancestor::price:*"
604                 + "/saleEnds",
605             "never");
606     }
607 
608     public void testAxisAncestorOrSelf() {
609         // ancestor-or-self::
610         assertXPathValue(
611             context,
612             "vendor/product/price:sale/"
613                 + "ancestor-or-self::price:sale/saleEnds",
614             "never");
615     }
616 
617     public void testAxisFollowing() {
618         assertXPathValueIterator(
619             context,
620             "vendor/contact/following::location//street",
621             list("Orchard Road", "Tangerine Drive"));
622 
623         // following:: with a namespace
624         assertXPathValue(
625             context,
626             "//location/following::price:sale/saleEnds",
627             "never");
628     }
629 
630     public void testAxisSelf() {
631         // self:: with a namespace
632         assertXPathValue(
633             context,
634             "//price:sale/self::price:sale/saleEnds",
635             "never");
636 
637         // self:: with an unmatching name
638         assertXPathValueLenient(context, "//price:sale/self::x/saleEnds", null);
639     }
640 
641     public void testNodeTypeComment() {
642         // comment()
643         assertXPathValue(
644             context,
645             "//product/comment()",
646             "We are not buying this product, ever");
647     }
648 
649     public void testNodeTypeText() {
650         // text()
651         assertXPathValue(
652             context,
653             "//product/text()[. != '']",
654             "We love this product.");
655 
656         // text() pointer
657         assertXPathPointer(
658             context,
659             "//product/text()",
660             "/vendor[1]/product[1]/text()[1]");
661 
662     }
663 
664     public void testNodeTypeProcessingInstruction() {
665         // processing-instruction() without an argument
666         assertXPathValue(
667             context,
668             "//product/processing-instruction()",
669             "do not show anybody");
670 
671         // processing-instruction() with an argument
672         assertXPathValue(
673             context,
674             "//product/processing-instruction('report')",
675             "average only");
676 
677         // processing-instruction() pointer without an argument
678         assertXPathPointer(
679             context,
680             "//product/processing-instruction('report')",
681             "/vendor[1]/product[1]/processing-instruction('report')[1]");
682 
683         // processing-instruction name
684         assertXPathValue(
685             context,
686             "name(//product/processing-instruction()[1])",
687             "security");
688     }
689 
690     public void testLang() {
691         // xml:lang built-in attribute
692         assertXPathValue(context, "//product/prix/@xml:lang", "fr");
693 
694         // lang() used the built-in xml:lang attribute
695         assertXPathValue(context, "//product/prix[lang('fr')]", "934.99");
696 
697         // Default language
698         assertXPathValue(
699             context,
700             "//product/price:sale[lang('en')]/saleEnds",
701             "never");
702     }
703 
704     public void testDocument() {
705         assertXPathValue(
706             context,
707             "$document/vendor/location[1]//street",
708             "Orchard Road");
709 
710         assertXPathPointer(
711             context,
712             "$document/vendor/location[1]//street",
713             "$document/vendor[1]/location[1]/address[1]/street[1]");
714 
715         assertXPathValue(context, "$document/vendor//street", "Orchard Road");
716     }
717 
718     public void testContainer() {
719         assertXPathValue(context, "$container/vendor//street", "Orchard Road");
720 
721         assertXPathValue(context, "$container//street", "Orchard Road");
722 
723         assertXPathPointer(
724             context,
725             "$container//street",
726             "$container/vendor[1]/location[1]/address[1]/street[1]");
727 
728         // Conversion to number
729         assertXPathValue(
730             context,
731             "number(vendor/location/employeeCount)",
732             new Double(10));
733     }
734 
735     public void testElementInVariable() {
736         assertXPathValue(context, "$element", "Orchard Road");
737     }
738 
739     public void testTypeConversions() {
740         // Implicit conversion to number
741         assertXPathValue(
742             context,
743             "vendor/location/employeeCount + 1",
744             new Double(11));
745 
746         // Implicit conversion to boolean
747         assertXPathValue(
748             context,
749             "vendor/location/employeeCount and true()",
750             Boolean.TRUE);
751     }
752 
753     public void testBooleanFunction() {
754         assertXPathValue(
755             context,
756             "boolean(vendor//saleEnds[../@stores = 'all'])",
757             Boolean.TRUE);
758 
759         assertXPathValue(
760             context,
761             "boolean(vendor//promotion[../@stores = 'all'])",
762             Boolean.TRUE);
763 
764         assertXPathValue(
765             context,
766             "boolean(vendor//promotion[../@stores = 'some'])",
767             Boolean.FALSE);
768     }
769     
770     public void testFunctionsLastAndPosition() {
771         assertXPathPointer(
772                 context,
773                 "vendor//location[last()]",
774                 "/vendor[1]/location[2]");
775     }
776 
777     public void testNamespaceMapping() {
778         context.registerNamespace("rate", "priceNS");
779         context.registerNamespace("goods", "productNS");
780 
781         assertEquals("Context node namespace resolution", 
782                 "priceNS", 
783                 context.getNamespaceURI("price"));        
784         
785         assertEquals("Registered namespace resolution", 
786                 "priceNS", 
787                 context.getNamespaceURI("rate"));
788 
789         // child:: with a namespace and wildcard
790         assertXPathValue(context, 
791                 "count(vendor/product/rate:*)", 
792                 new Double(2));
793 
794         // Preference for externally registered namespace prefix
795         assertXPathValueAndPointer(context,
796                 "//product:name",
797                 "Box of oranges",
798                 "/vendor[1]/product[1]/goods:name[1]");
799     }
800 }