blob: e37a2bfbbd2e0fed35cbedb9fb0366ec460fd36a [file]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xmlcursor.xpath.common;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static xmlcursor.common.BasicCursorTestCase.*;
/**
* Verifies XPath using functions
* http://www.w3schools.com/xpath/xpath_functions.asp
*/
public class XPathFunctionTest implements XPathTestBase {
// Node Set Functions
// http://www.w3.org/TR/xpath#section-Node-Set-Functions
private static String getQuery(String testName, int testCase) throws IllegalArgumentException {
switch (testName) {
case "testFunctionCount":
return new String[]{
"count(//cd)",
"//cd[position()=2]"}[testCase];
case "testFunctionLocalName":
return "//*[local-name(.)='bar']";
case "testFunctionConcat":
return "//bar/*[name(.)=concat(\"pr\",\"ice\")]";
case "testFunctionString":
return new String[]{
"/foo/*[name(.)=" +
"concat(\"bar\",string(./foo/bar/price[last()]))]"}[testCase];
case "testFunctionStringLength":
return "//bar/*[string-length(name(.))=5]";
case "testFunctionSubString":
return "//bar/*[substring(name(.),3,3)=\"ice\"]";
case "testFunctionSubStringAfter":
return "//bar/*[substring-after(name(.),'pr')=\"ice\"]";
case "testFunctionSubStringBefore":
return "//bar/*[substring-before(name(.),'ice')=\"pr\"]";
case "testFunctionTranslate":
return "//bar/*[translate(name(.),'ice','pr')=\"prpr\"]";
case "testFunctionLang":
return new String[]{
"//price[lang(\"en\")=true()]",
"//foo[lang(\"en\")=true()]"}[testCase];
case "testFunctionTrue":
return "//*[boolean(@at)=true()]";
default:
return XPathTestBase.getQuery(testName, testCase);
}
}
/**
* count()
* Returns the number of nodes in a node-set
* number=count(node-set)
*/
@Test
void testFunctionCount() throws Exception {
String ex0Simple = getQuery("testFunctionCount", 0);
String ex0Simple1 = getQuery("testFunctionCount", 1);
System.out.println("Test 0: " + ex0Simple);
try (XmlCursor x0 = cur("<foo><cd>1</cd><cd>2</cd></foo>");
XmlCursor x01 = x0.newCursor()) {
/* XmlCursor countCheck = x0.newCursor();
countCheck.selectPath("count(.//cd)");
countCheck.toNextSelection();
System.out.println(" Global count "+countCheck.xmlText());
*/
String sExpectedResult = "<cd>2</cd>";
x01.selectPath(ex0Simple1);
assertEquals(1, x01.getSelectionCount());
x01.toNextSelection();
assertEquals(sExpectedResult, x01.xmlText());
sExpectedResult = "<xml-fragment>2</xml-fragment>";
x0.selectPath(ex0Simple);
XPathCommon.display(x0);
assertEquals(1, x0.getSelectionCount());
x0.toNextSelection();
//XPathCommon.compare(x0, new XmlObject[]{XmlObject.Factory.parse("<a>foo</a>")});
assertEquals(sExpectedResult, x0.xmlText());
}
}
/**
* id()
* Selects elements by their unique ID
* node-set=id(value)
*/
@Test
void testFunctionId() throws Exception {
XmlObject xDoc = jobj("xbean/xmlcursor/xpath/cdcatalog.xml");
String ex1Simple = getQuery("testFunctionId", 0); //"id(\"bobdylan\")"
String ex1R1 =
"<cd id=\"bobdylan\">" +
"<title>Empire Burlesque</title>" +
"<artist>Bob Dylan</artist><country>USA</country>" +
"<company>Columbia</company><price>10.90</price>" +
"<year>1985</year></cd>";
XmlObject[] exXml1 = {obj(ex1R1)};
//"id(\"foobar\")"
String ex2Simple = getQuery("testFunctionId", 1);
//"//child::cd[position()=3]"
String ex3Simple = getQuery("testFunctionId", 2);
String ex3R1 = "<cd id=\"id3\"><title>Greatest Hits</title><artist>Dolly Parton</artist><country>USA</country><company>RCA</company><price>9.90</price><year>1982</year></cd>";
XmlObject[] exXml3 = new XmlObject[]{XmlObject.Factory.parse(ex3R1)};
try (XmlCursor x1 = xDoc.newCursor()) {
x1.toChild("catalog");
x1.selectPath(ex1Simple);
//XPathCommon.display(x1);
//assertEquals(1,x1.getSelectionCount());
XPathCommon.compare(x1, exXml1);
//assertEquals(ex1R1, x1.xmlText());
}
try (XmlCursor x2 = xDoc.newCursor()) {
x2.selectPath(ex2Simple);
XPathCommon.display(x2);
assertFalse(x2.toNextSelection());
}
try (XmlCursor x3 = xDoc.newCursor()) {
x3.selectPath(ex3Simple);
XPathCommon.display(x3);
XPathCommon.compare(x3, exXml3);
}
}
/**
* last()
* Returns the position number of the last node in the processed node list
* number=last()
*/
@Test
void testFunctionLast() throws Exception {
String ex1Simple = getQuery("testFunctionLast", 0);
String ex1R1 =
"<cd>" +
"<title>Unchain my heart</title>" +
"<artist>Joe Cocker</artist><country>USA</country>" +
"<company>EMI</company><price>8.20</price>" +
"<year>1987</year></cd>";
XmlObject xDoc = jobj("xbean/xmlcursor/xpath/cdcatalog.xml");
XmlObject[] exXml1 = {obj(ex1R1)};
try (XmlCursor x1 = xDoc.newCursor()) {
x1.selectPath(ex1Simple);
XPathCommon.display(x1);
XPathCommon.compare(x1, exXml1);
}
}
/**
* local-name()
* Returns the local part of a node. A node usually consists of a prefix, a colon, followed by the local name
* string=local-name(node)
*/
@Test
void testFunctionLocalName() throws Exception {
String sXPath = getQuery("testFunctionLocalName", 0);
String sXml =
"<foo xmlns:pre=\"uri.org\">" +
"<pre:bar><price at=\"val0\">3.00</price>" +
"<price at=\"val1\">2</price></pre:bar><bar1>3.00</bar1></foo>";
String sExpected =
"<pre:bar xmlns:pre=\"uri.org\"><price at=\"val0\">3.00</price>" +
"<price at=\"val1\">2</price></pre:bar>";
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
m_xc.toNextSelection();
assertEquals(sExpected, m_xc.xmlText());
}
}
// /**
// * name()
// * Returns the name of a node
// * string=name(node)
// */
// public void testFunctionName() throws Exception {
//
// }
/**
* namespace-uri()
* Returns the namespace URI of a specified node
* uri=namespace-uri(node)
*/
@Test
void testFunctionNamespaceURI() throws Exception {
String sXPath = getQuery("testFunctionNamespaceURI", 0);
String sXml =
"<foo xmlns:pre=\"uri.org\">" +
"<pre:bar><price at=\"val0\">3.00</price>" +
"<price at=\"val1\">2</price></pre:bar><bar1>3.00</bar1></foo>";
String sExpected =
"<pre:bar xmlns:pre=\"uri.org\">" +
"<price at=\"val0\">3.00</price>" +
"<price at=\"val1\">2</price></pre:bar>";
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
m_xc.toNextSelection();
assertEquals(sExpected, m_xc.xmlText());
}
}
// /**
// * position()
// * Returns the position in the node list of the node that is currently being processed
// * number=position()
// */
// public void testFunctionPosition() throws Exception {
//
// }
// String Functions
// http://www.w3.org/TR/xpath#section-String-Functions
/**
* concat()
* Returns the concatenation of all its arguments string=concat(val1, val2, ..)
* Example: concat('The',' ','XML') Result: 'The XML'
*/
@Test
void testFunctionConcat() throws Exception {
String sXPath = getQuery("testFunctionConcat", 0);
String sXml =
"<foo><bar><price at=\"val0\">3.00</price>" +
"<price at=\"val1\">2</price></bar><bar1>3.00</bar1></foo>";
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(2, m_xc.getSelectionCount());
}
}
/**
* string()
* Converts the value argument to a string
* string(value)
* Example: string(314) Result: '314'
*/
@Test
void testFunctionString() throws Exception {
String sXPath = getQuery("testFunctionString", 0);
String sXml =
"<foo><bar><price at=\"val0\">3.00</price>" +
"<price at=\"val1\">1</price></bar><bar1>3.00</bar1></foo>";
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(1, m_xc.getSelectionCount());
}
/* m_xc.clearSelections();
sXPath="xf:concat(xf:string(//foo/bar/price[1]),xf:string(//foo/bar/price[last()]))";
String sExpected="03.00";
m_xc.selectPath(sXPath);
m_xc.toNextSelection();
assertEquals(sExpected,m_xc.xmlText());
*/
}
/**
* string-length()
* Returns the number of characters in a string
* number=string-length(string)
* Example: string-length('Beatles') Result: 7
*/
@Test
void testFunctionStringLength() throws Exception {
String sXPath = getQuery("testFunctionStringLength", 0);
String sXml =
"<foo><bar>" +
"<price at=\"val0\">3.00</price><price at=\"val1\">2</price></bar>" +
"<bar1>3.00</bar1></foo>";
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(2, m_xc.getSelectionCount());
m_xc.clearSelections();
}
}
/**
* substring()
* Returns a part of the string in the string argument
* string=substring(string,start,length)
* Example: substring('Beatles',1,4) Result: 'Beat'
*/
@Test
void testFunctionSubString() throws Exception {
String sXml = "<foo><bar><price at=\"val0\">3.00</price><price at=\"val1\">2</price></bar><bar1>3.00</bar1></foo>";
String sXPath = getQuery("testFunctionSubString", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(2, m_xc.getSelectionCount());
m_xc.clearSelections();
}
}
/**
* substring-after()
* Returns the part of the string in the string argument that occurs after the substring in the substr argument
* string=substring-after(string,substr)
* Example: substring-after('12/10','/') Result: '10'
*/
@Test
void testFunctionSubStringAfter() throws Exception {
String sXml = "<foo><bar><price at=\"val0\">3.00</price><price at=\"val1\">2</price></bar><bar1>3.00</bar1></foo>";
String sXPath = getQuery("testFunctionSubStringAfter", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(2, m_xc.getSelectionCount());
}
}
/**
* substring-before()
* Returns the part of the string in the string argument that occurs before the substring in the substr argument
* string=substring-before(string,substr)
* Example: substring-before('12/10','/') Result: '12'
*/
@Test
void testFunctionSubStringBefore() throws Exception {
String sXml = "<foo><bar><price at=\"val0\">3.00</price>" +
"<price at=\"val1\">2</price></bar><bar1>3.00</bar1></foo>";
String sXPath = getQuery("testFunctionSubStringBefore", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(2, m_xc.getSelectionCount());
}
}
/**
* translate()
* Performs a character by character replacement. It looks in the value argument for characters
* contained in string1, and replaces each character for the one in the same position in the string2
* string=translate(value,string1,string2)
* Examples: translate('12:30','30','45') Result: '12:45'
* translate('12:30','03','54') Result: '12:45'
* translate('12:30','0123','abcd') Result: 'bc:da'
*/
@Test
void testFunctionTranslate() throws Exception {
String sXml = "<foo><bar><price at=\"val0\">3.00</price><price at=\"val1\">2</price></bar><bar1>3.00</bar1></foo>";
//TODO: is this a bug in XQRL?
String sXPath = getQuery("testFunctionTranslate", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(2, m_xc.getSelectionCount());
m_xc.toNextSelection();
assertEquals("<price at=\"val0\">3.00</price>", m_xc.xmlText());
m_xc.toNextSelection();
assertEquals("<price at=\"val1\">2</price>", m_xc.xmlText());
}
}
// Number Functions
// http://www.w3.org/TR/xpath#section-Number-Functions
// /**
// * ceiling()
// * Returns the smallest integer that is not less than the number argument
// * number=ceiling(number)
// * Example: ceiling(3.14) Result: 4
// */
// public void testFunctionCeiling() throws Exception {
//
// }
// /**
// * floor()
// * Returns the largest integer that is not greater than the number argument
// * number=floor(number)
// * Example: floor(3.14) Result: 3
// */
// public void testFunctionFloor() throws Exception {
//
// }
/**
* number()
* Converts the value argument to a number
* number=number(value)
* Example: number('100') Result: 100
*/
@Test
void testFunctionNumber() throws Exception {
String sXml = "<foo><bar><price at=\"val0\">3.00</price></bar><bar1>3.00</bar1></foo>";
//really wanted . here...seems like number function doesn't
//recognize context?
String sXPath = getQuery("testFunctionNumber", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(1, m_xc.getSelectionCount());
m_xc.clearSelections();
}
}
/**
* round()
* Rounds the number argument to the nearest integer
* integer=round(number)
* Example: round(3.14) Result: 3
*/
@Test
void testFunctionRound() throws Exception {
String sXml = "<foo><bar><price at=\"val0\">3.15</price>" +
"<price at=\"val1\">2.87</price></bar><bar1>3.00</bar1></foo>";
String sXPath = getQuery("testFunctionRound", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath); //"//bar//*[round(text())=3]"
assertEquals(2, m_xc.getSelectionCount());
m_xc.toNextSelection();
assertEquals("<price at=\"val0\">3.15</price>", m_xc.xmlText());
m_xc.toNextSelection();
assertEquals("<price at=\"val1\">2.87</price>", m_xc.xmlText());
m_xc.clearSelections();
}
}
/**
* sum()
* Returns the total value of a set of numeric values in a node-set
* number=sum(nodeset)
* Example: sum(/cd/price)
*/
@Test
void testFunctionSum() throws Exception {
String sXml = "<foo><bar><price at=\"val0\">" +
"3.00</price><price at=\"val1\">2</price>" +
"</bar><bar1>3.00</bar1></foo>";
String sXPath = getQuery("testFunctionSum", 0);
String exp = "<bar><price at=\"val0\">3.00</price><price at=\"val1\">2</price></bar>";
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(1, m_xc.getSelectionCount());
m_xc.toNextSelection();
assertEquals(exp, m_xc.xmlText());
m_xc.clearSelections();
}
}
// Boolean Functions
// http://www.w3.org/TR/xpath#section-Boolean-Functions
/**
* boolean()
* Converts the value argument to Boolean and returns true or false
* bool=boolean(value)
* a number is true if and only if it is neither positive or negative zero nor NaN
* a node-set is true if and only if it is non-empty
* a string is true if and only if its length is non-zero
* an object of a type other than the four basic types is converted to a boolean in a way that is dependent on that type
*/
@Test
void testFunctionBoolean() throws Exception {
String sXml = "<foo><price at=\"val0\">3.00</price></foo>";
String sXPath = getQuery("testFunctionBoolean", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
m_xc.toNextSelection();
assertEquals(1, m_xc.getSelectionCount());
}
}
/**
* false()
* Returns false false()
* Example: number(false()) Result: 0
*/
@Test
void testFunctionFalse() throws Exception {
String sXml = "<foo><price at=\"val0\">3.00</price></foo>";
String sXPath = getQuery("testFunctionFalse", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
assertEquals(0, m_xc.getSelectionCount());
}
}
/**
* lang()
* Returns true if the language argument matches the language of the the
* xsl:lang element, otherwise it returns false
* bool=lang(language)
*/
@Test
void testFunctionLang() throws Exception {
String sXml = "<foo><div xml:lang=\"en\"><para/><price at=\"val0\">3.00</price></div></foo>";
String sXPath = getQuery("testFunctionLang", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
String sExpected = "<price at=\"val0\">3.00</price>";
m_xc.toNextSelection();
assertEquals(sExpected, m_xc.xmlText());
m_xc.clearSelections();
sXPath = getQuery("testFunctionLang", 1);
m_xc.selectPath(sXPath);
assertEquals(0, m_xc.getSelectionCount());
}
}
// /**
// * not()
// * Returns true if the condition argument is false,
// * and false if the condition argument is true
// * bool=not(condition)
// * Example: not(false())
// */
// public void testFunctionNot() throws Exception {
//
// }
/**
* true()
* Returns true
* true()
* Example: number(true()) Result: 1
*/
@Test
void testFunctionTrue() throws Exception {
String sXml = "<foo><price at=\"val0\">3.00</price></foo>";
String sXPath = getQuery("testFunctionTrue", 0);
try (XmlCursor m_xc = cur(sXml)) {
m_xc.selectPath(sXPath);
String sExpected = "<price at=\"val0\">3.00</price>";
m_xc.toNextSelection();
assertEquals(sExpected, m_xc.xmlText());
}
}
@Test
void testErrorMessages() throws Exception {
//do nothing for Jaxen
}
//ensure Jaxen is not in the classpath
@Test
@Disabled("Saxon will always jump in, so ignoring it")
public void testAntiJaxenTest() throws XmlException {
String sXml = "<foo><price at=\"val0\">3.00</price></foo>";
try (XmlCursor m_xc = cur(sXml)) {
// XQRL shouldn't handle absolute paths
assertThrows(Throwable.class, () -> m_xc.selectPath("//*"));
}
}
@Disabled
public void testExternalVariable() throws Exception {
}
@Test
void testExternalFunction() throws Exception {
String query =
"declare function local:toc($book-or-section as element()) as element()* { $book-or-section/section }; " +
"local:toc(book)";
String input =
"<book>\n" +
" <title>Data on the Web</title>\n" +
" <author>Serge Abiteboul</author>\n" +
" <author>Peter Buneman</author>\n" +
" <author>Dan Suciu</author>\n" +
" <section id=\"intro\" difficulty=\"easy\" >\n" +
" <title>Introduction</title>\n" +
" <p>Text ... </p>\n" +
" <section>\n" +
" <title>Audience</title>\n" +
" <p>Text ... </p>\n" +
" </section>\n" +
" <section>\n" +
" <title>Web Data and the Two Cultures</title>\n" +
" <p>Text ... </p>\n" +
" <figure height=\"400\" width=\"400\">\n" +
" <title>Traditional client/server architecture</title>\n" +
" <image source=\"csarch.gif\"/>\n" +
" </figure>\n" +
" <p>Text ... </p>\n" +
" </section>\n" +
" </section>\n" +
" <section id=\"syntax\" difficulty=\"medium\" >\n" +
" <title>A Syntax For Data</title>\n" +
" <p>Text ... </p>\n" +
" <figure height=\"200\" width=\"500\">\n" +
" <title>Graph representations of structures</title>\n" +
" <image source=\"graphs.gif\"/>\n" +
" </figure>\n" +
" <p>Text ... </p>\n" +
" <section>\n" +
" <title>Base Types</title>\n" +
" <p>Text ... </p>\n" +
" </section>\n" +
" <section>\n" +
" <title>Representing Relational Databases</title>\n" +
" <p>Text ... </p>\n" +
" <figure height=\"250\" width=\"400\">\n" +
" <title>Examples of Relations</title>\n" +
" <image source=\"relations.gif\"/>\n" +
" </figure>\n" +
" </section>\n" +
" <section>\n" +
" <title>Representing Object Databases</title>\n" +
" <p>Text ... </p>\n" +
" </section> \n" +
" </section>\n" +
"</book>";
XmlObject o = obj(input);
XmlObject[] res = o.execQuery(query);
assertEquals(2, res.length);
}
}