1e209b8615849755908f17053490e168051be97e0Jesse Wilson/*
2e209b8615849755908f17053490e168051be97e0Jesse Wilson * Copyright (C) 2010 The Android Open Source Project
3e209b8615849755908f17053490e168051be97e0Jesse Wilson *
4e209b8615849755908f17053490e168051be97e0Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License");
5e209b8615849755908f17053490e168051be97e0Jesse Wilson * you may not use this file except in compliance with the License.
6e209b8615849755908f17053490e168051be97e0Jesse Wilson * You may obtain a copy of the License at
7e209b8615849755908f17053490e168051be97e0Jesse Wilson *
8e209b8615849755908f17053490e168051be97e0Jesse Wilson *      http://www.apache.org/licenses/LICENSE-2.0
9e209b8615849755908f17053490e168051be97e0Jesse Wilson *
10e209b8615849755908f17053490e168051be97e0Jesse Wilson * Unless required by applicable law or agreed to in writing, software
11e209b8615849755908f17053490e168051be97e0Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS,
12e209b8615849755908f17053490e168051be97e0Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e209b8615849755908f17053490e168051be97e0Jesse Wilson * See the License for the specific language governing permissions and
14e209b8615849755908f17053490e168051be97e0Jesse Wilson * limitations under the License.
15e209b8615849755908f17053490e168051be97e0Jesse Wilson */
16e209b8615849755908f17053490e168051be97e0Jesse Wilson
17e209b8615849755908f17053490e168051be97e0Jesse Wilsonpackage org.apache.harmony.xml;
18e209b8615849755908f17053490e168051be97e0Jesse Wilson
19e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport junit.framework.AssertionFailedError;
20e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport junit.framework.Test;
21e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport junit.framework.TestCase;
22e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport junit.framework.TestSuite;
23e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport junit.textui.TestRunner;
24e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport org.w3c.dom.Element;
25e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport org.w3c.dom.Node;
26e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport org.w3c.dom.NodeList;
27e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport org.xml.sax.InputSource;
28e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport org.xml.sax.SAXException;
29e209b8615849755908f17053490e168051be97e0Jesse Wilson
30e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.namespace.QName;
31e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.parsers.DocumentBuilderFactory;
32e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.parsers.ParserConfigurationException;
33e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.xpath.XPath;
34e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.xpath.XPathConstants;
35e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.xpath.XPathExpressionException;
36e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.xpath.XPathFactory;
37e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport javax.xml.xpath.XPathVariableResolver;
38e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport java.io.File;
39e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport java.io.IOException;
40e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport java.util.ArrayList;
41e209b8615849755908f17053490e168051be97e0Jesse Wilsonimport java.util.List;
42e209b8615849755908f17053490e168051be97e0Jesse Wilson
43e209b8615849755908f17053490e168051be97e0Jesse Wilson/**
44e209b8615849755908f17053490e168051be97e0Jesse Wilson * The implementation-independent part of the <a
45e209b8615849755908f17053490e168051be97e0Jesse Wilson * href="http://jaxen.codehaus.org/">Jaxen</a> XPath test suite, adapted for use
46e209b8615849755908f17053490e168051be97e0Jesse Wilson * by JUnit. To run these tests on a device:
47e209b8615849755908f17053490e168051be97e0Jesse Wilson * <ul>
48e209b8615849755908f17053490e168051be97e0Jesse Wilson *   <li>Obtain the Jaxen source from the project's website.
49e209b8615849755908f17053490e168051be97e0Jesse Wilson *   <li>Copy the files to a device: <code>adb shell mkdir /data/jaxen ;
50e209b8615849755908f17053490e168051be97e0Jesse Wilson *       adb push /home/dalvik-prebuild/jaxen /data/jaxen</code>
51e209b8615849755908f17053490e168051be97e0Jesse Wilson *   <li>Invoke this class' main method, passing the on-device path to the test
52e209b8615849755908f17053490e168051be97e0Jesse Wilson *       suite's root directory as an argument.
53e209b8615849755908f17053490e168051be97e0Jesse Wilson * </ul>
54e209b8615849755908f17053490e168051be97e0Jesse Wilson */
55e209b8615849755908f17053490e168051be97e0Jesse Wilsonpublic class JaxenXPathTestSuite {
56e209b8615849755908f17053490e168051be97e0Jesse Wilson
57e209b8615849755908f17053490e168051be97e0Jesse Wilson    private static final File DEFAULT_JAXEN_HOME
58e209b8615849755908f17053490e168051be97e0Jesse Wilson            = new File("/home/dalvik-prebuild/jaxen");
59e209b8615849755908f17053490e168051be97e0Jesse Wilson
60e209b8615849755908f17053490e168051be97e0Jesse Wilson    public static void main(String[] args) throws Exception {
61e209b8615849755908f17053490e168051be97e0Jesse Wilson        if (args.length != 1) {
62e209b8615849755908f17053490e168051be97e0Jesse Wilson            System.out.println("Usage: JaxenXPathTestSuite <jaxen-home>");
63e209b8615849755908f17053490e168051be97e0Jesse Wilson            return;
64e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
65e209b8615849755908f17053490e168051be97e0Jesse Wilson
66e209b8615849755908f17053490e168051be97e0Jesse Wilson        File jaxenHome = new File(args[0]);
67e209b8615849755908f17053490e168051be97e0Jesse Wilson        TestRunner.run(suite(jaxenHome));
68e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
69e209b8615849755908f17053490e168051be97e0Jesse Wilson
70e209b8615849755908f17053490e168051be97e0Jesse Wilson    public static Test suite() throws Exception {
71e209b8615849755908f17053490e168051be97e0Jesse Wilson        return suite(DEFAULT_JAXEN_HOME);
72e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
73e209b8615849755908f17053490e168051be97e0Jesse Wilson
74e209b8615849755908f17053490e168051be97e0Jesse Wilson    /**
75e209b8615849755908f17053490e168051be97e0Jesse Wilson     * Creates a test suite from the Jaxen tests.xml catalog.
76e209b8615849755908f17053490e168051be97e0Jesse Wilson     */
77e209b8615849755908f17053490e168051be97e0Jesse Wilson    public static Test suite(File jaxenHome)
78e209b8615849755908f17053490e168051be97e0Jesse Wilson            throws ParserConfigurationException, IOException, SAXException {
79e209b8615849755908f17053490e168051be97e0Jesse Wilson
80e209b8615849755908f17053490e168051be97e0Jesse Wilson        /*
81e209b8615849755908f17053490e168051be97e0Jesse Wilson         * The tests.xml document has this structure:
82e209b8615849755908f17053490e168051be97e0Jesse Wilson         *
83e209b8615849755908f17053490e168051be97e0Jesse Wilson         * <tests>
84e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <document url="...">
85e209b8615849755908f17053490e168051be97e0Jesse Wilson         *     <context .../>
86e209b8615849755908f17053490e168051be97e0Jesse Wilson         *     <context .../>
87e209b8615849755908f17053490e168051be97e0Jesse Wilson         *     <context .../>
88e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   </document>
89e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <document url="...">
90e209b8615849755908f17053490e168051be97e0Jesse Wilson         *     <context .../>
91e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   </document>
92e209b8615849755908f17053490e168051be97e0Jesse Wilson         * </tests>
93e209b8615849755908f17053490e168051be97e0Jesse Wilson         */
94e209b8615849755908f17053490e168051be97e0Jesse Wilson
95e209b8615849755908f17053490e168051be97e0Jesse Wilson        File testsXml = new File(jaxenHome + "/xml/test/tests.xml");
96e209b8615849755908f17053490e168051be97e0Jesse Wilson        Element tests = DocumentBuilderFactory.newInstance()
97e209b8615849755908f17053490e168051be97e0Jesse Wilson                .newDocumentBuilder().parse(testsXml).getDocumentElement();
98e209b8615849755908f17053490e168051be97e0Jesse Wilson
99e209b8615849755908f17053490e168051be97e0Jesse Wilson        TestSuite result = new TestSuite();
100e209b8615849755908f17053490e168051be97e0Jesse Wilson        for (Element document : elementsOf(tests.getElementsByTagName("document"))) {
101e209b8615849755908f17053490e168051be97e0Jesse Wilson            String url = document.getAttribute("url");
102e209b8615849755908f17053490e168051be97e0Jesse Wilson            InputSource inputSource = new InputSource("file:" + jaxenHome + "/" + url);
103e209b8615849755908f17053490e168051be97e0Jesse Wilson            for (final Element context : elementsOf(document.getElementsByTagName("context"))) {
104e209b8615849755908f17053490e168051be97e0Jesse Wilson                contextToTestSuite(result, url, inputSource, context);
105e209b8615849755908f17053490e168051be97e0Jesse Wilson            }
106e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
107e209b8615849755908f17053490e168051be97e0Jesse Wilson
108e209b8615849755908f17053490e168051be97e0Jesse Wilson        return result;
109e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
110e209b8615849755908f17053490e168051be97e0Jesse Wilson
111e209b8615849755908f17053490e168051be97e0Jesse Wilson    /**
112e209b8615849755908f17053490e168051be97e0Jesse Wilson     * Populates the test suite with tests from the given XML context element.
113e209b8615849755908f17053490e168051be97e0Jesse Wilson     */
114e209b8615849755908f17053490e168051be97e0Jesse Wilson    private static void contextToTestSuite(TestSuite suite, String url,
115e209b8615849755908f17053490e168051be97e0Jesse Wilson            InputSource inputSource, Element element) {
116e209b8615849755908f17053490e168051be97e0Jesse Wilson
117e209b8615849755908f17053490e168051be97e0Jesse Wilson        /*
118e209b8615849755908f17053490e168051be97e0Jesse Wilson         * Each context element has this structure:
119e209b8615849755908f17053490e168051be97e0Jesse Wilson         *
120e209b8615849755908f17053490e168051be97e0Jesse Wilson         * <context select="...">
121e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <test .../>
122e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <test .../>
123e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <test .../>
124e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <valueOf .../>
125e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <valueOf .../>
126e209b8615849755908f17053490e168051be97e0Jesse Wilson         *   <valueOf .../>
127e209b8615849755908f17053490e168051be97e0Jesse Wilson         * </context>
128e209b8615849755908f17053490e168051be97e0Jesse Wilson         */
129e209b8615849755908f17053490e168051be97e0Jesse Wilson
130e209b8615849755908f17053490e168051be97e0Jesse Wilson        String select = element.getAttribute("select");
131e209b8615849755908f17053490e168051be97e0Jesse Wilson        Context context = new Context(inputSource, url, select);
132e209b8615849755908f17053490e168051be97e0Jesse Wilson
133e209b8615849755908f17053490e168051be97e0Jesse Wilson        XPath xpath = XPathFactory.newInstance().newXPath();
134e209b8615849755908f17053490e168051be97e0Jesse Wilson        xpath.setXPathVariableResolver(new ElementVariableResolver(element));
135e209b8615849755908f17053490e168051be97e0Jesse Wilson
136e209b8615849755908f17053490e168051be97e0Jesse Wilson        for (Element test : elementsOf(element.getChildNodes())) {
137e209b8615849755908f17053490e168051be97e0Jesse Wilson            if (test.getTagName().equals("test")) {
138e209b8615849755908f17053490e168051be97e0Jesse Wilson                suite.addTest(createFromTest(xpath, context, test));
139e209b8615849755908f17053490e168051be97e0Jesse Wilson
140e209b8615849755908f17053490e168051be97e0Jesse Wilson            } else if (test.getTagName().equals("valueOf")) {
141e209b8615849755908f17053490e168051be97e0Jesse Wilson                suite.addTest(createFromValueOf(xpath, context, test));
142e209b8615849755908f17053490e168051be97e0Jesse Wilson
143e209b8615849755908f17053490e168051be97e0Jesse Wilson            } else {
144e209b8615849755908f17053490e168051be97e0Jesse Wilson                throw new UnsupportedOperationException("Unsupported test: " + context);
145e209b8615849755908f17053490e168051be97e0Jesse Wilson            }
146e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
147e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
148e209b8615849755908f17053490e168051be97e0Jesse Wilson
149e209b8615849755908f17053490e168051be97e0Jesse Wilson    /**
150e209b8615849755908f17053490e168051be97e0Jesse Wilson     * Returns the test described by the given {@code <test>} element. Such
151e209b8615849755908f17053490e168051be97e0Jesse Wilson     * tests come in one of three varieties:
152e209b8615849755908f17053490e168051be97e0Jesse Wilson     *
153e209b8615849755908f17053490e168051be97e0Jesse Wilson     * <ul>
154e209b8615849755908f17053490e168051be97e0Jesse Wilson     *   <li>Expected failures.
155e209b8615849755908f17053490e168051be97e0Jesse Wilson     *   <li>String matches. These tests have a nested {@code <valueOf>} element
156e209b8615849755908f17053490e168051be97e0Jesse Wilson     *       that sub-selects an expected text.
157e209b8615849755908f17053490e168051be97e0Jesse Wilson     *   <li>Count matches. These tests specify how many nodes are expected to
158e209b8615849755908f17053490e168051be97e0Jesse Wilson     *       match.
159e209b8615849755908f17053490e168051be97e0Jesse Wilson     * </ul>
160e209b8615849755908f17053490e168051be97e0Jesse Wilson     */
161e209b8615849755908f17053490e168051be97e0Jesse Wilson    private static TestCase createFromTest(
162e209b8615849755908f17053490e168051be97e0Jesse Wilson            final XPath xpath, final Context context, final Element element) {
163e209b8615849755908f17053490e168051be97e0Jesse Wilson        final String select = element.getAttribute("select");
164e209b8615849755908f17053490e168051be97e0Jesse Wilson
165e209b8615849755908f17053490e168051be97e0Jesse Wilson        /* Such as <test exception="true" select="..." count="0"/> */
166e209b8615849755908f17053490e168051be97e0Jesse Wilson        if (element.getAttribute("exception").equals("true")) {
167e209b8615849755908f17053490e168051be97e0Jesse Wilson            return new XPathTest(context, select) {
168e209b8615849755908f17053490e168051be97e0Jesse Wilson                @Override void test(Node contextNode) {
169e209b8615849755908f17053490e168051be97e0Jesse Wilson                    try {
170e209b8615849755908f17053490e168051be97e0Jesse Wilson                        xpath.evaluate(select, contextNode);
171e209b8615849755908f17053490e168051be97e0Jesse Wilson                        fail("Expected exception!");
172e209b8615849755908f17053490e168051be97e0Jesse Wilson                    } catch (XPathExpressionException expected) {
173e209b8615849755908f17053490e168051be97e0Jesse Wilson                    }
174e209b8615849755908f17053490e168051be97e0Jesse Wilson                }
175e209b8615849755908f17053490e168051be97e0Jesse Wilson            };
176e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
177e209b8615849755908f17053490e168051be97e0Jesse Wilson
178e209b8615849755908f17053490e168051be97e0Jesse Wilson        /* a <test> with a nested <valueOf>, both of which have select attributes */
179e209b8615849755908f17053490e168051be97e0Jesse Wilson        NodeList valueOfElements = element.getElementsByTagName("valueOf");
180e209b8615849755908f17053490e168051be97e0Jesse Wilson        if (valueOfElements.getLength() == 1) {
181e209b8615849755908f17053490e168051be97e0Jesse Wilson            final Element valueOf = (Element) valueOfElements.item(0);
182e209b8615849755908f17053490e168051be97e0Jesse Wilson            final String valueOfSelect = valueOf.getAttribute("select");
183e209b8615849755908f17053490e168051be97e0Jesse Wilson
184e209b8615849755908f17053490e168051be97e0Jesse Wilson            return new XPathTest(context, select) {
185e209b8615849755908f17053490e168051be97e0Jesse Wilson                @Override void test(Node contextNode) throws XPathExpressionException {
186e209b8615849755908f17053490e168051be97e0Jesse Wilson                    Node newContext = (Node) xpath.evaluate(
187e209b8615849755908f17053490e168051be97e0Jesse Wilson                            select, contextNode, XPathConstants.NODE);
188e209b8615849755908f17053490e168051be97e0Jesse Wilson                    assertEquals(valueOf.getTextContent(),
189e209b8615849755908f17053490e168051be97e0Jesse Wilson                            xpath.evaluate(valueOfSelect, newContext, XPathConstants.STRING));
190e209b8615849755908f17053490e168051be97e0Jesse Wilson                }
191e209b8615849755908f17053490e168051be97e0Jesse Wilson            };
192e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
193e209b8615849755908f17053490e168051be97e0Jesse Wilson
194e209b8615849755908f17053490e168051be97e0Jesse Wilson        /* Such as <test select="..." count="5"/> */
195e209b8615849755908f17053490e168051be97e0Jesse Wilson        final String count = element.getAttribute("count");
196e209b8615849755908f17053490e168051be97e0Jesse Wilson        if (count.length() > 0) {
197e209b8615849755908f17053490e168051be97e0Jesse Wilson            return new XPathTest(context, select) {
198e209b8615849755908f17053490e168051be97e0Jesse Wilson                @Override void test(Node contextNode) throws XPathExpressionException {
199e209b8615849755908f17053490e168051be97e0Jesse Wilson                    NodeList result = (NodeList) xpath.evaluate(
200e209b8615849755908f17053490e168051be97e0Jesse Wilson                            select, contextNode, XPathConstants.NODESET);
201e209b8615849755908f17053490e168051be97e0Jesse Wilson                    assertEquals(Integer.parseInt(count), result.getLength());
202e209b8615849755908f17053490e168051be97e0Jesse Wilson                }
203e209b8615849755908f17053490e168051be97e0Jesse Wilson            };
204e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
205e209b8615849755908f17053490e168051be97e0Jesse Wilson
206e209b8615849755908f17053490e168051be97e0Jesse Wilson        throw new UnsupportedOperationException("Unsupported test: " + context);
207e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
208e209b8615849755908f17053490e168051be97e0Jesse Wilson
209e209b8615849755908f17053490e168051be97e0Jesse Wilson    /**
210e209b8615849755908f17053490e168051be97e0Jesse Wilson     * Returns the test described by the given {@code <valueOf>} element. These
211e209b8615849755908f17053490e168051be97e0Jesse Wilson     * tests select an expected text.
212e209b8615849755908f17053490e168051be97e0Jesse Wilson     */
213e209b8615849755908f17053490e168051be97e0Jesse Wilson    private static TestCase createFromValueOf(
214e209b8615849755908f17053490e168051be97e0Jesse Wilson            final XPath xpath, final Context context, final Element element) {
215e209b8615849755908f17053490e168051be97e0Jesse Wilson        final String select = element.getAttribute("select");
216e209b8615849755908f17053490e168051be97e0Jesse Wilson        return new XPathTest(context, select) {
217e209b8615849755908f17053490e168051be97e0Jesse Wilson            @Override void test(Node contextNode) throws XPathExpressionException {
218e209b8615849755908f17053490e168051be97e0Jesse Wilson                assertEquals(element.getTextContent(),
219e209b8615849755908f17053490e168051be97e0Jesse Wilson                        xpath.evaluate(select, contextNode, XPathConstants.STRING));
220e209b8615849755908f17053490e168051be97e0Jesse Wilson            }
221e209b8615849755908f17053490e168051be97e0Jesse Wilson        };
222e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
223e209b8615849755908f17053490e168051be97e0Jesse Wilson
224e209b8615849755908f17053490e168051be97e0Jesse Wilson    /**
225e209b8615849755908f17053490e168051be97e0Jesse Wilson     * The subject of an XPath query. This is itself defined by an XPath query,
226e209b8615849755908f17053490e168051be97e0Jesse Wilson     * so each test requires at least XPath expressions to be evaluated.
227e209b8615849755908f17053490e168051be97e0Jesse Wilson     */
228e209b8615849755908f17053490e168051be97e0Jesse Wilson    static class Context {
229e209b8615849755908f17053490e168051be97e0Jesse Wilson        private final InputSource inputSource;
230e209b8615849755908f17053490e168051be97e0Jesse Wilson        private final String url;
231e209b8615849755908f17053490e168051be97e0Jesse Wilson        private final String select;
232e209b8615849755908f17053490e168051be97e0Jesse Wilson
233e209b8615849755908f17053490e168051be97e0Jesse Wilson        Context(InputSource inputSource, String url, String select) {
234e209b8615849755908f17053490e168051be97e0Jesse Wilson            this.inputSource = inputSource;
235e209b8615849755908f17053490e168051be97e0Jesse Wilson            this.url = url;
236e209b8615849755908f17053490e168051be97e0Jesse Wilson            this.select = select;
237e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
238e209b8615849755908f17053490e168051be97e0Jesse Wilson
239e209b8615849755908f17053490e168051be97e0Jesse Wilson        Node getNode() {
240e209b8615849755908f17053490e168051be97e0Jesse Wilson            XPath xpath = XPathFactory.newInstance().newXPath();
241e209b8615849755908f17053490e168051be97e0Jesse Wilson            try {
242e209b8615849755908f17053490e168051be97e0Jesse Wilson                return (Node) xpath.evaluate(select, inputSource, XPathConstants.NODE);
243e209b8615849755908f17053490e168051be97e0Jesse Wilson            } catch (XPathExpressionException e) {
244e209b8615849755908f17053490e168051be97e0Jesse Wilson                Error error = new AssertionFailedError("Failed to get context");
245e209b8615849755908f17053490e168051be97e0Jesse Wilson                error.initCause(e);
246e209b8615849755908f17053490e168051be97e0Jesse Wilson                throw error;
247e209b8615849755908f17053490e168051be97e0Jesse Wilson            }
248e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
249e209b8615849755908f17053490e168051be97e0Jesse Wilson
250e209b8615849755908f17053490e168051be97e0Jesse Wilson        @Override public String toString() {
251e209b8615849755908f17053490e168051be97e0Jesse Wilson            return url + " " + select;
252e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
253e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
254e209b8615849755908f17053490e168051be97e0Jesse Wilson
255e209b8615849755908f17053490e168051be97e0Jesse Wilson    /**
256e209b8615849755908f17053490e168051be97e0Jesse Wilson     * This test evaluates an XPath expression against a context node and
257e209b8615849755908f17053490e168051be97e0Jesse Wilson     * compares the result to a known expectation.
258e209b8615849755908f17053490e168051be97e0Jesse Wilson     */
259e209b8615849755908f17053490e168051be97e0Jesse Wilson    public abstract static class XPathTest extends TestCase {
260e209b8615849755908f17053490e168051be97e0Jesse Wilson        private final Context context;
261e209b8615849755908f17053490e168051be97e0Jesse Wilson        private final String select;
262e209b8615849755908f17053490e168051be97e0Jesse Wilson
263e209b8615849755908f17053490e168051be97e0Jesse Wilson        public XPathTest(Context context, String select) {
264e209b8615849755908f17053490e168051be97e0Jesse Wilson            super("test");
265e209b8615849755908f17053490e168051be97e0Jesse Wilson            this.context = context;
266e209b8615849755908f17053490e168051be97e0Jesse Wilson            this.select = select;
267e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
268e209b8615849755908f17053490e168051be97e0Jesse Wilson
269e209b8615849755908f17053490e168051be97e0Jesse Wilson        abstract void test(Node contextNode) throws XPathExpressionException;
270e209b8615849755908f17053490e168051be97e0Jesse Wilson
271e209b8615849755908f17053490e168051be97e0Jesse Wilson        public final void test() throws XPathExpressionException {
272e209b8615849755908f17053490e168051be97e0Jesse Wilson            try {
273e209b8615849755908f17053490e168051be97e0Jesse Wilson                test(context.getNode());
274e209b8615849755908f17053490e168051be97e0Jesse Wilson            } catch (XPathExpressionException e) {
275e209b8615849755908f17053490e168051be97e0Jesse Wilson                if (isMissingFunction(e)) {
276e209b8615849755908f17053490e168051be97e0Jesse Wilson                    fail(e.getCause().getMessage());
277e209b8615849755908f17053490e168051be97e0Jesse Wilson                } else {
278e209b8615849755908f17053490e168051be97e0Jesse Wilson                    throw e;
279e209b8615849755908f17053490e168051be97e0Jesse Wilson                }
280e209b8615849755908f17053490e168051be97e0Jesse Wilson            }
281e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
282e209b8615849755908f17053490e168051be97e0Jesse Wilson
283e209b8615849755908f17053490e168051be97e0Jesse Wilson        private boolean isMissingFunction(XPathExpressionException e) {
284e209b8615849755908f17053490e168051be97e0Jesse Wilson            return e.getCause() != null
285e209b8615849755908f17053490e168051be97e0Jesse Wilson                    && e.getCause().getMessage().startsWith("Could not find function");
286e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
287e209b8615849755908f17053490e168051be97e0Jesse Wilson
288e209b8615849755908f17053490e168051be97e0Jesse Wilson        @Override public String getName() {
289e209b8615849755908f17053490e168051be97e0Jesse Wilson            return context + " " + select;
290e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
291e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
292e209b8615849755908f17053490e168051be97e0Jesse Wilson
293e209b8615849755908f17053490e168051be97e0Jesse Wilson    /**
294e209b8615849755908f17053490e168051be97e0Jesse Wilson     * Performs XPath variable resolution by using {@code var:name="value"}
295e209b8615849755908f17053490e168051be97e0Jesse Wilson     * attributes from the given element.
296e209b8615849755908f17053490e168051be97e0Jesse Wilson     */
297e209b8615849755908f17053490e168051be97e0Jesse Wilson    private static class ElementVariableResolver implements XPathVariableResolver {
298e209b8615849755908f17053490e168051be97e0Jesse Wilson        private final Element element;
299e209b8615849755908f17053490e168051be97e0Jesse Wilson        public ElementVariableResolver(Element element) {
300e209b8615849755908f17053490e168051be97e0Jesse Wilson            this.element = element;
301e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
302e209b8615849755908f17053490e168051be97e0Jesse Wilson        public Object resolveVariable(QName variableName) {
303e209b8615849755908f17053490e168051be97e0Jesse Wilson            return element.getAttribute("var:" + variableName.getLocalPart());
304e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
305e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
306e209b8615849755908f17053490e168051be97e0Jesse Wilson
307e209b8615849755908f17053490e168051be97e0Jesse Wilson    private static List<Element> elementsOf(NodeList nodeList) {
308e209b8615849755908f17053490e168051be97e0Jesse Wilson        List<Element> result = new ArrayList<Element>();
309e209b8615849755908f17053490e168051be97e0Jesse Wilson        for (int i = 0; i < nodeList.getLength(); i++) {
310e209b8615849755908f17053490e168051be97e0Jesse Wilson            Node node = nodeList.item(i);
311e209b8615849755908f17053490e168051be97e0Jesse Wilson            if (node instanceof Element) {
312e209b8615849755908f17053490e168051be97e0Jesse Wilson                result.add((Element) node);
313e209b8615849755908f17053490e168051be97e0Jesse Wilson            }
314e209b8615849755908f17053490e168051be97e0Jesse Wilson        }
315e209b8615849755908f17053490e168051be97e0Jesse Wilson        return result;
316e209b8615849755908f17053490e168051be97e0Jesse Wilson    }
317e209b8615849755908f17053490e168051be97e0Jesse Wilson}
318