net.sf.saxon.s9api.Processor Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.s9api;
import net.sf.saxon.Configuration;
import net.sf.saxon.Version;
import net.sf.saxon.event.*;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.parser.Loc;
import net.sf.saxon.expr.sort.RuleBasedSubstringMatcher;
import net.sf.saxon.expr.sort.SimpleCollation;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.s9api.push.Push;
import net.sf.saxon.serialize.SerializationProperties;
import net.sf.saxon.trans.CommandLineOptions;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharpModifiers;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.value.SequenceType;
import javax.xml.transform.Source;
import java.io.File;
import java.io.OutputStream;
import java.io.Writer;
import java.text.RuleBasedCollator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
/**
* The Processor
class serves three purposes: it allows global Saxon configuration options to be set;
* it acts as a factory for generating XQuery, XPath, and XSLT compilers; and it owns certain shared
* resources such as the Saxon NamePool and compiled schemas. This is the first object that a
* Saxon application should create. Once established, a Processor may be used in multiple threads.
* It is possible to run more than one Saxon Processor concurrently, but only when running completely
* independent workloads. Nothing can be shared between Processor instances. Within a query or transformation,
* all source documents and schemas must be built using the same Processor, which must also be used to
* compile the query or stylesheet.
*/
@CSharpModifiers(code={"internal"})
public class Processor implements Configuration.ApiProvider {
private Configuration config;
private SchemaManager schemaManager;
/**
* Create a Processor
*
* @param licensedEdition indicates whether the Processor requires features of Saxon that need a license
* file (that is, features not available in Saxon HE (Home Edition). If true, the method will create
* a Configuration appropriate to the version of the software that is running: for example, if running
* Saxon-EE, it will create an EnterpriseConfiguration. The method does not at this stage check that a license
* is available, and in the absence of a license, it should run successfully provided no features that
* require licensing are actually used. If the argument is set to false, a plain Home Edition Configuration
* is created unconditionally.
*/
public Processor(boolean licensedEdition) {
if (licensedEdition) {
config = Configuration.newConfiguration();
if (config.getEditionCode().equals("EE")) {
schemaManager = makeSchemaManager();
}
} else {
config = new Configuration();
}
config.setProcessor(this);
}
/**
* Create a Processor based on an existing Configuration. This constructor is useful
* when new components of an application are to use s9api interfaces but existing
* components use older interfaces (for example, JAXP). It is also useful in cases where,
* for example, multiple configurations need to be built using the same configuration file
* or sharing license data: in such cases, the Configuration can be manually built, and
* then wrapped in a Processor.
*
* @param config the Configuration to be used by this processor
* @since 9.3
*/
public Processor(/*@NotNull*/ Configuration config) {
this.config = config;
if (config.getEditionCode().equals("EE")) {
schemaManager = makeSchemaManager();
}
}
/**
* Create a Processor configured according to the settings in a supplied configuration file.
*
* @param source the Source of the configuration file
* @throws SaxonApiException if the configuration file cannot be read, or its contents are invalid
* @since 9.2
*/
public Processor(Source source) throws SaxonApiException {
try {
config = Configuration.readConfiguration(source);
schemaManager = makeSchemaManager();
} catch (XPathException e) {
throw new SaxonApiException(e);
}
config.setProcessor(this);
}
/**
* Create a {@code DocumentBuilder}. A DocumentBuilder is used to load source XML documents.
*
* @return a newly created DocumentBuilder
*/
/*@NotNull*/
public DocumentBuilder newDocumentBuilder() {
return new DocumentBuilder(config);
}
/**
* Create a {@code JsonBuilder}. A {@code JsonBuilder} is used to load source JSON documents.
*
* @return a newly created {@code JsonBuilder}
* @since 11
*/
/*@NotNull*/
public JsonBuilder newJsonBuilder() {
return new JsonBuilder(getUnderlyingConfiguration());
}
/**
* Create an XPathCompiler. An XPathCompiler is used to compile XPath expressions.
*
* @return a newly created XPathCompiler
*/
/*@NotNull*/
public XPathCompiler newXPathCompiler() {
return new XPathCompiler(this);
}
/**
* Create an XsltCompiler. An XsltCompiler is used to compile XSLT stylesheets.
*
* @return a newly created XsltCompiler
* @throws UnsupportedOperationException if this version of the Saxon product does not support XSLT processing
*/
/*@NotNull*/
public XsltCompiler newXsltCompiler() {
return new XsltCompiler(this);
}
/**
* Create an XQueryCompiler. An XQueryCompiler is used to compile XQuery queries.
*
* @return a newly created XQueryCompiler
* @throws UnsupportedOperationException if this version of the Saxon product does not support XQuery processing
*/
/*@NotNull*/
public XQueryCompiler newXQueryCompiler() {
return new XQueryCompiler(this);
}
/**
* Create a Serializer
*
* @return a new Serializer
* @since 9.3
*/
/*@NotNull*/
public Serializer newSerializer() {
return new Serializer(this);
}
/**
* Create a Serializer initialized to write to a given OutputStream.
* Closing the output stream after use is the responsibility of the caller.
*
* @param stream The OutputStream to which the Serializer will write
* @return a new Serializer
* @since 9.3
*/
/*@NotNull*/
public Serializer newSerializer(OutputStream stream) {
Serializer s = new Serializer(this);
s.setOutputStream(stream);
return s;
}
/**
* Create a Serializer initialized to write to a given Writer.
* Closing the writer after use is the responsibility of the caller.
*
* @param writer The Writer to which the Serializer will write
* @return a new Serializer
* @since 9.3
*/
/*@NotNull*/
public Serializer newSerializer(Writer writer) {
Serializer s = new Serializer(this);
s.setOutputWriter(writer);
return s;
}
/**
* Create a Serializer initialized to write to a given File.
*
* @param file The File to which the Serializer will write
* @return a new Serializer
* @since 9.3
*/
/*@NotNull*/
public Serializer newSerializer(File file) {
Serializer s = new Serializer(this);
s.setOutputFile(file);
return s;
}
/**
* Get a new {@link Push} provider. The returned {@link Push} object allows the client
* application to construct events (such as {@code startElement()}, {@code text()},
* and {@code endElement()}) and send them to a specified {@link Destination}.
*
* @param destination the destination
* @return a new recipient of {@link Push} events.
* @throws SaxonApiException if the {@link Destination} is not able to handle the request.
*/
public Push newPush(Destination destination) throws SaxonApiException {
PipelineConfiguration pipe = getUnderlyingConfiguration().makePipelineConfiguration();
SerializationProperties props = new SerializationProperties();
return new PushToReceiver(destination.getReceiver(pipe, props));
}
/**
* Register a simple external/extension function that is to be made available within any stylesheet, query,
* or XPath expression compiled under the control of this processor.
* This interface provides only for simple extension functions that have no side-effects and no dependencies
* on the static or dynamic context.
*
* @param function the implementation of the extension function.
* @since 9.4
*/
public void registerExtensionFunction(ExtensionFunction function) {
ExtensionFunctionDefinitionWrapper wrapper = new ExtensionFunctionDefinitionWrapper(function);
registerExtensionFunction(wrapper);
}
/**
* Register an extension function that is to be made available within any stylesheet, query,
* or XPath expression compiled under the control of this processor. This method
* registers an extension function implemented as an instance of
* {@link net.sf.saxon.lib.ExtensionFunctionDefinition}, using an arbitrary name and namespace.
* This interface allows extension functions that have dependencies on the static or dynamic
* context. It also allows an extension function to declare that it has side-effects, in which
* case calls to the function will be optimized less aggressively than usual, although the semantics
* are still to some degree unpredictable.
*
* @param function the implementation of the extension function.
* @since 9.2
*/
public void registerExtensionFunction(ExtensionFunctionDefinition function) {
try {
config.registerExtensionFunction(function);
} catch (Exception err) {
throw new IllegalArgumentException(err);
}
}
/**
* Get the associated SchemaManager. The SchemaManager provides capabilities to load and cache
* XML schema definitions. There is exactly one SchemaManager in a schema-aware Processor, and none
* in a Processor that is not schema-aware. The SchemaManager is created automatically by the system.
*
* @return the associated SchemaManager, or null if the Processor is not schema-aware.
*/
public SchemaManager getSchemaManager() {
return schemaManager;
}
/**
* Test whether this processor is schema-aware
*
* @return true if this this processor is licensed for schema processing, false otherwise
*/
public boolean isSchemaAware() {
return config.isLicensedFeature(Configuration.LicenseFeature.SCHEMA_VALIDATION);
}
/**
* Get the user-visible Saxon product version, for example "9.0.0.1"
*
* @return the Saxon product version, as a string
*/
public String getSaxonProductVersion() {
return Version.getProductVersion();
}
/**
* Get the short name of the Saxon product edition, for example "EE". This represents the kind of configuration
* that has been created, rather than the software that has been installed; for example it is possible to
* instantiate an "HE" configuration even when using the "PE" or "EE" software.
* @return the Saxon edition code: "EE", "PE", or "HE"
*/
public String getSaxonEdition() {
return config.getEditionCode();
}
/**
* Set the version of XML used by this Processor. If the value is set to "1.0", then
* output documents will be serialized as XML 1.0. This option also affects
* the characters permitted to appear in queries and stylesheets, and the characters that can appear
* in names (for example, in path expressions).
* Note that source documents specifying xml version="1.0" or "1.1" are accepted
* regardless of this setting.
*
* @param version must be one of the strings "1.0" or "1.1"
* @throws IllegalArgumentException if any string other than "1.0" or "1.1" is supplied
*/
public void setXmlVersion(/*@NotNull*/ String version) {
switch (version) {
case "1.0":
config.setXMLVersion(Configuration.XML10);
break;
case "1.1":
config.setXMLVersion(Configuration.XML11);
break;
default:
throw new IllegalArgumentException("XmlVersion");
}
}
/**
* Get the version of XML used by this Processor. If the value is "1.0", then input documents
* must be XML 1.0 documents, and output documents will be serialized as XML 1.0. This option also affects
* the characters permitted to appear in queries and stylesheets, and the characters that can appear
* in names (for example, in path expressions).
*
* @return one of the strings "1.0" or "1.1"
*/
/*@NotNull*/
public String getXmlVersion() {
if (config.getXMLVersion() == Configuration.XML10) {
return "1.0";
} else {
return "1.1";
}
}
/**
* Set a configuration property
*
* @param name the name of the option to be set. The names of the options available are listed
* as constants in class {@link net.sf.saxon.lib.FeatureKeys}.
* @param value the value of the option to be set.
* @throws IllegalArgumentException if the property name is not recognized or if the supplied value
* is not a valid value for the named property.
* @deprecated since 9.9 - use {@link #setConfigurationProperty(Feature, Object)}
*/
@Deprecated
public void setConfigurationProperty(/*@NotNull*/ String name, /*@NotNull*/ Object value) {
if (name.equals(FeatureKeys.CONFIGURATION)) {
config = (Configuration) value;
} else {
config.setConfigurationProperty(name, value);
}
}
/**
* Get the value of a configuration property
*
* @param name the name of the option required. The names of the properties available are listed
* as constants in class {@link net.sf.saxon.lib.FeatureKeys}.
* @return the value of the property, if one is set; or null if the property is unset and there is
* no default.
* @throws IllegalArgumentException if the property name is not recognized
* @deprecated since 9.9 - use {@link #getConfigurationProperty(Feature)}
*/
/*@Nullable*/
@Deprecated
public Object getConfigurationProperty(/*@NotNull*/ String name) {
return config.getConfigurationProperty(name);
}
/**
* Set a configuration property
*
* @param feature the option to be set. The names of the options available are listed
* as constants in class {@link net.sf.saxon.lib.Feature}.
* @param value the value of the option to be set (which must be of the appropriate type for the
* particular feature.
* @param the type of the value required by the feature (often boolean or string)
* @throws IllegalArgumentException if the supplied value is not a valid value for the selected feature.
* @since 9.9 introduced to give a faster and type-safe alternative to
* {@link #setConfigurationProperty(String, Object)}
*/
@CSharpReplaceBody(code=""
+ " if ((feature.code==Saxon.Hej.lib.FeatureCode.CONFIGURATION)) {"
+ " config = (Saxon.Hej.Configuration)(object)value;"
+ " } else {\n"
+ " config.setConfigurationProperty(feature, value);"
+ " }")
public void setConfigurationProperty(Feature feature, T value) {
if (feature == Feature.CONFIGURATION) {
config = (Configuration) value;
} else {
config.setConfigurationProperty(feature, value);
}
}
/**
* Get the value of a configuration property
*
* @param feature the option required. The names of the properties available are listed
* as constants in class {@link net.sf.saxon.lib.Feature}.
* @param the type of the feature (often boolean or string)
* @return the value of the property, if one is set; or null if the property is unset and there is
* no default.
* @since 9.9 introduced to give a faster and type-safe alternative to
* {@link #getConfigurationProperty(String)}
*/
/*@Nullable*/
public T getConfigurationProperty(Feature feature) {
return config.getConfigurationProperty(feature);
}
/**
* Bind a collation URI to a collation
*
* @param uri the absolute collation URI
* @param collation a {@link Comparator} object that implements the required collation
* @throws IllegalArgumentException if an attempt is made to rebind the standard URI
* for the Unicode codepoint collation or the HTML5 case-blind
* collation
* @since 9.6. Changed in 9.8 to allow any Comparator to be supplied as a collation
*/
public void declareCollation(String uri, final Comparator super String> collation) {
if (uri.equals(NamespaceConstant.CODEPOINT_COLLATION_URI)) {
throw new IllegalArgumentException("Cannot redeclare the Unicode codepoint collation URI");
}
if (uri.equals(NamespaceConstant.HTML5_CASE_BLIND_COLLATION_URI)) {
throw new IllegalArgumentException("Cannot redeclare the HTML5 caseblind collation URI");
}
StringCollator saxonCollation = makeStringCollator(uri, collation);
config.registerCollation(uri, saxonCollation);
}
@CSharpReplaceBody(code="return new Saxon.Hej.expr.sort.SimpleCollation(uri, collation);")
private static StringCollator makeStringCollator(String uri, Comparator super String> collation) {
if (collation instanceof RuleBasedCollator) {
return new RuleBasedSubstringMatcher(uri, (RuleBasedCollator) collation);
} else {
return new SimpleCollation(uri, collation);
}
}
/**
* Register a specific URI and bind it to a specific ResourceCollection. A collection that is
* registered in this way will be returned prior to calling any registered {@link CollectionFinder}.
* This method should only be used while the configuration is being initialized for use;
* the effect of adding or replacing collections dynamically while a configuration is in use
* is undefined.
*
* Registered collections take priority over any user-supplied CollectionFinder
;
* if a collection URI has been registered, then it is used before the user-supplied
* CollectionFinder
is invoked.
*
* @param collectionURI the collection URI to be registered. Must not be null.
* @param collection the ResourceCollection to be associated with this URI. Must not be null.
* @since 11.0
*/
public void registerCollection(String collectionURI, ResourceCollection collection) {
config.registerCollection(collectionURI, collection);
}
/**
* Supply one or more resource catalog files to be used for URI resolution.
* The call has no effect if the CommonResourceResolver
registered with the
* Configuration
does not use catalog files.
* @param fileNames the files to be used. If no files are supplied, the call removes any existing catalog
* files that were previously registered.
*/
public void setCatalogFiles(String... fileNames) {
if (config.getResourceResolver() instanceof ConfigurableResourceResolver) {
CommandLineOptions.setCatalogFiles(((ConfigurableResourceResolver) config.getResourceResolver() ), Arrays.asList(fileNames));
}
}
/**
* Get the underlying {@link Configuration} object that underpins this Processor. This method
* provides an escape hatch to internal Saxon implementation objects that offer a finer and lower-level
* degree of control than the s9api classes and methods. Some of these classes and methods may change
* from release to release.
*
* @return the underlying Configuration object
*/
public Configuration getUnderlyingConfiguration() {
return config;
}
/**
* Write an XdmValue to a given destination.
*
* If the destination is a {@link Serializer} then the method processor.writeXdmValue(V, S)
* is equivalent to calling S.serializeXdmValue(V)
.
*
* In other cases, the sequence represented by the XdmValue is "normalized"
* as defined in the serialization specification (this is equivalent to constructing a document node
* in XSLT or XQuery with this sequence as the content expression), and the resulting document is
* then copied to the destination. Note that the construction of a document tree will fail if
* the sequence contains items such as maps and arrays.
*
* @param value the value to be written
* @param destination the destination to which the value is to be written
* @throws SaxonApiException if any failure occurs, for example a serialization error
*/
public void writeXdmValue(XdmValue value, Destination destination) throws SaxonApiException {
Objects.requireNonNull(value);
Objects.requireNonNull(destination);
try {
if (destination instanceof Serializer) {
((Serializer)destination).serializeXdmValue(value);
} else {
Receiver out = destination.getReceiver(config.makePipelineConfiguration(), config.obtainDefaultSerializationProperties());
ComplexContentOutputter tree = new ComplexContentOutputter(out);
tree.open();
tree.startDocument(ReceiverOption.NONE);
for (XdmItem item : value) {
tree.append(item.getUnderlyingValue(), Loc.NONE, ReceiverOption.ALL_NAMESPACES);
}
tree.endDocument();
tree.close();
destination.closeAndNotify();
}
} catch (XPathException err) {
throw new SaxonApiException(err);
}
}
private static class ExtensionFunctionDefinitionWrapper extends ExtensionFunctionDefinition {
private final ExtensionFunction function;
public ExtensionFunctionDefinitionWrapper(ExtensionFunction function) {
this.function = function;
}
/**
* Get the name of the function, as a QName.
* This method must be implemented in all subclasses
*
* @return the function name
*/
@Override
public StructuredQName getFunctionQName() {
return function.getName().getStructuredQName();
}
/**
* Get the minimum number of arguments required by the function
* The default implementation returns the number of items in the result of calling
* {@link #getArgumentTypes}
*
* @return the minimum number of arguments that must be supplied in a call to this function
*/
@Override
public int getMinimumNumberOfArguments() {
return function.getArgumentTypes().length;
}
/**
* Get the maximum number of arguments allowed by the function.
* The default implementation returns the value of {@link #getMinimumNumberOfArguments}
*
* @return the maximum number of arguments that may be supplied in a call to this function
*/
@Override
public int getMaximumNumberOfArguments() {
return function.getArgumentTypes().length;
}
/**
* Get the required types for the arguments of this function.
*
This method must be implemented in all subtypes.
*
* @return the required types of the arguments, as defined by the function signature. Normally
* this should be an array of size {@link #getMaximumNumberOfArguments()}; however for functions
* that allow a variable number of arguments, the array can be smaller than this, with the last
* entry in the array providing the required type for all the remaining arguments.
*/
/*@NotNull*/
@Override
public net.sf.saxon.value.SequenceType[] getArgumentTypes() {
net.sf.saxon.s9api.SequenceType[] declaredArgs = function.getArgumentTypes();
net.sf.saxon.value.SequenceType[] types = new net.sf.saxon.value.SequenceType[declaredArgs.length];
for (int i = 0; i < declaredArgs.length; i++) {
types[i] = net.sf.saxon.value.SequenceType.makeSequenceType(
declaredArgs[i].getItemType().getUnderlyingItemType(),
declaredArgs[i].getOccurrenceIndicator().getCardinality());
}
return types;
}
/**
* Get the type of the result of the function
* This method must be implemented in all subtypes.
*
* @param suppliedArgumentTypes the static types of the supplied arguments to the function.
* This is provided so that a more precise result type can be returned in the common
* case where the type of the result depends on the types of the arguments.
* @return the return type of the function, as defined by its function signature
*/
@Override
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
net.sf.saxon.s9api.SequenceType declaredResult = function.getResultType();
return net.sf.saxon.value.SequenceType.makeSequenceType(
declaredResult.getItemType().getUnderlyingItemType(),
declaredResult.getOccurrenceIndicator().getCardinality());
}
/**
* Ask whether the result actually returned by the function can be trusted,
* or whether it should be checked against the declared type.
*
* @return true if the function implementation warrants that the value it returns will
* be an instance of the declared result type. The default value is false, in which case
* the result will be checked at run-time to ensure that it conforms to the declared type.
* If the value true is returned, but the function returns a value of the wrong type, the
* consequences are unpredictable.
*/
@Override
public boolean trustResultType() {
return false;
}
/**
* Ask whether the result of the function depends on the focus, or on other variable parts
* of the context.
*
* @return true if the result of the function depends on the context item, position, or size.
* Despite the method name, the method should also return true if the function depends on other
* parts of the context that vary from one part of the query/stylesheet to another, for example
* the XPath default namespace.
* The default implementation returns false.
* The method must return true if the function
* makes use of any of these values from the dynamic context. Returning true inhibits certain
* optimizations, such as moving the function call out of the body of an xsl:for-each loop,
* or extracting it into a global variable.
*/
@Override
public boolean dependsOnFocus() {
return false;
}
/**
* Ask whether the function has side-effects. If the function does have side-effects, the optimizer
* will be less aggressive in moving or removing calls to the function. However, calls on functions
* with side-effects can never be guaranteed.
*
* @return true if the function has side-effects (including creation of new nodes, if the
* identity of those nodes is significant). The default implementation returns false.
*/
@Override
public boolean hasSideEffects() {
return false;
}
/**
* Create a call on this function. This method is called by the compiler when it identifies
* a function call that calls this function.
*/
/*@NotNull*/
@Override
public ExtensionFunctionCall makeCallExpression() {
return new ExtensionFunctionCall() {
@Override
public Sequence call(
/*@NotNull*/ XPathContext context, Sequence[] arguments) throws XPathException {
XdmValue[] args = new XdmValue[arguments.length];
for (int i = 0; i < args.length; i++) {
GroundedValue val = arguments[i].materialize();
args[i] = XdmValue.wrap(val);
}
try {
XdmValue result = function.call(args);
return result.getUnderlyingValue();
} catch (SaxonApiException e) {
throw new XPathException(e);
}
}
};
}
}
private SchemaManager makeSchemaManager() {
SchemaManager manager = null;
return manager;
}
}