All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.Query Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 Saxonica Limited
// 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/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon;

import net.sf.saxon.event.Receiver;
import net.sf.saxon.expr.instruct.TerminationException;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.*;
import net.sf.saxon.query.QueryReader;
import net.sf.saxon.query.QueryResult;
import net.sf.saxon.query.UpdateAgent;
import net.sf.saxon.query.XQueryExpression;
import net.sf.saxon.s9api.*;
import net.sf.saxon.serialize.SerializationProperties;
import net.sf.saxon.str.StringView;
import net.sf.saxon.trace.*;
import net.sf.saxon.trans.CommandLineOptions;
import net.sf.saxon.trans.LicenseException;
import net.sf.saxon.trans.Timer;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharp;
import net.sf.saxon.transpile.CSharpModifiers;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.type.ConversionResult;
import net.sf.saxon.type.SchemaException;
import net.sf.saxon.type.Type;
import net.sf.saxon.value.DateTimeValue;
import org.xml.sax.InputSource;

import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * This Query class provides a command-line interface to the Saxon XQuery processor.
 * 

The XQuery syntax supported conforms to the W3C XQuery 1.0 drafts.

* */ public class Query { private Processor processor; protected Configuration config; protected boolean showTime = false; protected int repeat = 1; protected String sourceXmlFileName = null; protected String sourceJsonFileName = null; protected String queryFileName = null; protected boolean useURLs = false; protected String outputFileName = null; protected String moduleURIResolverClass = null; protected boolean explaining = false; protected boolean wrap = false; protected boolean projection = false; protected boolean streaming = false; protected boolean updating = false; protected boolean writeback = false; protected boolean backup = true; protected String explainOutputFileName = null; private Logger traceDestination = new StandardLogger(); private boolean closeTraceDestination = false; private boolean allowExit = true; protected String languageVersion = "3.1"; /** * Get the configuration in use * * @return the configuration */ protected Configuration getConfiguration() { return config; } /** * Main program, can be used directly from the command line. *

The format is:

*

java net.sf.saxon.Query [options] query-file >output-file

*

followed by any number of parameters in the form {keyword=value}... which can be * referenced from within the query.

*

This program executes the query in query-file.

* * @param args List of arguments supplied on operating system command line */ public static void main(String[] args) { // the real work is delegated to another routine so that it can be used in a subclass new Query().doQuery(args, "java net.sf.saxon.Query"); } /** * Set the options that are recognized on the command line. This method can be overridden in a subclass * to define additional command line options. * * @param options the CommandLineOptions in which the recognized options are to be registered. */ void setPermittedOptions(CommandLineOptions options) { options.addRecognizedOption("backup", CommandLineOptions.TYPE_BOOLEAN, "Save updated documents before overwriting"); options.addRecognizedOption("catalog", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "Use specified catalog file to resolve URIs"); options.addRecognizedOption("config", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "Use specified configuration file"); options.addRecognizedOption("cr", CommandLineOptions.TYPE_CLASSNAME | CommandLineOptions.VALUE_REQUIRED, "Use specified collection URI resolver class"); options.addRecognizedOption("dtd", CommandLineOptions.TYPE_ENUMERATION, "Validate using DTD"); options.setPermittedValues("dtd", new String[]{"on", "off", "recover"}, "on"); options.addRecognizedOption("expand", CommandLineOptions.TYPE_BOOLEAN, "Expand attribute defaults from DTD or Schema"); options.addRecognizedOption("explain", CommandLineOptions.TYPE_FILENAME, "Display compiled expression tree and optimization decisions"); options.addRecognizedOption("ext", CommandLineOptions.TYPE_BOOLEAN, "Allow calls to Java extension functions and xsl:result-document"); options.addRecognizedOption("init", CommandLineOptions.TYPE_CLASSNAME, "User-supplied code to initialize the Saxon configuration"); options.addRecognizedOption("json", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "Source file for primary JSON input"); options.addRecognizedOption("l", CommandLineOptions.TYPE_BOOLEAN, "Maintain line numbers for source documents"); options.addRecognizedOption("mr", CommandLineOptions.TYPE_CLASSNAME | CommandLineOptions.VALUE_REQUIRED, "Use named ModuleURIResolver class"); options.addRecognizedOption("now", CommandLineOptions.TYPE_DATETIME | CommandLineOptions.VALUE_REQUIRED, "Run with specified current date/time"); options.addRecognizedOption("o", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "Use specified file for primary output"); options.addRecognizedOption("opt", CommandLineOptions.TYPE_STRING | CommandLineOptions.VALUE_REQUIRED, "Enable/disable optimization options [-]cfgklmnsvwx"); options.addRecognizedOption("outval", CommandLineOptions.TYPE_ENUMERATION | CommandLineOptions.VALUE_REQUIRED, "Action when validation of output file fails"); options.setPermittedValues("outval", new String[]{"recover", "fatal"}, null); options.addRecognizedOption("p", CommandLineOptions.TYPE_BOOLEAN, "Recognize query parameters in URI passed to doc()"); options.addRecognizedOption("projection", CommandLineOptions.TYPE_BOOLEAN, "Use source document projection"); options.addRecognizedOption("q", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "Query filename"); options.addRecognizedOption("qs", CommandLineOptions.TYPE_STRING | CommandLineOptions.VALUE_REQUIRED, "Query string (usually in quotes)"); options.addRecognizedOption("quit", CommandLineOptions.TYPE_BOOLEAN | CommandLineOptions.VALUE_REQUIRED, "Quit JVM if query fails"); options.addRecognizedOption("qversion", CommandLineOptions.TYPE_STRING | CommandLineOptions.VALUE_REQUIRED, "XQuery language version: 3.1 or 4.0. Default is 3.1"); options.setPermittedValues("qversion", new String[]{"3.1", "4.0"}, "3.1"); options.addRecognizedOption("r", CommandLineOptions.TYPE_CLASSNAME | CommandLineOptions.VALUE_REQUIRED, "Use named URIResolver class"); options.addRecognizedOption("repeat", CommandLineOptions.TYPE_INTEGER | CommandLineOptions.VALUE_REQUIRED, "Run N times for performance measurement"); options.addRecognizedOption("s", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "Source file for primary XML input"); options.addRecognizedOption("sa", CommandLineOptions.TYPE_BOOLEAN, "Run in schema-aware mode"); options.addRecognizedOption("scmin", CommandLineOptions.TYPE_FILENAME, "Pre-load schema in SCM format"); options.addRecognizedOption("stream", CommandLineOptions.TYPE_BOOLEAN, "Execute in streamed mode"); options.addRecognizedOption("strip", CommandLineOptions.TYPE_ENUMERATION | CommandLineOptions.VALUE_REQUIRED, "Handling of whitespace text nodes in source documents"); options.setPermittedValues("strip", new String[]{"none", "all", "ignorable"}, null); options.addRecognizedOption("t", CommandLineOptions.TYPE_BOOLEAN, "Display version and timing information"); options.addRecognizedOption("T", CommandLineOptions.TYPE_CLASSNAME, "Use named TraceListener class, or standard TraceListener"); options.addRecognizedOption("TJ", CommandLineOptions.TYPE_BOOLEAN, "Debug binding and execution of extension functions"); options.setPermittedValues("TJ", new String[]{"on", "off"}, "on"); options.addRecognizedOption("tree", CommandLineOptions.TYPE_ENUMERATION | CommandLineOptions.VALUE_REQUIRED, "Use specified tree model for source documents"); options.addRecognizedOption("Tlevel", CommandLineOptions.TYPE_STRING, "Level of detail for trace listener output"); options.setPermittedValues("Tlevel", new String[]{"none", "low", "normal", "high"}, "normal"); options.addRecognizedOption("Tout", CommandLineOptions.TYPE_FILENAME, "File for trace listener output"); options.addRecognizedOption("TP", CommandLineOptions.TYPE_FILENAME, "Use profiling trace listener, with specified output file"); options.addRecognizedOption("TPxsl", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "Stylesheet for formatting -TP output"); options.addRecognizedOption("traceout", CommandLineOptions.TYPE_FILENAME | CommandLineOptions.VALUE_REQUIRED, "File for output of trace() messages"); options.setPermittedValues("tree", new String[]{"linked", "tiny", "tinyc"}, null); options.addRecognizedOption("u", CommandLineOptions.TYPE_BOOLEAN, "Interpret filename arguments as URIs"); options.setPermittedValues("u", new String[]{"on", "off"}, "on"); options.addRecognizedOption("update", CommandLineOptions.TYPE_ENUMERATION | CommandLineOptions.VALUE_REQUIRED, "Enable or disable XQuery updates, or enable the syntax but discard the updates"); options.setPermittedValues("update", new String[]{"on", "off", "discard"}, null); options.addRecognizedOption("val", CommandLineOptions.TYPE_ENUMERATION, "Apply validation to source documents"); options.setPermittedValues("val", new String[]{"strict", "lax"}, "strict"); options.addRecognizedOption("wrap", CommandLineOptions.TYPE_BOOLEAN, "Wrap result sequence in XML elements"); options.addRecognizedOption("x", CommandLineOptions.TYPE_CLASSNAME | CommandLineOptions.VALUE_REQUIRED, "Use named XMLReader class for parsing source documents"); options.addRecognizedOption("xi", CommandLineOptions.TYPE_BOOLEAN, "Expand XInclude directives in source documents"); options.addRecognizedOption("xmlversion", CommandLineOptions.TYPE_ENUMERATION | CommandLineOptions.VALUE_REQUIRED, "Indicate whether XML 1.1 is supported"); options.setPermittedValues("xmlversion", new String[]{"1.0", "1.1"}, null); options.addRecognizedOption("xsd", CommandLineOptions.TYPE_FILENAME_LIST | CommandLineOptions.VALUE_REQUIRED, "List of schema documents to be preloaded"); options.addRecognizedOption("xsdversion", CommandLineOptions.TYPE_ENUMERATION | CommandLineOptions.VALUE_REQUIRED, "Indicate whether XSD 1.1 is supported"); options.setPermittedValues("xsdversion", new String[]{"1.0", "1.1"}, null); options.addRecognizedOption("xsiloc", CommandLineOptions.TYPE_BOOLEAN, "Load schemas named in xsi:schemaLocation (default on)"); options.addRecognizedOption("?", CommandLineOptions.VALUE_PROHIBITED, "Display command line help text"); } /** * Support method for main program. This support method can also be invoked from subclasses * that support the same command line interface * * @param args the command-line arguments * @param command name of the class, to be used in error messages */ @SuppressWarnings("ResultOfMethodCallIgnored") public void doQuery(String[] args, String command) { CommandLineOptions options = new CommandLineOptions(); setPermittedOptions(options); try { options.setActualOptions(args); } catch (XPathException err) { quit(err.getMessage(), 2); } boolean schemaAware = false; String configFile = options.getOptionValue("config"); if (configFile != null) { try { config = Configuration.readConfiguration(new StreamSource(configFile)); initializeConfiguration(config); schemaAware = config.isLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XQUERY); } catch (XPathException e) { quit(e.getMessage(), 2); } } if (config == null && !schemaAware) { schemaAware = options.testIfSchemaAware(); } if (config == null) { config = Configuration.newConfiguration(); initializeConfiguration(config); } //config.setHostLanguage(Configuration.XQUERY); processor = new Processor(config); config.setProcessor(processor); // Check the command-line arguments. try { parseOptions(options); XQueryCompiler compiler = processor.newXQueryCompiler(); compiler.setSchemaAware(schemaAware); compiler.setLanguageVersion(languageVersion); if (updating) { compiler.setUpdatingEnabled(true); } if (moduleURIResolverClass != null) { Object mr = config.getInstance(moduleURIResolverClass); if (!(mr instanceof ModuleURIResolver)) { badUsage(moduleURIResolverClass + " is not a ModuleURIResolver"); } if (mr instanceof StandardModuleURIResolver) { ((StandardModuleURIResolver) mr).setConfiguration(config); } compiler.setModuleURIResolver((ModuleURIResolver) mr); } config.displayLicenseMessage(); if (schemaAware && !config.isLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XQUERY)) { if ("EE".equals(config.getEditionCode())) { quit("Installed license does not allow schema-aware query", 2); } else { quit("Schema-aware query requires Saxon Enterprise Edition", 2); } } if (explaining) { config.setBooleanProperty(Feature.TRACE_OPTIMIZER_DECISIONS, true); } compiler.setStreaming(streaming); Source sourceInput = null; if (sourceXmlFileName != null) { sourceInput = processSourceFile(sourceXmlFileName, useURLs); } long startTime = System.nanoTime(); if (showTime) { config.getLogger().info("Analyzing query from " + queryFileName); } // Compile the query XQueryExecutable exp = null; try { exp = compileQuery(compiler, queryFileName, useURLs); if (showTime) { long endTime = System.nanoTime(); config.getLogger().info("Analysis time: " + ((endTime - startTime) / 1e6) + " milliseconds"); startTime = endTime; } } catch (SaxonApiException e) { if (e.getCause() instanceof XPathException) { XPathException err = (XPathException) e.getCause(); int line = -1; String module = null; if (err.getLocator() != null) { line = err.getLocator().getLineNumber(); module = err.getLocator().getSystemId(); } if (err.hasBeenReported()) { quit("Static error(s) in query", 2); } else { if (line == -1) { config.getLogger().error("Static error in query: " + err.getMessage()); } else { config.getLogger().error("Static error at line " + line + " of " + module + ':'); config.getLogger().error(err.getMessage()); } } exp = null; if (allowExit) { System.exit(2); } else { throw new RuntimeException(err.getMessage()); } } else { quit(e.getMessage(), 2); } } if (explaining && exp != null) { Serializer out; if (explainOutputFileName == null || explainOutputFileName.equals("")) { out = processor.newSerializer(System.err); } else { out = processor.newSerializer(new File(explainOutputFileName)); } out.setOutputProperty(Serializer.Property.METHOD, "xml"); out.setOutputProperty(Serializer.Property.INDENT, "yes"); out.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes"); if (processor.equals(processor.getSaxonEdition())) { out.setOutputProperty(Serializer.Property.SAXON_INDENT_SPACES, "2"); } exp.explain(out); } // Load the source file (applying document projection if requested) exp.getUnderlyingCompiledQuery().setAllowDocumentProjection(projection); final XQueryEvaluator evaluator = exp.load(); evaluator.setTraceFunctionDestination(traceDestination); if (options.getOptionValue("now") != null) { String now = options.getOptionValue("now"); ConversionResult dt = DateTimeValue.makeDateTimeValue(StringView.tidy(now), config.getConversionRules()); if (dt instanceof DateTimeValue) { evaluator.getUnderlyingQueryContext().setCurrentDateTime((DateTimeValue) dt); } else { config.getLogger().warning("Invalid dateTime: " + now + " (ignored)"); } } processSource(sourceInput, exp, evaluator); if (sourceJsonFileName != null) { try { Reader jsonReader = new BufferedReader( new InputStreamReader(new FileInputStream(sourceJsonFileName), StandardCharsets.UTF_8)); XdmValue jsonTree = parseJson(processor, jsonReader); if (!jsonTree.isEmpty()) { evaluator.setContextItem(jsonTree.itemAt(0)); } } catch (IOException e) { throw new SaxonApiException("Cannot read JSON input: " + e.getMessage()); } } // Supply query parameters options.setParams(processor, CSharp.methodRef(evaluator::setExternalVariable)); // Run the query (repeatedly, if the -repeat option was set) startTime = System.nanoTime(); long totalTime = 0; int r; for (r = 0; r < repeat; r++) { // repeat is for internal testing/timing try { Serializer serializer; if (outputFileName != null) { File outputFile = new File(outputFileName); if (outputFile.isDirectory()) { quit("Output is a directory", 2); } serializer = processor.newSerializer(outputFile); } else { serializer = processor.newSerializer(System.out); } try { options.setSerializationProperties(serializer); } catch (IllegalArgumentException e) { quit(e.getMessage(), 2); } if (updating && exp.isUpdateQuery()) { serializer.setOutputProperties( exp.getUnderlyingCompiledQuery().getExecutable().getPrimarySerializationProperties().getProperties()); runUpdate(exp, evaluator, serializer); } else { runQuery(exp, evaluator, sourceInput, serializer); } } catch (SaxonApiException err) { if (err.getCause() instanceof XPathException && ((XPathException) err.getCause()).hasBeenReported()) { String category = ((XPathException) err.getCause()).isTypeError() ? "type" : "dynamic"; quit("Query failed with " + category + " error: " + err.getCause().getMessage(), 2); } else { throw err; } } long endTime = System.nanoTime(); if (r >= 3) { totalTime += endTime - startTime; } if (showTime) { if (repeat < 100) { config.getLogger().info("Execution time: " + Timer.showExecutionTimeNano(endTime - startTime)); config.getLogger().info(Timer.showMemoryUsed()); Instrumentation.report(); } else if (totalTime > 1000000000000L) { // quit after 1000 seconds break; } } startTime = endTime; } if (repeat > 3) { config.getLogger().info("Average execution time: " + Timer.showExecutionTimeNano(totalTime / (r - 3))); } } catch (TerminationException err) { quit(err.getMessage(), 1); } catch (SchemaException err) { quit("Schema processing failed: " + err.getMessage(), 2); } catch (XPathException | LicenseException | SaxonApiException err) { quit("Query processing failed: " + err.getMessage(), 2); } catch (TransformerFactoryConfigurationError err) { err.printStackTrace(); quit("Query processing failed", 2); } catch (Exception err2) { err2.printStackTrace(); quit("Fatal error during query: " + err2.getClass().getName() + ": " + (err2.getMessage() == null ? " (no message)" : err2.getMessage()), 2); } } @CSharpModifiers(code={"internal", "static"}) @CSharpReplaceBody(code="return new Saxon.Api.JsonBuilder(processor.getUnderlyingConfiguration()).ParseJson(jsonReader);") static XdmValue parseJson(Processor processor, Reader jsonReader) throws SaxonApiException { return processor.newJsonBuilder().parseJson(jsonReader); } /** * Customisation hook called immediately after the Configuration * object is instantiated. The intended purpose of this hook is to allow * a subclass to supply an OEM license key programmatically, but it can also * be used for other initialization of the Configuration. This method is * called before analyzing the command line options, so configuration settings * made at this stage may be overridden when the command line options are processed. * However, if a configuration file is used, the settings defined in the configuration * file will have been applied. * * @param config the Configuration object */ protected void initializeConfiguration(Configuration config) { // no action: provided for subclasses to override } /** * Parse the options supplied on the command line * * @param options the command line arguments * @throws TransformerException if failures occur. Note, the method may also invoke System.exit(). */ void parseOptions(CommandLineOptions options) throws TransformerException { // Apply those options which simply update the Configuration options.applyToConfiguration(processor); // Apply options that are processed locally allowExit = !"off".equals(options.getOptionValue("quit")); backup = "on".equals(options.getOptionValue("backup")); explainOutputFileName = options.getOptionValue("explain"); explaining = explainOutputFileName != null; moduleURIResolverClass = options.getOptionValue("mr"); outputFileName = options.getOptionValue("o"); streaming = "on".equals(options.getOptionValue("stream")); String value = options.getOptionValue("p"); if ("on".equals(value)) { config.setBooleanProperty(Feature.RECOGNIZE_URI_QUERY_PARAMETERS, true); useURLs = true; } projection = "on".equals(options.getOptionValue("projection")); value = options.getOptionValue("q"); if (value != null) { queryFileName = value; } value = options.getOptionValue("qs"); if (value != null) { queryFileName = "{" + value + "}"; } String qv = options.getOptionValue("qversion"); if (qv != null && !"3.1".equals(qv) && !"4.0".equals(qv)) { config.getLogger().warning("-qversion ignored: 3.1 is assumed"); } else if ("4.0".equals(qv)) { languageVersion = "4.0"; } value = options.getOptionValue("repeat"); if (value != null) { try { repeat = Integer.parseInt(value); } catch (NumberFormatException err) { badUsage("Bad number after -repeat"); } } sourceXmlFileName = options.getOptionValue("s"); sourceJsonFileName = options.getOptionValue("json"); value = options.getOptionValue("t"); if ("on".equals(value)) { config.getLogger().info(config.getProductTitle()); config.getLogger().info(Version.platform.getPlatformVersion()); config.setTiming(true); showTime = true; } value = options.getOptionValue("traceout"); if (value != null) { switch (value) { case "#err": // no action, this is the default break; case "#out": traceDestination = new StandardLogger(System.out); break; case "#null": traceDestination = null; break; default: try { traceDestination = new StandardLogger(new File(value)); closeTraceDestination = true; } catch (FileNotFoundException e) { badUsage("Trace output file " + value + " cannot be created"); } break; } } value = options.getOptionValue("T"); if (value != null) { if ("".equals(value)) { makeXQueryTraceListener(options); } else { config.setTraceListenerClass(value); } config.setLineNumbering(true); } value = options.getOptionValue("Tout"); if (value != null) { config.setTraceListenerOutputFile(value); if (options.getOptionValue("T") == null) { makeXQueryTraceListener(options); } } value = options.getOptionValue("TP"); if (value != null) { TimingTraceListener traceListener = new TimingTraceListener(); config.setTraceListener(traceListener); config.setLineNumbering(true); config.getDefaultStaticQueryContext().setCodeInjector(new TimingCodeInjector()); if (!value.isEmpty()) { try { traceListener.setOutputDestination( new StandardLogger(new File(value))); } catch (FileNotFoundException e) { badUsage("Trace output file " + value + " cannot be created"); } } String formatter = options.getOptionValue("TPxsl"); if (formatter != null) { try { if (useURLs) { traceListener.setStylesheet(new URL(formatter)); } else { traceListener.setStylesheet(new File(formatter).toURI().toURL()); } } catch (MalformedURLException e) { System.err.println("Invalid URL " + formatter + " - ignored"); } } } value = options.getOptionValue("u"); if (value != null) { useURLs = "on".equals(value); } value = options.getOptionValue("update"); if (value != null) { if (!"off".equals(value)) { updating = true; } writeback = !"discard".equals(value); } wrap = "on".equals(options.getOptionValue("wrap")); value = options.getOptionValue("x"); if (value != null) { config.setSourceParserClass(value); } String additionalSchemas = options.getOptionValue("xsd"); value = options.getOptionValue("?"); if (value != null) { badUsage(""); } if (options.getOptionValue("xsiloc") != null && options.getOptionValue("val") == null) { config.getLogger().warning("-xsiloc is ignored when -val is absent"); } if (sourceXmlFileName != null && sourceJsonFileName != null) { badUsage("Cannot supply both -s and -json"); } // Apply options defined locally in a subclass applyLocalOptions(options, config); // Apply positional options List positional = options.getPositionalOptions(); int currentPositionalOption = 0; if (queryFileName == null) { if (positional.size() == currentPositionalOption) { badUsage("No query file name"); } queryFileName = positional.get(currentPositionalOption++); } if (currentPositionalOption < positional.size()) { badUsage("Unrecognized option: " + positional.get(currentPositionalOption)); } String scmInput = options.getOptionValue("scmin"); if (scmInput != null) { config.importComponents(new StreamSource(scmInput)); } if (additionalSchemas != null) { CommandLineOptions.loadAdditionalSchemas(config, additionalSchemas); } } private void makeXQueryTraceListener(CommandLineOptions options) { XQueryTraceListener listener = new XQueryTraceListener(); String value = options.getOptionValue("Tout"); if (value != null) { try { listener.setOutputDestination(new StandardLogger(new PrintStream(value))); } catch (FileNotFoundException e) { badUsage("Cannot write to " + value); } } value = options.getOptionValue("Tlevel"); if (value != null) { switch (value) { case "none": listener.setLevelOfDetail(0); break; case "low": listener.setLevelOfDetail(1); break; case "normal": listener.setLevelOfDetail(2); break; case "high": listener.setLevelOfDetail(3); break; } } config.setTraceListener(listener); } /** * Customisation hook: apply options defined locally in a subclass * * @param options the CommandLineOptions * @param config the Saxon Configuration */ @SuppressWarnings("EmptyMethod") @CSharpModifiers(code={"internal"}) protected void applyLocalOptions(CommandLineOptions options, Configuration config) { // no action: provided for subclasses to override } /*@Nullable*/ protected Source processSourceFile(String sourceFileName, boolean useURLs) throws TransformerException { Source sourceInput; if (useURLs || CommandLineOptions.isImplicitURI(sourceFileName)) { ResourceRequest request = new ResourceRequest(); request.relativeUri = sourceFileName; URI cwd = new File(System.getProperty("user.dir")).toURI(); request.baseUri = cwd.toString(); request.uri = cwd.resolve(sourceFileName).toString(); request.nature = ResourceRequest.XML_NATURE; request.purpose = ResourceRequest.ANY_PURPOSE; sourceInput = request.resolve(config.getResourceResolver()); } else if (sourceFileName.equals("-")) { // take input from stdin String sysId = new File(System.getProperty("user.dir")).toURI().toASCIIString(); sourceInput = new StreamSource(System.in, sysId); } else { File sourceFile = new File(sourceFileName); if (!sourceFile.exists()) { quit("Source file " + sourceFile + " does not exist", 2); } if (Version.platform.isJava()) { InputSource eis = new InputSource(sourceFile.toURI().toString()); sourceInput = new SAXSource(eis); } else { sourceInput = new StreamSource(sourceFile.toURI().toString()); } } return sourceInput; } /** * Compile the query * * @param compiler the XQuery compiler * @param queryFileName the filename holding the query (or "-" for the standard input, * or the actual query text enclosed in curly braces * @param useURLs true if the filename is in the form of a URI * @return the compiled query * @throws SaxonApiException if query compilation fails * @throws IOException if the query cannot be read */ /*@Nullable*/ @CSharpModifiers(code={"internal"}) protected XQueryExecutable compileQuery(XQueryCompiler compiler, String queryFileName, boolean useURLs) throws SaxonApiException, IOException { XQueryExecutable exp; if (queryFileName.equals("-")) { Reader queryReader = standardInputReader(); compiler.setBaseURI(new File(System.getProperty("user.dir")).toURI()); exp = compiler.compile(queryReader); } else if (queryFileName.startsWith("{") && queryFileName.endsWith("}")) { // query is inline on the command line String q = queryFileName.substring(1, queryFileName.length() - 1); compiler.setBaseURI(new File(System.getProperty("user.dir")).toURI()); exp = compiler.compile(q); } else if (useURLs || CommandLineOptions.isImplicitURI(queryFileName)) { ModuleURIResolver resolver = compiler.getModuleURIResolver(); boolean isStandardResolver = false; if (resolver == null) { resolver = getConfiguration().getStandardModuleURIResolver(); isStandardResolver = true; } while (true) { String[] locations = {queryFileName}; Source[] sources; try { sources = resolver.resolve(null, null, locations); } catch (Exception e) { if (e instanceof XPathException) { throw new SaxonApiException(e); } else { XPathException xe = new XPathException("Exception in ModuleURIResolver: ", e); xe.setErrorCode("XQST0059"); throw new SaxonApiException(xe); } } if (sources == null) { if (isStandardResolver) { // this should not happen quit("System problem: standard ModuleURIResolver returned null", 4); } else { resolver = getConfiguration().getStandardModuleURIResolver(); isStandardResolver = true; } } else { if (sources.length != 1 || !(sources[0] instanceof StreamSource)) { quit("Module URI Resolver must return a single StreamSource", 2); } try { String queryText = QueryReader.readSourceQuery((StreamSource) sources[0], config.getValidCharacterChecker()); exp = compiler.compile(queryText); } catch (XPathException e) { throw new SaxonApiException(e); } break; } } } else { //noinspection EmptyFinallyBlock try (InputStream queryStream = new FileInputStream(queryFileName)) { compiler.setBaseURI(new File(queryFileName).toURI()); exp = compiler.compile(queryStream); } finally { // No action (here for C# conversion) } } return exp; } @CSharpReplaceBody(code="return System.Console.In;") private Reader standardInputReader() { return new InputStreamReader(System.in); } /** * Explain the results of query compilation * * @param exp the compiled expression * @throws FileNotFoundException if the destination for the explanation doesn't exist * @throws XPathException if other failures occur */ protected void explain(XQueryExpression exp) throws FileNotFoundException, XPathException { StreamResult explainOutput; if (explainOutputFileName == null || "".equals(explainOutputFileName)) { explainOutput = standardErrorResult(); } else { explainOutput = fileResult(explainOutputFileName); } SerializationProperties props = ExpressionPresenter.makeDefaultProperties(config); Receiver diag = config.getSerializerFactory().getReceiver(explainOutput, props); ExpressionPresenter expressionPresenter = new ExpressionPresenter(config, diag); exp.explain(expressionPresenter); } private StreamResult standardErrorResult() { return new StreamResult(System.err); } private StreamResult fileResult(String fileName) throws FileNotFoundException { return new StreamResult(new FileOutputStream(fileName)); } /** * Process the supplied XML source file * * @param sourceInput the supplied source * @param exp the compiled XQuery expression * @param evaluator the dynamic query context * @throws SaxonApiException if processing fails */ @CSharpModifiers(code = {"internal"}) protected void processSource(/*@Nullable*/ Source sourceInput, XQueryExecutable exp, XQueryEvaluator evaluator) throws SaxonApiException { if (sourceInput != null && !streaming) { DocumentBuilder builder = processor.newDocumentBuilder(); if (exp.isUpdateQuery()) { builder.setTreeModel(TreeModel.LINKED_TREE); } if (showTime) { config.getLogger().info("Processing " + sourceInput.getSystemId()); } if (!exp.getUnderlyingCompiledQuery().usesContextItem()) { config.getLogger().warning("Source document ignored - query can be evaluated without reference to the context item"); return; } if (projection) { builder.setDocumentProjectionQuery(exp); if (explaining) { exp.getUnderlyingCompiledQuery().explainPathMap(); } } builder.setDTDValidation(getConfiguration().getBooleanProperty(Feature.DTD_VALIDATION)); if (getConfiguration().getBooleanProperty(Feature.DTD_VALIDATION_RECOVERABLE)) { sourceInput = new AugmentedSource(sourceInput, getConfiguration().getParseOptions()); } XdmNode doc = builder.build(sourceInput); evaluator.setContextItem(doc); } } /** * Run the query * * @param exp the compiled query expression * @param evaluator the dynamic query context * @param input the supplied XML source if any * @param destination the destination for serialized results * @throws SaxonApiException if the query fails */ @CSharpModifiers(code = {"internal"}) protected void runQuery(XQueryExecutable exp, XQueryEvaluator evaluator, Source input, Destination destination) throws SaxonApiException { try { if (wrap) { try { XQueryExpression e = exp.getUnderlyingCompiledQuery(); SequenceIterator results = e.iterator(evaluator.getUnderlyingQueryContext()); NodeInfo resultDoc = QueryResult.wrap(results, config); XdmValue wrappedResultDoc = XdmValue.wrap(resultDoc); processor.writeXdmValue(wrappedResultDoc, destination); destination.closeAndNotify(); } catch (XPathException e1) { throw new SaxonApiException(e1); } } else if (streaming) { evaluator.runStreamed(input, destination); } else { evaluator.run(destination); } } finally { if (closeTraceDestination && traceDestination != null) { traceDestination.close(); } } } /** * Run an updating query * * @param exp the compiled query expression * @param evaluator the query evaluator * @param serializer the destination for serialized results * @throws SaxonApiException if the query fails */ @CSharpModifiers(code = {"internal"}) protected void runUpdate(XQueryExecutable exp, XQueryEvaluator evaluator, final Serializer serializer) throws SaxonApiException { try { if (serializer.getOutputProperty(Serializer.Property.METHOD) == null) { serializer.setOutputProperty(Serializer.Property.METHOD, "xml"); } if (writeback) { final List errors = new ArrayList<>(3); UpdateAgent agent = (node, controller) -> { try { DocumentPool pool = controller.getDocumentPool(); String documentURI = pool.getDocumentURI(node); if (documentURI != null) { rewriteToDisk(node, serializer, backup, showTime ? config.getLogger() : null); } else if (showTime) { config.getLogger().warning("Updated document discarded because it was not read using doc()"); } } catch (SaxonApiException err) { config.getLogger().warning(err.getMessage()); errors.add(err); } }; evaluator.run(); try { exp.getUnderlyingCompiledQuery().runUpdate(evaluator.getUnderlyingQueryContext(), agent); } catch (XPathException e) { throw new SaxonApiException(e); } if (!errors.isEmpty()) { throw errors.get(0); } } else { try { if (evaluator.getContextItem() != null) { Set affectedDocuments = exp.getUnderlyingCompiledQuery().runUpdate(evaluator.getUnderlyingQueryContext()); Item initial = evaluator.getContextItem().getUnderlyingValue().head(); //noinspection SuspiciousMethodCalls if (initial instanceof NodeInfo && affectedDocuments.contains(initial)) { processor.writeXdmValue(evaluator.getContextItem(), serializer); } } } catch (XPathException e) { throw new SaxonApiException(e); } } } finally { if (closeTraceDestination && traceDestination != null) { traceDestination.close(); } } } /** * Write an updated document back to disk, using the original URI from which it was read * * @param doc an updated document. Must be a document node, or a parentless element, or an * element that has a document node as its parent. The document will be written to the URI * found in the systemId property of this node. * @param serializer serialization properties * @param backup true if the old document at that location is to be copied to a backup file * @param logger destination for progress messages; if null, no progress messages are written * @throws SaxonApiException if the document has no known URI, if the URI is not a writable location, * or if a serialization error occurs. */ @SuppressWarnings("ResultOfMethodCallIgnored") private static void rewriteToDisk(NodeInfo doc, Serializer serializer, boolean backup, Logger logger) throws SaxonApiException { switch (doc.getNodeKind()) { case Type.DOCUMENT: // OK break; case Type.ELEMENT: NodeInfo parent = doc.getParent(); if (parent != null && parent.getNodeKind() != Type.DOCUMENT) { throw new SaxonApiException("Cannot rewrite an element node unless it is top-level"); } break; default: throw new SaxonApiException("Node to be rewritten must be a document or element node"); } String uri = doc.getSystemId(); if (uri == null || uri.isEmpty()) { throw new SaxonApiException("Cannot rewrite a document with no known URI"); } URI u; try { u = new URI(uri); } catch (URISyntaxException e) { throw new SaxonApiException("SystemId of updated document is not a valid URI: " + uri); } File existingFile = new File(u); File dir = existingFile.getParentFile(); if (backup && existingFile.exists()) { File backupFile = new File(dir, existingFile.getName() + ".bak"); if (logger != null) { logger.info("Creating backup file " + backupFile); } boolean success = existingFile.renameTo(backupFile); if (!success) { throw new SaxonApiException("Failed to create backup file of " + backupFile); } } if (!existingFile.exists()) { if (logger != null) { logger.info("Creating file " + existingFile); } try { existingFile.createNewFile(); } catch (IOException e) { throw new SaxonApiException("Failed to create new file " + existingFile); } } else { if (logger != null) { logger.info("Overwriting file " + existingFile); } } serializer.setOutputFile(existingFile); serializer.getProcessor().writeXdmValue(XdmValue.wrap(doc), serializer); } /** * Exit with a message * * @param message The message to be output * @param code The result code to be returned to the operating * system shell */ protected void quit(String message, int code) { if (config != null && config.getLogger() != null) { config.getLogger().warning(message); } else { System.err.println(message); } if (allowExit) { System.exit(code); } else { throw new RuntimeException(message); } } /** * Report incorrect usage of the command line, with a list of the options and arguments that are available * * @param message The error message */ protected void badUsage(String message) { Logger logger = config.getLogger(); if (!"".equals(message)) { logger.error(message); } if (!showTime) { logger.info(config.getProductTitle()); } logger.info("Usage: see http://www.saxonica.com/documentation/index.html#!using-xquery/commandline"); logger.info("Format: " + CommandLineOptions.getCommandName(this) + " options params"); CommandLineOptions options = new CommandLineOptions(); setPermittedOptions(options); logger.info("Options available:" + options.displayPermittedOptions()); logger.info("Use -XYZ:? for details of option XYZ or --? to list configuration features"); logger.info("Params: "); logger.info(" param=value Set query string parameter"); logger.info(" +param=filename Set query document parameter"); logger.info(" ?param=expression Set query parameter using XPath"); logger.info(" !param=value Set serialization parameter"); if (allowExit) { System.exit("".equals(message) ? 0 : 2); } else { throw new RuntimeException(message); } } /** * Utility method to create a file if it does not already exist, including creation of any * necessary directories named in the file path * @param file the file that is required to exist * @throws IOException if file creation fails */ @SuppressWarnings("ResultOfMethodCallIgnored") public static void createFileIfNecessary(File file) throws IOException { if (!file.exists()) { File directory = file.getParentFile(); if (directory != null && !directory.exists()) { directory.mkdirs(); } file.createNewFile(); } } private String getCommandName() { String s = getClass().getName(); if (s.equals("cli.Saxon.Cmd.DotNetQuery")) { s = "Query"; } return s; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy