package lux.xqts; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringWriter; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import lux.XdmResultSet; import lux.xqts.TestCase.VariableBinding.Type; import net.sf.saxon.Configuration; import net.sf.saxon.expr.XPathContext; import net.sf.saxon.expr.sort.CodepointCollator; import net.sf.saxon.expr.sort.GenericAtomicComparer; import net.sf.saxon.functions.DeepEqual; import net.sf.saxon.query.QueryResult; import net.sf.saxon.s9api.Axis; import net.sf.saxon.s9api.QName; import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.XdmItem; import net.sf.saxon.s9api.XdmNode; import net.sf.saxon.s9api.XdmNodeKind; import net.sf.saxon.s9api.XdmSequenceIterator; import net.sf.saxon.s9api.XdmValue; import net.sf.saxon.trans.XPathException; import org.apache.commons.io.IOUtils; /** * represents an XQTS test case * */ public class TestCase { private final Catalog catalog; private final String name; private final String path; private final String scenario; private final String queryName; private final boolean xpath2; private final XdmValue contextItem; private final ComparisonMode comparisonMode; private HashMap<String,VariableBinding> externalVariables; private final String queryText; //private final String inputFileText; private String[] outputFileText; private final boolean expectError; private String principalData; static final String XQTS_NS = "http://www.w3.org/2005/02/query-test-XQTSCatalog"; static final QName COMPARE = new QName("compare"); static final QName IS_X_PATH2 = new QName("is-XPath2"); static final QName SCENARIO = new QName("scenario"); static final QName FILE_PATH = new QName("FilePath"); static final QName NAME = new QName("name"); static final QName VARIABLE = new QName("variable"); static final QName ROLE = new QName("role"); static final QName QUERY = new QName(XQTS_NS, "query"); static final QName INPUT_QUERY = new QName(XQTS_NS, "input-query"); static final QName INPUT_FILE = new QName(XQTS_NS, "input-file"); static final QName INPUT_URI = new QName(XQTS_NS, "input-URI"); static final QName OUTPUT_FILE = new QName(XQTS_NS, "output-file"); static final QName CONTEXT_ITEM = new QName(XQTS_NS, "contextItem"); private static final QName EXPECTED_ERROR = new QName(XQTS_NS, "expected-error"); public enum ComparisonMode { XML, Text, Fragment, Ignore, Inspect; } public TestCase (XdmNode testCase, Catalog catalog) throws FileNotFoundException, IOException, SaxonApiException { name = testCase.getAttributeValue(NAME); path = testCase.getAttributeValue(FILE_PATH); scenario = testCase.getAttributeValue(SCENARIO); xpath2 = Boolean.valueOf(testCase.getAttributeValue(IS_X_PATH2)); externalVariables = new HashMap<String, VariableBinding>(); this.catalog = catalog; XdmNode query = (XdmNode) testCase.axisIterator(Axis.CHILD, QUERY).next(); queryName = query.getAttributeValue(NAME); XdmSequenceIterator context = testCase.axisIterator(Axis.CHILD, CONTEXT_ITEM); if (context.hasNext()) { String contextItemFileName = context.next().getStringValue(); contextItem = catalog.getBuilder().build(new File(catalog.getSourceFileByID(contextItemFileName))); } else { contextItem = null; } comparisonMode = readOutputText(testCase); File queryFile = new File (getQueryPath(queryName)); String text = IOUtils.toString (new FileInputStream(queryFile)); queryText = text; bindExternalVariables(testCase, INPUT_FILE, VariableBinding.Type.FILE); bindExternalVariables(testCase, INPUT_URI, VariableBinding.Type.URI); // Are there input queries? If so, record the bindings for later evaluation XdmSequenceIterator inputQuery = testCase.axisIterator(Axis.CHILD, INPUT_QUERY); while (inputQuery.hasNext()) { XdmNode q = (XdmNode) inputQuery.next(); String filename = q.getAttributeValue(NAME); VariableBinding binding = new VariableBinding(); binding.type = Type.FILE; binding.value = getQueryPath(filename); externalVariables.put(q.getAttributeValue(VARIABLE), binding); } XdmSequenceIterator errors = testCase.axisIterator(Axis.CHILD, EXPECTED_ERROR); expectError = errors.hasNext(); catalog.putTestCase(name, this); } private void bindExternalVariables(XdmNode testCase, QName elementName, Type type) { XdmSequenceIterator input = testCase.axisIterator(Axis.CHILD, elementName); XdmNode inputFileNode = null; while (input.hasNext()) { inputFileNode = (XdmNode) input.next(); String inputVariable = inputFileNode.getAttributeValue(VARIABLE); String role = inputFileNode.getAttributeValue(ROLE); String inputFileName = inputFileNode.axisIterator(Axis.CHILD).next().getStringValue(); VariableBinding binding = new VariableBinding(); binding.type = type; binding.role = role; binding.value = catalog.getSourceFileByID(inputFileName); externalVariables.put(inputVariable, binding); if ("principal-data".equals (role)) { principalData = inputFileName; } } } private String getQueryPath(String filename) { return catalog.getDirectory() + "/Queries/XQuery/" + path + '/' + filename + ".xq"; } private ComparisonMode readOutputText(XdmNode testCase) throws IOException, FileNotFoundException { XdmSequenceIterator output = testCase.axisIterator(Axis.CHILD, OUTPUT_FILE); if (!output.hasNext()) { return ComparisonMode.Ignore; } // first count the output files (and get the comparison mode from the first one) XdmNode outputFileNode = (XdmNode) output.next(); int outputFileCount = 1; ComparisonMode mode = ComparisonMode.valueOf(outputFileNode.getAttributeValue(COMPARE)); while (output.hasNext()) { ++outputFileCount; output.next(); } outputFileText = new String[outputFileCount]; // now get the text from each node outputFileCount = 0; output = testCase.axisIterator(Axis.CHILD, OUTPUT_FILE); while (output.hasNext()) { outputFileNode = (XdmNode) output.next(); XdmSequenceIterator outputFileSeq = outputFileNode.axisIterator(Axis.CHILD); if (outputFileSeq.hasNext()) { String outputFileName = outputFileSeq.next().getStringValue(); File outputFile = new File (catalog.getDirectory() + "/ExpectedTestResults/" + path + '/' + outputFileName); String text = IOUtils.toString (new FileInputStream(outputFile)); outputFileText[outputFileCount++] = text; } else { outputFileText = null; } } return mode; } public String getName() { return name; } public String getPath() { return path; } public String getScenario() { return scenario; } public String getQueryName() { return queryName; } public String getQueryText() { return queryText; } public String getBenchmarkQueryText() { String benchQueryText = queryText; for (Map.Entry<String,VariableBinding> entry : externalVariables.entrySet()) { VariableBinding binding = entry.getValue(); String varName = entry.getKey(); if ("principal-data".equals (binding.role)) { benchQueryText = benchQueryText.replace ("declare variable $" + varName + " external;", "declare variable $" + varName + " := collection();"); } } return benchQueryText; } public boolean isXpath2() { return xpath2; } public ComparisonMode getComparisonMode() { return comparisonMode; } public String[] getOutputText() { return outputFileText; } public Boolean compareResult(Iterable<?> results) throws SaxonApiException, XPathException { switch (getComparisonMode()) { case Fragment: case Text: String result = results == null ? "" : resultToString(results); if ("-0".equals(result)) { result = "0"; } boolean isNode = (!result.isEmpty() && results.iterator().next() instanceof XdmNode); for (String output : getOutputText()) { if ("-0".equals(output)) { output = "0"; } XdmNode docWrapped = createWrappedNode(output); if (isNode) { XdmNode resultDoc = createWrappedNode(result); if (areNodesEqual (docWrapped, resultDoc)) { return true; } } else { if (normalizeWhitespace(result).equals(normalizeWhitespace(unescape(output)))) return true; } } return false; case XML: for (String output : getOutputText()) { //output = output.replace("\r\n", "\n"); XdmNode doc = catalog.getBuilder().build(new StreamSource(new ByteArrayInputStream(output.getBytes()))); // true if any of the docs is equal?? for (Object node : results) { if (areNodesEqual (doc, (XdmNode) node)) { return true; } } } return false; case Ignore: case Inspect: default: return null; } } private Object normalizeWhitespace(String s) { return s.replaceAll("\\s+", " ").trim(); } private XdmNode createWrappedNode(Object node) throws SaxonApiException { String s = node.toString(); // remove any xml declaration; a hack sure s = s.replaceFirst("<\\?xml[^>]+>", ""); return catalog.getBuilder().build(new StreamSource(new ByteArrayInputStream(("<a>"+s+"</a>").getBytes()))); } public static String resultToString(Iterable<?> results) throws XPathException { Iterator<?> iterator = results.iterator(); if (!iterator.hasNext()) { return ""; } Object result = iterator.next(); StringBuilder buf = new StringBuilder (resultToString (result)); boolean lastNode = result instanceof XdmNode; while (iterator.hasNext()) { result = iterator.next(); if (! (result instanceof XdmNode) && !lastNode) { buf.append (' '); } buf.append (resultToString (result)); lastNode = result instanceof XdmNode; } return buf.toString(); } public static String resultToString (Object o) throws XPathException { if (o instanceof XdmNode) { return resultToString((XdmNode) o); } return o.toString(); } public static String resultToString (XdmNode node) throws XPathException { StringWriter sw = new StringWriter(); Properties props = new Properties(); props.setProperty("method", "xml"); props.setProperty("indent", "no"); props.setProperty("omit-xml-declaration", "yes"); QueryResult.serialize(node.getUnderlyingNode(), new StreamResult(sw), props); return sw.toString(); } private boolean areNodesEqual (XdmNode node1, XdmNode node2) throws XPathException { if (node2.getNodeKind() != XdmNodeKind.DOCUMENT) { // compare root elements if (node1.getNodeKind() == XdmNodeKind.DOCUMENT) { node1 = (XdmNode) node1.axisIterator(Axis.CHILD).next(); } } Configuration config = catalog.getProcessor().getUnderlyingConfiguration(); XPathContext conversionContext = config.getConversionContext(); return DeepEqual.deepEquals( node1.getUnderlyingNode().iterate(), node2.getUnderlyingNode().iterate(), new GenericAtomicComparer(CodepointCollator.getInstance(), conversionContext), conversionContext, DeepEqual.INCLUDE_PREFIXES | DeepEqual.EXCLUDE_WHITESPACE_TEXT_NODES | DeepEqual.INCLUDE_COMMENTS | DeepEqual.COMPARE_STRING_VALUES | DeepEqual.INCLUDE_PROCESSING_INSTRUCTIONS); } private String unescape (String s) { return s.replace("&", "&").replace("<", "<").replace(">",">").replace("\r\n", "\n"); } public String getPrincipalData () { return principalData; } public XdmValue getContextItem () { return contextItem; } @Override public String toString () { return "XQueryTestCase{" + name + "}"; } public boolean isExpectError() { return expectError; } public HashMap<String,VariableBinding> getExternalVariables () { return externalVariables; } static class VariableBinding { enum Type { URI, FILE }; String value; String role; Type type; } public Boolean compareResult(XdmResultSet results, XdmValue value) throws XPathException, SaxonApiException { XdmSequenceIterator iter = value.iterator(); for (Object o: results) { XdmItem result = (XdmItem) o; XdmItem item = iter.next(); if (item.isAtomicValue()) { if (! (item.getStringValue().equals(result.getStringValue()))) { System.err.println (item.getStringValue() + " is not " + result.getStringValue()); return false; } } else { XdmNode expected = nodeFor(item); XdmNode node = nodeFor((XdmItem) result); if (! areNodesEqual (expected, node)) { //System.err.println (node.toString() + " is not " + expected.toString()); return false; } } } return true; } private XdmNode nodeFor(XdmItem item) throws SaxonApiException { XdmNode itemNode; if (item.isAtomicValue()) { itemNode = createWrappedNode(item); } else { itemNode = (XdmNode) item; } return itemNode; } } /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */