    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 }