blob: 816ca73b40d632aa99c27a6534e630199059f835 [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.complex.checkin;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import xmlcursor.xpath.common.XPathCommon;
import java.io.IOException;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.fail;
import static xmlcursor.common.BasicCursorTestCase.jcur;
public class XPathTest {
static final String XML =
"<?xml version=\"1.0\"?>" +
"<doc xmlns:ext=\"http://somebody.elses.extension\">" +
"<a test=\"test\" />" +
"<b attr1=\"a1\" attr2=\"a2\" " +
"xmlns:java=\"http://xml.apache.org/xslt/java\">" +
" <a>" +
" </a>" +
"</b>" +
"</doc><!-- --> ";
private static final String[] STEPS = {
/* 0 */ "<xml-fragment xmlns:ext=\"http://somebody.elses.extension\"/>",
/* 1 */ "<doc xmlns:ext=\"http://somebody.elses.extension\"><a test=\"test\" /><b attr1=\"a1\" attr2=\"a2\" xmlns:java=\"http://xml.apache.org/xslt/java\"> <a /> </b></doc>",
/* 2 */ "<a test=\"test\" xmlns:ext=\"http://somebody.elses.extension\"/>",
/* 3 */ "<xml-fragment test=\"test\" xmlns:ext=\"http://somebody.elses.extension\" /> ",
/* 4 */ "<a xmlns:java=\"http://xml.apache.org/xslt/java\" xmlns:ext=\"http://somebody.elses.extension\" />",
/* 5 */ "<b attr1=\"a1\" attr2=\"a2\" xmlns:java=\"http://xml.apache.org/xslt/java\"> <a /> </b>",
/* 6 */ "<xml-fragment attr1=\"a1\" xmlns:java=\"http://xml.apache.org/xslt/java\" xmlns:ext=\"http://somebody.elses.extension\" />",
/* 7 */ "<xml-fragment attr2=\"a2\" xmlns:java=\"http://xml.apache.org/xslt/java\" xmlns:ext=\"http://somebody.elses.extension\" />",
/* 8 */ "<xml-fragment><!-- --></xml-fragment>",
/* 9 */ " <xml-fragment xmlns:java=\"http://xml.apache.org/xslt/java\" xmlns:ext=\"http://somebody.elses.extension\" />",
/* 10 */ "<a> </a>",
/* 11 */ "<xml-fragment> </xml-fragment>"
};
private static final String XMLFRAG_EMPTY = "<xml-fragment/>";
private static XmlObject doc;
@BeforeAll
public static void init() throws XmlException {
doc = XmlObject.Factory.parse(XML);
}
public static Stream<Arguments> dataCheckin() {
return Stream.of(
addConf("/doc/a/@test", STEPS[2]),
addConf("//.", XML, STEPS[1], STEPS[2], STEPS[5], XMLFRAG_EMPTY, STEPS[10], XMLFRAG_EMPTY, STEPS[8]),
addConf("/doc", STEPS[1]),
addConf("/doc/a", STEPS[2]),
addConf("//@*", STEPS[3], STEPS[6], STEPS[7]),
addConf(".", XML),
addConf("//ancestor-or-self::*", XML, STEPS[2], STEPS[5], STEPS[10]),
addConf("./child::*[1]", STEPS[1]),
addConf("//descendant-or-self::*/@*[1]", STEPS[2], STEPS[6]),
// This is tricky:
// The expression "*" is true for the principal axis: since the axis is self, so we're looking for elements: doc
// elt node() also returns the doc elt, but also the comment nodes in the union set are returned in doc order
addConf("//@* | * | node()", STEPS[1], STEPS[3], STEPS[6], STEPS[7], STEPS[8]),
addConf("//*", STEPS[1], STEPS[2], STEPS[5], STEPS[4]),
addConf("/doc/n", (String) null),
addConf("//descendant::comment()", STEPS[8]),
addConf("//*[local-name()='a']", STEPS[2], STEPS[4]),
addConf("//*/@*", STEPS[3], STEPS[6], STEPS[7]),
addConf("//*[last()]", STEPS[1], STEPS[5], STEPS[4]),
addConf("doc/*[last()]", STEPS[5]),
// TODO: BUGBUG: fix this
addConf("/doc/a/*/@*", (String) null),
addConf("doc/descendant::node()", STEPS[2], STEPS[5], STEPS[11], STEPS[10], STEPS[11]),
addConf("doc/a/@*", STEPS[2]),
addConf("doc/b/a/ancestor-or-self::*", STEPS[1], STEPS[5], STEPS[4]),
addConf("doc/b/a/preceding::*", STEPS[2]),
addConf("doc/a/following::*", STEPS[5], STEPS[4]),
addConf("/doc/b/preceding-sibling::*", STEPS[2]),
addConf("/doc/a/following-sibling::*", STEPS[5])
// "/doc/namespace::*", STEPS[0],DEFAULT_NS};
);
}
private static Arguments addConf(String xpath, String... expected) {
return Arguments.of(xpath, expected);
}
@ParameterizedTest
@MethodSource("dataCheckin")
void testConformance(String xpath, String[] expected) {
try (XmlCursor actual = doc.newCursor()) {
actual.selectPath(xpath);
if (actual.getSelectionCount() == 0) {
assertNull(expected[0]);
return;
}
XmlObject[] expXO = Stream.of(expected).map(XPathTest::parse).toArray(XmlObject[]::new);
XPathCommon.compare(actual, expXO);
}
}
public static Stream<Arguments> dataZvon() {
return Stream.of(
addZvon(1, "/AAA", "<AAA><BBB/><CCC/><BBB/><BBB/><DDD><BBB/></DDD><CCC/></AAA>"),
addZvon(1, "/AAA/CCC", "<CCC/>", "<CCC/>"),
addZvon(1, "/AAA/DDD/BBB", "<BBB/>"),
addZvon(2, "//BBB", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>"),
addZvon(2, "//DDD/BBB", "<BBB/>", "<BBB/>", "<BBB/>"),
addZvon(3, "/AAA/CCC/DDD/*", "<BBB/>", "<BBB/>", "<EEE/>", "<FFF/>"),
addZvon(3, "/*/*/*/BBB", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB><BBB/></BBB>"),
//according to Galax the document order is :
addZvon(3, "//*",
"<AAA><XXX><DDD><BBB/><BBB/><EEE/><FFF/></DDD></XXX><CCC><DDD><BBB/><BBB/><EEE/><FFF/></DDD></CCC><CCC><BBB><BBB><BBB/></BBB></BBB></CCC></AAA>",
"<XXX><DDD><BBB/><BBB/><EEE/><FFF/></DDD></XXX>", "<DDD><BBB/><BBB/><EEE/><FFF/></DDD>", "<BBB/>", "<BBB/>",
"<EEE/>", "<FFF/>", "<CCC><DDD><BBB/><BBB/><EEE/><FFF/></DDD></CCC>", "<DDD><BBB/><BBB/><EEE/><FFF/></DDD>",
"<BBB/>", "<BBB/>", "<EEE/>", "<FFF/>", "<CCC><BBB><BBB><BBB/></BBB></BBB></CCC>",
"<BBB><BBB><BBB/></BBB></BBB>", "<BBB><BBB/></BBB>", "<BBB/>"),
addZvon(4, "/AAA/BBB[1]", "<BBB/>"),
addZvon(4, "/AAA/BBB[last()]", "<BBB/>"),
addZvon(5, "//@id", "<xml-fragment id=\"b1\"/>", "<xml-fragment id=\"b2\"/>"),
addZvon(5, "//BBB[@id]", "<BBB id = \"b1\"/>", "<BBB id = \"b2\"/>"),
addZvon(5, "//BBB[@name]", "<BBB name=\"bbb\"/>"),
addZvon(5, "//BBB[@*]", "<BBB id = \"b1\"/>", "<BBB id = \"b2\"/>", "<BBB name=\"bbb\"/>"),
addZvon(5, "//BBB[not(@*)]", "<BBB/>"),
addZvon(6, "//BBB[@id='b1']", "<BBB id = \"b1\"/>"),
addZvon(6, "//BBB[@name='bbb']", "<BBB name=\"bbb\"/>"),
addZvon(6, "//BBB[normalize-space(@name)='bbb']", "<BBB name=\" bbb \"/>", "<BBB name=\"bbb\"/>"),
addZvon(7, "//*[count(BBB)=2]", "<DDD><BBB/><BBB/></DDD>"),
addZvon(7, "//*[count(*)=2]", "<DDD><BBB/><BBB/></DDD>", "<EEE><CCC/><DDD/></EEE>"),
addZvon(7, "//*[count(*)=3]",
"<AAA><CCC><BBB/><BBB/><BBB/></CCC><DDD><BBB/><BBB/></DDD><EEE><CCC/><DDD/></EEE></AAA>", "<CCC><BBB/><BBB/><BBB/></CCC>"),
addZvon(8, "//*[name()='BBB']", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>"),
addZvon(8, "//*[starts-with(name(),'B')]", "<BCC><BBB/><BBB/><BBB/></BCC>",
"<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>", "<BEC><CCC/><DBD/></BEC>"),
// ykadiysk: Jaxen prints in BF left-to-right order but XPath wants doc order
// addZvon("zvon8.xml", "//*[starts-with(name(),'B')]", "<BCC><BBB/><BBB/><BBB/></BCC>",
// "<BBB/>", "<BBB/>", "<BEC><CCC/><DBD/></BEC>", "<BBB/>", "<BBB/>", "<BBB/>"),
addZvon(8, "//*[contains(name(),'C')]", "<BCC><BBB/><BBB/><BBB/></BCC>", "<BEC><CCC/><DBD/></BEC>", "<CCC/>"),
addZvon(9, "//*[string-length(name()) = 3]", "<AAA><Q/><SSSS/><BB/><CCC/><DDDDDDDD/><EEEE/></AAA>", "<CCC/>"),
addZvon(9, "//*[string-length(name()) < 3]", "<Q/>", "<BB/>"),
addZvon(9, "//*[string-length(name()) > 3]", "<SSSS/>", "<DDDDDDDD/>", "<EEEE/>"),
addZvon(10, "$this//CCC | $this//BBB", "<BBB/>", "<CCC/>", "<CCC/>"),
// Nodes are returned in document order
addZvon(10, "$this/AAA/EEE | $this//BBB", "<BBB/>", "<EEE/>"),
addZvon(10, "./AAA/EEE |.//DDD/CCC | ./AAA | .//BBB", "<AAA><BBB/><CCC/><DDD><CCC/></DDD><EEE/></AAA>",
"<BBB/>", "<CCC/>", "<EEE/>"),
addZvon(11, "/AAA", "<AAA><BBB/><CCC/></AAA>"),
addZvon(11, "/child::AAA", "<AAA><BBB/><CCC/></AAA>"),
addZvon(11, "/AAA/BBB", "<BBB/>"),
addZvon(11, "/child::AAA/child::BBB", "<BBB/>"),
addZvon(11, "/child::AAA/BBB", "<BBB/>"),
addZvon(12, "/descendant::*",
"<AAA><BBB><DDD><CCC><DDD/><EEE/></CCC></DDD></BBB><CCC><DDD><EEE><DDD><FFF/></DDD></EEE></DDD></CCC></AAA>",
"<BBB><DDD><CCC><DDD/><EEE/></CCC></DDD></BBB>", "<DDD><CCC><DDD/><EEE/></CCC></DDD>",
"<CCC><DDD/><EEE/></CCC>", "<DDD/>", "<EEE/>", "<CCC><DDD><EEE><DDD><FFF/></DDD></EEE></DDD></CCC>",
"<DDD><EEE><DDD><FFF/></DDD></EEE></DDD>", "<EEE><DDD><FFF/></DDD></EEE>", "<DDD><FFF/></DDD>", "<FFF/>"),
addZvon(12, "/AAA/BBB/descendant::*", "<DDD><CCC><DDD/><EEE/></CCC></DDD>",
"<CCC><DDD/><EEE/></CCC>", "<DDD/>", "<EEE/>"),
addZvon(12, "//CCC/descendant::*", "<DDD/>", "<EEE/>", "<DDD><EEE><DDD><FFF/></DDD></EEE></DDD>",
"<EEE><DDD><FFF/></DDD></EEE>", "<DDD><FFF/></DDD>", "<FFF/>"),
addZvon(12, "//CCC/descendant::DDD", "<DDD/>", "<DDD><EEE><DDD><FFF/></DDD></EEE></DDD>", "<DDD><FFF/></DDD>"),
addZvon(13, "//DDD/parent::*", "<BBB><DDD><CCC><DDD/><EEE/></CCC></DDD></BBB>",
"<CCC><DDD/><EEE/></CCC>", "<CCC><DDD><EEE><DDD><FFF/></DDD></EEE></DDD></CCC>", "<EEE><DDD><FFF/></DDD></EEE>"),
addZvon(14, "/AAA/BBB/DDD/CCC/EEE/ancestor::*",
"<AAA><BBB><DDD><CCC><DDD/><EEE/></CCC></DDD></BBB><CCC><DDD><EEE><DDD><FFF/></DDD></EEE></DDD></CCC></AAA>",
"<BBB><DDD><CCC><DDD/><EEE/></CCC></DDD></BBB>", "<DDD><CCC><DDD/><EEE/></CCC></DDD>", "<CCC><DDD/><EEE/></CCC>"),
addZvon(14, "//FFF/ancestor::*",
"<AAA><BBB><DDD><CCC><DDD/><EEE/></CCC></DDD></BBB><CCC><DDD><EEE><DDD><FFF/></DDD></EEE></DDD></CCC></AAA>",
"<CCC><DDD><EEE><DDD><FFF/></DDD></EEE></DDD></CCC>", "<DDD><EEE><DDD><FFF/></DDD></EEE></DDD>",
"<EEE><DDD><FFF/></DDD></EEE>", "<DDD><FFF/></DDD>"),
addZvon(15, "/AAA/BBB/following-sibling::*",
"<XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX>", "<CCC><DDD/></CCC>"),
addZvon(15, "//CCC/following-sibling::*", "<DDD/>", "<FFF/>", "<FFF><GGG/></FFF>"),
addZvon(16, "/AAA/XXX/preceding-sibling::*", "<BBB><CCC/><DDD/></BBB>"),
addZvon(16, "//CCC/preceding-sibling::*", "<BBB><CCC/><DDD/></BBB>",
"<XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX>", "<EEE/>", "<DDD/>"),
addZvon(17, "/AAA/XXX/following::*", "<CCC><DDD/></CCC>", "<DDD/>"),
addZvon(17, "//ZZZ/following::*", "<FFF><GGG/></FFF>", "<GGG/>",
"<XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX>",
"<DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD>", "<EEE/>", "<DDD/>", "<CCC/>", "<FFF/>",
"<FFF><GGG/></FFF>", "<GGG/>", "<CCC><DDD/></CCC>", "<DDD/>"),
// the preceding axis contains all nodes that are descendants of the root of the tree in which the context
// node is found, are not ancestors of the context node, and occur before the context node in document order
addZvon(18, "/AAA/XXX/preceding::*", "<BBB><CCC/><ZZZ><DDD/></ZZZ></BBB>", "<CCC/>",
"<ZZZ><DDD/></ZZZ>", "<DDD/>"),
addZvon(18, "//GGG/preceding::*", "<BBB><CCC/><ZZZ><DDD/></ZZZ></BBB>", "<CCC/>",
"<ZZZ><DDD/></ZZZ>", "<DDD/>", "<EEE/>", "<DDD/>", "<CCC/>", "<FFF/>"),
addZvon(19, "/AAA/XXX/descendant-or-self::*", "<XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX>",
"<DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD>", "<EEE/>", "<DDD/>", "<CCC/>", "<FFF/>", "<FFF><GGG/></FFF>", "<GGG/>"),
addZvon(19, "//CCC/descendant-or-self::*", "<CCC/>", "<CCC/>", "<CCC><DDD/></CCC>", "<DDD/>"),
addZvon(20, "/AAA/XXX/DDD/EEE/ancestor-or-self::*",
"<AAA><BBB><CCC/><ZZZ><DDD/></ZZZ></BBB><XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX><CCC><DDD/></CCC></AAA>",
"<XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX>",
"<DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD>", "<EEE/>"),
addZvon(20, "//GGG/ancestor-or-self::*",
"<AAA><BBB><CCC/><ZZZ><DDD/></ZZZ></BBB><XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX><CCC><DDD/></CCC></AAA>",
"<XXX><DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD></XXX>",
"<DDD><EEE/><DDD/><CCC/><FFF/><FFF><GGG/></FFF></DDD>", "<FFF><GGG/></FFF>", "<GGG/>"),
addZvon(21, "//GGG/ancestor::*",
"<AAA><BBB><CCC/><ZZZ/></BBB><XXX><DDD><EEE/><FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF></DDD></XXX><CCC><DDD/></CCC></AAA>",
"<XXX><DDD><EEE/><FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF></DDD></XXX>",
"<DDD><EEE/><FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF></DDD>",
"<FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF>"),
addZvon(21, "//GGG/descendant::*", "<JJJ><QQQ/></JJJ>", "<QQQ/>", "<JJJ/>"),
addZvon(21, "//GGG/following::*", "<HHH/>", "<CCC><DDD/></CCC>", "<DDD/>"),
addZvon(21, "//GGG/preceding::*", "<BBB><CCC/><ZZZ/></BBB>", "<CCC/>", "<ZZZ/>", "<EEE/>", "<HHH/>"),
addZvon(21, "//GGG/self::*", "<GGG><JJJ><QQQ/></JJJ><JJJ/></GGG>"),
addZvon(21, "//GGG/ancestor::* | //GGG/descendant::* | //GGG/following::* | //GGG/preceding::* | //GGG/self::*",
"<AAA><BBB><CCC/><ZZZ/></BBB><XXX><DDD><EEE/><FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF></DDD></XXX><CCC><DDD/></CCC></AAA>",
"<BBB><CCC/><ZZZ/></BBB>", "<CCC/>", "<ZZZ/>", "<XXX><DDD><EEE/><FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF></DDD></XXX>",
"<DDD><EEE/><FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF></DDD>", "<EEE/>",
"<FFF><HHH/><GGG><JJJ><QQQ/></JJJ><JJJ/></GGG><HHH/></FFF>", "<HHH/>", "<GGG><JJJ><QQQ/></JJJ><JJJ/></GGG>",
"<JJJ><QQQ/></JJJ>", "<QQQ/>", "<JJJ/>", "<HHH/>", "<CCC><DDD/></CCC>", "<DDD/>"),
addZvon(22, "//BBB[position() mod 2 = 0 ]", "<BBB/>", "<BBB/>", "<BBB/>", "<BBB/>"),
addZvon(22, "//BBB[ position() = floor(last() div 2 + 0.5) or position() = ceiling(last() div 2 + 0.5) ]",
"<BBB/>", "<BBB/>"),
addZvon(22, "//CCC[ position() = floor(last() div 2 + 0.5) or position() = ceiling(last() div 2 + 0.5) ]",
"<CCC/>")
);
}
private static Arguments addZvon(int dataset, String xpath, String... expected) {
return Arguments.of(dataset, xpath, expected);
}
/**
* Verifies XPath impl using examples from
* http://www.zvon.org/xxl/XPathTutorial/Output/example1.html
* includes expanded notations as well
*/
@ParameterizedTest(name = "{index}: zvon{0}.xml {1}")
@MethodSource("dataZvon")
void zvonExample(int dataset, String xpath, String[] expected) throws IOException, XmlException {
try (XmlCursor x1 = jcur("xbean/xmlcursor/xpath/zvon"+dataset+".xml")) {
x1.selectPath(xpath);
XmlObject[] exp = new XmlObject[expected.length];
for (int i = 0; i < expected.length; i++) {
exp[i] = XmlObject.Factory.parse(expected[i]);
}
XPathCommon.compare(x1, exp);
}
}
private static XmlObject parse(String str) {
try {
return XmlObject.Factory.parse(str);
} catch (XmlException e) {
fail(e.getMessage());
return null;
}
}
}