net.sf.saxon.trans.CommandLineOptions Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 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.trans;
import net.sf.saxon.Configuration;
import net.sf.saxon.event.Builder;
import net.sf.saxon.lib.*;
import net.sf.saxon.s9api.*;
import net.sf.saxon.transpile.CSharpDelegate;
import net.sf.saxon.transpile.CSharpModifiers;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.type.SchemaException;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xmlresolver.ResolverFeature;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.text.Collator;
import java.util.*;
/**
* This is a helper class for classes such as net.sf.saxon.Transform and net.sf.saxon.Query that process
* command line options
*/
@CSharpModifiers(code = {"internal"})
public class CommandLineOptions {
public static final int TYPE_BOOLEAN = 1;
public static final int TYPE_FILENAME = 2;
public static final int TYPE_CLASSNAME = 3;
public static final int TYPE_ENUMERATION = 4;
public static final int TYPE_INTEGER = 5;
public static final int TYPE_QNAME = 6;
public static final int TYPE_FILENAME_LIST = 7;
public static final int TYPE_DATETIME = 8;
public static final int TYPE_STRING = 9;
public static final int TYPE_INTEGER_PAIR = 10;
public static final int VALUE_REQUIRED = 1 << 8;
public static final int VALUE_PROHIBITED = 2 << 8;
private final HashMap recognizedOptions = new HashMap<>();
private final HashMap optionHelp = new HashMap<>();
private final Properties namedOptions = new Properties();
private final Properties configOptions = new Properties();
private final Map> permittedValues = new HashMap<>();
private final Map defaultValues = new HashMap<>();
private final List positionalOptions = new ArrayList<>();
private final Properties paramValues = new Properties();
private final Properties paramExpressions = new Properties();
private final Properties paramFiles = new Properties();
private final Properties serializationParams = new Properties();
/**
* Set the permitted options.
*
* @param option A permitted option.
* @param optionProperties of this option, for example whether it is mandatory
* @param helpText message to be output if the user needs help concerning this option
*/
public void addRecognizedOption(String option, int optionProperties, String helpText) {
recognizedOptions.put(option, optionProperties);
optionHelp.put(option, helpText);
if ((optionProperties & 0xff) == TYPE_BOOLEAN) {
setPermittedValues(option, new String[]{"on", "off"}, "on");
}
}
/**
* Set the permitted values for an option
*
* @param option the option keyword
* @param values the set of permitted values
* @param defaultValue the default value if the option is supplied but no value is given. May be null if no
* default is defined.
*/
public void setPermittedValues(String option, String[] values, /*@Nullable*/ String defaultValue) {
Set valueSet = new HashSet<>(Arrays.asList(values));
permittedValues.put(option, valueSet);
if (defaultValue != null) {
defaultValues.put(option, defaultValue);
}
}
/**
* Display a list of the values permitted for an option with type enumeration
*
* @param permittedValues the set of permitted values
* @return the set of values as a string, pipe-separated
*/
private static String displayPermittedValues(/*@NotNull*/ Set permittedValues) {
StringBuilder sb = new StringBuilder(20);
for (String val : permittedValues) {
if ("".equals(val)) {
sb.append("\"\"");
} else {
sb.append(val);
}
sb.append('|');
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* Set the options actually present on the command line
*
* @param args the options supplied on the command line
* @throws XPathException if an unrecognized or invalid option is found
*/
public void setActualOptions(/*@NotNull*/ String[] args) throws XPathException {
for (String arg : args) {
if ("-".equals(arg)) {
positionalOptions.add(arg);
} else if (arg.equals("--?")) {
System.err.println("Configuration features:" + featureKeys());
} else if (arg.charAt(0) == '-') {
String option;
String value = "";
if (arg.length() > 5 && arg.charAt(1) == '-') {
// --featureKey:value format
int colon = arg.indexOf(':');
if (colon > 0 && colon < arg.length() - 1) {
option = arg.substring(2, colon);
value = arg.substring(colon + 1);
configOptions.setProperty(option, value);
} else if (colon > 0 && colon == arg.length() - 1) {
option = arg.substring(2, colon);
configOptions.setProperty(option, "");
} else {
option = arg.substring(2);
configOptions.setProperty(option, "true");
}
} else {
int colon = arg.indexOf(':');
if (colon > 0 && colon < arg.length() - 1) {
option = arg.substring(1, colon);
value = arg.substring(colon + 1);
} else {
option = arg.substring(1);
}
if (recognizedOptions.getOrDefault(option, -1) == -1) {
throw new XPathException("Command line option -" + option +
" is not recognized. Options available: " + displayPermittedOptions());
}
if (namedOptions.getProperty(option) != null) {
throw new XPathException("Command line option -" + option + " appears more than once");
} else if ("?".equals(value)) {
displayOptionHelp(option);
throw new XPathException("No processing requested");
} else {
if ("".equals(value)) {
int prop = recognizedOptions.get(option);
if ((prop & VALUE_REQUIRED) != 0) {
String msg = "Command line option -" + option + " requires a value";
if (permittedValues.get(option) != null) {
msg += ": permitted values are " + displayPermittedValues(permittedValues.get(option));
}
throw new XPathException(msg);
}
String defaultValue = defaultValues.get(option);
if (defaultValue != null) {
value = defaultValue;
}
} else {
int prop = recognizedOptions.get(option);
if ((prop & VALUE_PROHIBITED) != 0) {
String msg = "Command line option -" + option + " does not expect a value";
throw new XPathException(msg);
}
}
Set permitted = permittedValues.get(option);
if (permitted != null && !permitted.contains(value)) {
throw new XPathException("Bad option value " + arg +
": permitted values are " + displayPermittedValues(permitted));
}
namedOptions.setProperty(option, value);
}
}
} else {
// handle keyword=value options
int eq = arg.indexOf('=');
if (eq >= 1) {
String keyword = arg.substring(0, eq);
String value = "";
if (eq < arg.length() - 1) {
value = arg.substring(eq + 1);
}
char ch = arg.charAt(0);
if (ch == '!' && eq >= 2) {
serializationParams.setProperty(keyword.substring(1), value);
} else if (ch == '?' && eq >= 2) {
paramExpressions.setProperty(keyword.substring(1), value);
} else if (ch == '+' && eq >= 2) {
paramFiles.setProperty(keyword.substring(1), value);
} else {
paramValues.setProperty(keyword, value);
}
} else {
positionalOptions.add(arg);
}
}
}
}
/**
* Test whether there is any keyword=value option present
*
* @return true if there are any keyword=value options
*/
public boolean definesParameterValues() {
return !serializationParams.isEmpty() ||
!paramExpressions.isEmpty() ||
!paramFiles.isEmpty() ||
!paramValues.isEmpty();
}
/**
* Prescan the command line arguments to see if any of them imply use of a schema-aware processor
*
* @return true if a schema-aware processor is needed
*/
public boolean testIfSchemaAware() {
return getOptionValue("sa") != null ||
getOptionValue("outval") != null ||
getOptionValue("val") != null ||
getOptionValue("vlax") != null ||
getOptionValue("xsd") != null ||
getOptionValue("xsdversion") != null;
}
/**
* Apply options to the Configuration
*
* @param processor the s9api Processor object
* @throws javax.xml.transform.TransformerException
* if invalid options are present
*/
public void applyToConfiguration(/*@NotNull*/ final Processor processor) throws TransformerException {
Configuration config = processor.getUnderlyingConfiguration();
for (String name : configOptions.stringPropertyNames()) {
String value = configOptions.getProperty(name);
String fullName = "http://saxon.sf.net/feature/" + name;
if (!name.startsWith("parserFeature?") && !name.startsWith("parserProperty?")) {
if (FeatureIndex.exists(fullName)) {
FeatureData f = FeatureIndex.getData(fullName);
if (f == null) {
throw new XPathException("Unknown configuration feature " + name);
}
if (f.type == Boolean.class) {
Configuration.requireBoolean(name, value);
} else if (f.type == Integer.class) {
//noinspection ResultOfMethodCallIgnored
Integer.valueOf(value);
} else if (f.type != String.class) {
throw new XPathException("Property --" + name + " cannot be supplied as a string");
}
} else {
throw new XPathException("Unknown configuration property --" + name);
}
}
try {
processor.getUnderlyingConfiguration().setConfigurationProperty(fullName, value);
} catch (IllegalArgumentException err) {
throw new XPathException("Incorrect value for --" + name + ": " + err.getMessage());
}
}
String optionValue = getOptionValue("catalog");
if (optionValue != null) {
ArrayList catalogs = new ArrayList<>();
if ((getOptionValue("u") != null) || isImplicitURI(optionValue)) {
ResourceRequest request = new ResourceRequest();
request.nature = "urn:oasis:names:tc:entity:xmlns:xml:catalog";
request.purpose = "catalog";
for (String s : optionValue.split(";")) {
Source sourceInput;
try {
request.uri = s;
sourceInput = new DirectResourceResolver(config).resolve(request);
} catch (XPathException e) {
throw new XPathException("Catalog file not found: " + s, e);
}
catalogs.add(sourceInput.getSystemId());
}
} else {
for (String s : optionValue.split(";")) {
File catalogFile = new File(s);
if (!catalogFile.exists()) {
throw new XPathException("Catalog file not found: " + s);
}
catalogs.add(catalogFile.toURI().toASCIIString());
}
}
setCatalogFiles(config, catalogs);
}
optionValue = getOptionValue("dtd");
if (optionValue != null) {
switch (optionValue) {
case "on":
config.setBooleanProperty(Feature.DTD_VALIDATION, true);
config.getParseOptions().setDTDValidationMode(Validation.STRICT);
break;
case "off":
config.setBooleanProperty(Feature.DTD_VALIDATION, false);
config.getParseOptions().setDTDValidationMode(Validation.SKIP);
break;
case "recover":
config.setBooleanProperty(Feature.DTD_VALIDATION, true);
config.setBooleanProperty(Feature.DTD_VALIDATION_RECOVERABLE, true);
config.getParseOptions().setDTDValidationMode(Validation.LAX);
break;
}
}
optionValue = getOptionValue("ea");
if (optionValue != null) {
boolean on = Configuration.requireBoolean("ea", optionValue);
config.getDefaultXsltCompilerInfo().setAssertionsEnabled(on);
}
optionValue = getOptionValue("expand");
if (optionValue != null) {
boolean on = Configuration.requireBoolean("expand", optionValue);
config.getParseOptions().setExpandAttributeDefaults(on);
}
optionValue = getOptionValue("ext");
if (optionValue != null) {
boolean on = Configuration.requireBoolean("ext", optionValue);
config.setBooleanProperty(Feature.ALLOW_EXTERNAL_FUNCTIONS, on);
}
optionValue = getOptionValue("l");
if (optionValue != null) {
boolean on = Configuration.requireBoolean("l", optionValue);
config.setBooleanProperty(Feature.LINE_NUMBERING, on);
}
optionValue = getOptionValue("m");
if (optionValue != null) {
config.setConfigurationProperty(Feature.MESSAGE_EMITTER_CLASS, optionValue);
}
optionValue = getOptionValue("opt");
if (optionValue != null) {
config.setConfigurationProperty(Feature.OPTIMIZATION_LEVEL, optionValue);
}
optionValue = getOptionValue("or");
if (optionValue != null) {
Object resolver = config.getInstance(optionValue);
if (resolver instanceof OutputURIResolver) {
config.setConfigurationProperty(Feature.OUTPUT_URI_RESOLVER, (OutputURIResolver) resolver);
} else {
throw new XPathException("Class " + optionValue + " is not an OutputURIResolver");
}
}
optionValue = getOptionValue("outval");
if (optionValue != null) {
Boolean isRecover = "recover".equals(optionValue);
config.setConfigurationProperty(Feature.VALIDATION_WARNINGS, isRecover);
config.setConfigurationProperty(Feature.VALIDATION_COMMENTS, isRecover);
}
optionValue = getOptionValue("r");
if (optionValue != null) {
config.setResourceResolver(config.makeResourceResolver(optionValue));
}
optionValue = getOptionValue("strip");
if (optionValue != null) {
config.setConfigurationProperty(Feature.STRIP_WHITESPACE, optionValue);
}
optionValue = getOptionValue("T");
if (optionValue != null) {
config.setCompileWithTracing(true);
}
optionValue = getOptionValue("TJ");
if (optionValue != null) {
boolean on = Configuration.requireBoolean("TJ", optionValue);
config.setBooleanProperty(Feature.TRACE_EXTERNAL_FUNCTIONS, on);
}
optionValue = getOptionValue("tree");
if (optionValue != null) {
switch (optionValue) {
case "linked":
config.setTreeModel(Builder.LINKED_TREE);
break;
case "tiny":
config.setTreeModel(Builder.TINY_TREE);
break;
case "tinyc":
config.setTreeModel(Builder.TINY_TREE_CONDENSED);
break;
}
}
optionValue = getOptionValue("val");
if (optionValue != null) {
if ("strict".equals(optionValue)) {
processor.setConfigurationProperty(Feature.SCHEMA_VALIDATION, Validation.STRICT);
} else if ("lax".equals(optionValue)) {
processor.setConfigurationProperty(Feature.SCHEMA_VALIDATION, Validation.LAX);
}
}
optionValue = getOptionValue("x");
if (optionValue != null) {
processor.setConfigurationProperty(Feature.SOURCE_PARSER_CLASS, optionValue);
}
optionValue = getOptionValue("xi");
if (optionValue != null) {
boolean on = Configuration.requireBoolean("xi", optionValue);
processor.setConfigurationProperty(Feature.XINCLUDE, on);
}
optionValue = getOptionValue("xmlversion");
if (optionValue != null) {
processor.setConfigurationProperty(Feature.XML_VERSION, optionValue);
}
optionValue = getOptionValue("xsdversion");
if (optionValue != null) {
processor.setConfigurationProperty(Feature.XSD_VERSION, optionValue);
}
optionValue = getOptionValue("xsiloc");
if (optionValue != null) {
boolean on = Configuration.requireBoolean("xsiloc", optionValue);
processor.setConfigurationProperty(Feature.USE_XSI_SCHEMA_LOCATION, on);
}
optionValue = getOptionValue("y");
if (optionValue != null) {
processor.setConfigurationProperty(Feature.STYLE_PARSER_CLASS, optionValue);
}
// The init option must be done last
optionValue = getOptionValue("init");
if (optionValue != null) {
Initializer initializer = (Initializer) config.getInstance(optionValue);
initializer.initialize(config);
}
}
private void setCatalogFiles(Configuration config, List catalogs) {
ResourceResolver rr = config.getResourceResolver();
if (rr instanceof ConfigurableResourceResolver) {
setCatalogFiles(((ConfigurableResourceResolver) rr), catalogs);
} else {
throw new IllegalStateException("The resolver in the Configuration is not a ConfigurableResourceResolver");
}
}
@CSharpReplaceBody(code="crr.setFeature(Org.XmlResolver.Features.ResolverFeature.CATALOG_FILES, catalogs);")
public static void setCatalogFiles(ConfigurableResourceResolver crr, List catalogs) {
crr.setFeature(ResolverFeature.CATALOG_FILES, catalogs);
}
/**
* Display the list the permitted options
*
* @return the list of permitted options, as a string
*/
public String displayPermittedOptions() {
String[] options = new String[recognizedOptions.size()];
ArrayList keys = new ArrayList<>(recognizedOptions.keySet());
options = keys.toArray(options);
Arrays.sort(options, Collator.getInstance());
StringBuilder sb = new StringBuilder(100);
for (String opt : options) {
sb.append(" -");
sb.append(opt);
}
sb.append(" --?");
return sb.toString();
}
/**
* Display help for a specific option on the System.err output (in response to -opt:?)
*
* @param option: the option for which help is required
*/
private void displayOptionHelp(String option) {
System.err.println("Help for -" + option + " option");
int prop = recognizedOptions.get(option);
if ((prop & VALUE_PROHIBITED) == 0) {
switch (prop & 0xff) {
case TYPE_BOOLEAN:
System.err.println("Value: on|off");
break;
case TYPE_INTEGER:
System.err.println("Value: integer");
break;
case TYPE_FILENAME:
System.err.println("Value: file name");
break;
case TYPE_FILENAME_LIST:
System.err.println("Value: list of file names, semicolon-separated");
break;
case TYPE_CLASSNAME:
System.err.println("Value: Java fully-qualified class name");
break;
case TYPE_QNAME:
System.err.println("Value: QName in Clark notation ({uri}local)");
break;
case TYPE_STRING:
System.err.println("Value: string");
break;
case TYPE_INTEGER_PAIR:
System.err.println("Value: int,int");
break;
case TYPE_ENUMERATION:
String message = "Value: one of ";
message += displayPermittedValues(permittedValues.get(option));
System.err.println(message);
break;
default:
break;
}
}
System.err.println("Meaning: " + optionHelp.get(option));
}
/**
* Get the value of a named option. Returns null if the option was not present on the command line.
* Returns "" if the option was present but with no value ("-x" or "-x:").
*
* @param option the option keyword
* @return the option value, or null if not specified.
*/
public String getOptionValue(String option) {
return namedOptions.getProperty(option);
}
/**
* Get the options specified positionally, that is, without a leading "-"
*
* @return the list of positional options
*/
/*@NotNull*/
public List getPositionalOptions() {
return positionalOptions;
}
public void setParams(Processor processor, ParamSetter paramSetter)
throws SaxonApiException {
for (String name : paramValues.stringPropertyNames()) {
String value = paramValues.getProperty(name);
paramSetter.setParam(QName.fromClarkName(name), new XdmAtomicValue(value, ItemType.UNTYPED_ATOMIC));
}
applyFileParameters(processor, paramSetter);
for (String name : paramExpressions.stringPropertyNames()) {
String value = paramExpressions.getProperty(name);
// parameters starting with "?" are taken as XPath expressions
XPathCompiler xpc = processor.newXPathCompiler();
XPathExecutable xpe = xpc.compile(value);
XdmValue val = xpe.load().evaluate();
paramSetter.setParam(QName.fromClarkName(name), val);
}
}
private void applyFileParameters(Processor processor, ParamSetter paramSetter) throws SaxonApiException {
boolean useURLs = "on".equals(getOptionValue("u"));
for (String name : paramFiles.stringPropertyNames()) {
String value = paramFiles.getProperty(name);
List