net.sf.saxon.Controller Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 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.*;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.*;
import net.sf.saxon.expr.parser.ExplicitLocation;
import net.sf.saxon.expr.parser.PathMap;
import net.sf.saxon.functions.AccessorFn;
import net.sf.saxon.functions.IriToUri;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.resource.CollectionURIResolverWrapper;
import net.sf.saxon.serialize.Emitter;
import net.sf.saxon.serialize.ImplicitResultChecker;
import net.sf.saxon.style.StylesheetPackage;
import net.sf.saxon.trace.TraceEventMulticaster;
import net.sf.saxon.trans.*;
import net.sf.saxon.tree.iter.AxisIterator;
import net.sf.saxon.tree.iter.ManualIterator;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.tree.iter.UnfailingIterator;
import net.sf.saxon.tree.tiny.Statistics;
import net.sf.saxon.tree.tiny.TinyBuilder;
import net.sf.saxon.tree.wrapper.SpaceStrippedDocument;
import net.sf.saxon.tree.wrapper.TypeStrippedDocument;
import net.sf.saxon.type.Type;
import net.sf.saxon.type.Untyped;
import net.sf.saxon.value.DateTimeValue;
import net.sf.saxon.value.SequenceExtent;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.SingletonClosure;
import org.xml.sax.SAXParseException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* The Controller underpins Saxon's implementation of the JAXP Transformer class, and represents
* an executing instance of a transformation or query. Multiple concurrent executions of
* the same transformation or query will use different Controller instances. This class is
* therefore not thread-safe.
*
* The Controller is serially reusable: when one transformation or query
* is finished, it can be used to run another. However, there is no advantage in doing this
* rather than allocating a new Controller each time.
*
* A dummy Controller is created when running free-standing XPath expressions.
*
* The Controller holds those parts of the dynamic context that do not vary during the course
* of a transformation or query, or that do not change once their value has been computed.
* This also includes those parts of the static context that are required at run-time.
*
* Many methods on the Controller are designed for internal use and should not be
* considered stable. From release 8.4 onwards, those methods that are considered sufficiently
* stable to constitute path of the Saxon public API are labelled with the JavaDoc tag "since":
* the value indicates the release at which the method was added to the public API.
*
* Prior to Saxon 9.6 the Controller implemented (extended) the JAXP {@link Transformer}
* interface, and advanced applications were able to down-cast the Transformer to a Controller.
* This is no longer the case. Instead, the JAXP factory delivers an instance of {@link net.sf.saxon.jaxp.TransformerImpl},
* from which the Controller is accessible if required. Because the Controller is no longer required
* to implement the JAXP interface, it has been possible to make it less monolithic, so some of the
* things it did are now done elsewhere: for example, it no longer handles global parameters
*
* @author Michael H. Kay
* @since 8.4 From 9.6 this class should no longer be considered a public API.
*/
public class Controller implements ContextOriginator {
private Configuration config;
private Executable executable;
private Item globalContextItem;
private boolean globalContextItemPreset;
private Map binderies;
private GlobalParameterSet globalParameters;
private boolean convertParameters = true;
private Map> globalVariableDependencies = new HashMap>();
private String messageReceiverClassName;
private Receiver messageReceiver;
private TraceListener traceListener;
private boolean tracingPaused;
private Logger traceFunctionDestination;
private boolean assertionsEnabled = true;
private URIResolver standardURIResolver;
private URIResolver userURIResolver;
private Receiver principalResult;
private String principalResultURI;
private String cookedPrincipalResultURI;
private boolean thereHasBeenAnExplicitResultDocument;
private OutputURIResolver outputURIResolver;
private UnparsedTextURIResolver unparsedTextResolver;
private String defaultCollectionURI;
private UnfailingErrorListener errorListener;
private int recoveryPolicy;
private TreeModel treeModel = TreeModel.TINY_TREE;
private NamedTemplate initialTemplate = null;
private HashSet allOutputDestinations;
private DocumentPool sourceDocumentPool;
private SequenceOutputter reusableSequenceOutputter = null;
private HashMap userDataTable;
private DateTimeValue currentDateTime;
private boolean dateTimePreset = false;
private Mode initialMode = null;
private Function initialFunction = null;
private NodeInfo lastRememberedNode = null;
private int lastRememberedNumber = -1;
private PathMap pathMap = null;
private int validationMode = Validation.DEFAULT;
private boolean inUse = false;
private boolean stripSourceTrees = true;
private boolean buildTree = true;
private Map initialTemplateParams;
private Map initialTemplateTunnelParams;
private Stack attributeSetEvaluationStack = new Stack();
private StylesheetCache stylesheetCache = null;
private IAccumulatorManager accumulatorManager = null;
private CollectionFinder collectionFinder = null;
public final static String ANONYMOUS_PRINCIPAL_OUTPUT_URI = "dummy:/anonymous/principal/result";
/**
* Create a Controller and initialise variables. Note: XSLT applications should
* create the Controller by using the JAXP newTransformer() method, or in S9API
* by using XsltExecutable.load()
*
* @param config The Configuration used by this Controller
*/
public Controller(Configuration config) {
this.config = config;
// create a dummy executable
executable = new Executable(config);
sourceDocumentPool = new DocumentPool();
reset();
}
/**
* Create a Controller and initialise variables.
*
* @param config The Configuration used by this Controller
* @param executable The executable used by this Controller
*/
public Controller(Configuration config, Executable executable) {
this.config = config;
this.executable = executable;
sourceDocumentPool = new DocumentPool();
reset();
}
/**
* Reset this Transformer
to its original configuration.
*
* Transformer
is reset to the same state as when it was created with
* {@link javax.xml.transform.TransformerFactory#newTransformer()},
* {@link javax.xml.transform.TransformerFactory#newTransformer(javax.xml.transform.Source source)} or
* {@link javax.xml.transform.Templates#newTransformer()}.
* reset()
is designed to allow the reuse of existing Transformer
s
* thus saving resources associated with the creation of new Transformer
s.
*
* The above is from the JAXP specification. With Saxon, it's unlikely that reusing a Transformer will
* give any performance benefits over creating a new one. The one case where it might be beneficial is
* to reuse the document pool (the set of documents that have been loaded using the doc() or document()
* functions). Therefore, this method does not clear the document pool. If you want to clear the document
* pool, call the method {@link #clearDocumentPool} as well.
*
* The reset Transformer
is not guaranteed to have the same {@link javax.xml.transform.URIResolver}
* or {@link javax.xml.transform.ErrorListener} Object
s, e.g. {@link Object#equals(Object obj)}.
* It is guaranteed to have a functionally equal URIResolver
* and ErrorListener
.
*
* @since 1.5
*/
public void reset() {
globalParameters = new GlobalParameterSet();
standardURIResolver = config.getSystemURIResolver();
userURIResolver = config.getURIResolver();
outputURIResolver = config.getOutputURIResolver();
unparsedTextResolver = new StandardUnparsedTextResolver();
setErrorListener(config.getErrorListener());
recoveryPolicy = config.getRecoveryPolicy();
validationMode = config.getSchemaValidationMode();
accumulatorManager = config.makeAccumulatorManager();
if (errorListener instanceof StandardErrorListener) {
// if using a standard error listener, make a fresh one
// for each transformation, because it is stateful - and also because the
// host language is now known (a Configuration can serve multiple host languages)
Logger ps = ((StandardErrorListener) errorListener).getLogger();
errorListener = ((StandardErrorListener) errorListener).makeAnother(executable.getHostLanguage());
((StandardErrorListener) errorListener).setLogger(ps);
((StandardErrorListener) errorListener).setRecoveryPolicy(recoveryPolicy);
}
traceListener = null;
traceFunctionDestination = config.getLogger();
TraceListener tracer;
try {
tracer = config.makeTraceListener();
} catch (XPathException err) {
throw new IllegalStateException(err.getMessage());
}
if (tracer != null) {
addTraceListener(tracer);
}
setModel(config.getParseOptions().getModel());
globalContextItem = null;
messageReceiver = null;
currentDateTime = null;
dateTimePreset = false;
initialMode = null;
initialTemplate = null;
clearPerTransformationData();
}
/**
* Reset variables that need to be reset for each transformation if the controller
* is serially reused
*/
private void clearPerTransformationData() {
userDataTable = new HashMap(20);
principalResult = null;
allOutputDestinations = null;
thereHasBeenAnExplicitResultDocument = false;
lastRememberedNode = null;
lastRememberedNumber = -1;
tracingPaused = false;
if (!globalContextItemPreset) {
globalContextItem = null;
}
}
/**
* Get the Configuration associated with this Controller. The Configuration holds
* settings that potentially apply globally to many different queries and transformations.
*
* @return the Configuration object
* @since 8.4
*/
public Configuration getConfiguration() {
return config;
}
/**
* Set the initial mode for the transformation.
*
* XSLT 2.0 allows a transformation to be started in a mode other than the default mode.
* The transformation then starts by looking for the template rule in this mode that best
* matches the initial context node.
*
* This method may eventually be superseded by a standard JAXP method.
*
* @param expandedModeName the name of the initial mode. The mode is
* supplied as an expanded QName. Two special values are recognized, in the
* reserved XSLT namespace:
* xsl:unnamed to indicate the mode with no name, and xsl:default to indicate the
* mode defined in the stylesheet header as the default mode.
* The value null also indicates the unnamed mode.
* @throws XPathException if the requested mode is not defined in the stylesheet
* @since 8.4 Changed in 9.6 to accept a StructuredQName, and to throw an error if the mode
* has not been defined. Changed in 9.7 so that null indicates the default mode, which defaults
* to the unnamed mode but can be set otherwise in the stylesheet.
*/
public void setInitialMode(StructuredQName expandedModeName) throws XPathException {
if (expandedModeName == null || expandedModeName.equals(Mode.UNNAMED_MODE_NAME)) {
initialMode = ((PreparedStylesheet) executable).getRuleManager().obtainMode(Mode.UNNAMED_MODE_NAME, true);
} else if (expandedModeName.equals(Mode.DEFAULT_MODE_NAME)) {
StructuredQName defaultModeName = ((StylesheetPackage) executable.getTopLevelPackage()).getDefaultMode();
if (!expandedModeName.equals(defaultModeName)) {
setInitialMode(defaultModeName);
}
} else {
initialMode = ((PreparedStylesheet) executable).getRuleManager().obtainMode(expandedModeName, false);
if (initialMode == null) {
throw new XPathException("Requested initial mode " + expandedModeName + " is not defined in the stylesheet", "XTDE0045");
}
}
}
/**
* Get the name of the initial mode for the transformation
*
* @return the name of the initial mode; null indicates
* that the initial mode is the unnamed mode.
*/
public StructuredQName getInitialModeName() {
return initialMode == null ? null : initialMode.getModeName();
}
/**
* Get the initial mode for the transformation
*
* @return the initial mode. This will be the default/unnamed mode if no specific mode
* has been requested
*/
/*@NotNull*/
public Mode getInitialMode() {
if (initialMode == null) {
StructuredQName defaultMode = ((StylesheetPackage) executable.getTopLevelPackage()).getDefaultMode();
if (defaultMode != null) {
initialMode = ((PreparedStylesheet) executable).getRuleManager().obtainMode(defaultMode, false);
}
}
return initialMode;
}
/**
* Get the accumulator manager for this transformation. May be null if no accumulators are in use
*
* @return the accumulator manager, which holds dynamic information about the values of each
* accumulator for each document
*/
public IAccumulatorManager getAccumulatorManager() {
return accumulatorManager;
}
/**
* Get the value of a supplied parameter (XSLT) or external variable (XQuery)
*
* @param name the QName of the parameter
* @return the supplied value of the parameter, if such a parameter exists, and if a value
* was supplied. Returns null if the parameter is not declared or if no value was supplied,
* even if there is a default defined in the stylesheet or query.
*/
public Sequence getParameter(StructuredQName name) {
return globalParameters.get(name);
}
/**
* Get the value of a parameter, converted and/or type-checked
*
* @param name the name of the stylesheet parameter (XSLT) or external variable (XQuery)
* @param requiredType the declared type of the parameter
* @param context the dynamic evaluation context
* @return the parameter value if defined, or null otherwise. If the option
* {@link #setApplyFunctionConversionRulesToExternalVariables(boolean)}} is set, the supplied
* value is converted to the required type. Otherwise, the supplied value is checked
* against the required type.
*/
public Sequence getConvertedParameter(StructuredQName name, SequenceType requiredType, XPathContext context)
throws XPathException {
Sequence val = globalParameters.convertParameterValue(name, requiredType, convertParameters, context);
if (val != null) {
// Check that any nodes belong to the right configuration
Configuration config = getConfiguration();
SequenceIterator iter = val.iterate();
Item next;
while ((next = iter.next()) != null) {
if (next instanceof FingerprintedNode && !config.isCompatible(((FingerprintedNode) next).getConfiguration())) {
throw new XPathException("A node supplied in a global parameter must be built using the same Configuration " +
"that was used to compile the stylesheet or query", SaxonErrorCode.SXXP0004);
}
}
// If the supplied value is a document node, and the document node has a systemID that is an absolute
// URI, and the absolute URI does not already exist in the document pool, then register it in the document
// pool, so that the document-uri() function will find it there, and so that a call on doc() will not
// reload it.
if (val instanceof NodeInfo && ((NodeInfo) val).getNodeKind() == Type.DOCUMENT) {
String systemId = ((NodeInfo) val).getRoot().getSystemId();
try {
if (systemId != null && new URI(systemId).isAbsolute()) {
DocumentPool pool = getDocumentPool();
if (pool.find(systemId) == null) {
pool.add(((NodeInfo) val).getTreeInfo(), systemId);
}
}
} catch (URISyntaxException err) {
// ignore it
}
}
if (!(val instanceof GroundedValue)) {
val = new SequenceExtent(val.iterate());
}
}
return val;
}
/**
* Set the base output URI.
*
* This defaults to the system ID of the Result object for the principal output
* of the transformation if this is known; if it is not known, it defaults
* to the current directory.
*
* The base output URI is used for resolving relative URIs in the href
attribute
* of the xsl:result-document
instruction.
*
* @param uri the base output URI
* @since 8.4
*/
public void setBaseOutputURI(String uri) {
principalResultURI = uri;
}
/**
* Get the base output URI.
*
* This returns the value set using the {@link #setBaseOutputURI} method. If no value has been set
* explicitly, then the method returns null if called before the transformation, or the computed
* default base output URI if called after the transformation.
*
* The base output URI is used for resolving relative URIs in the href
attribute
* of the xsl:result-document
instruction.
*
* @return the base output URI
* @since 8.4
*/
/*@Nullable*/
public String getBaseOutputURI() {
return principalResultURI;
}
/**
* Get the base output URI after processing. The processing consists of (a) defaulting
* to the current user directory if no base URI is available and if the stylesheet is trusted,
* and (b) applying IRI-to-URI escaping
*
* @return the base output URI after processing.
*/
/*@Nullable*/
public String getCookedBaseOutputURI() {
if (cookedPrincipalResultURI == null) {
String base = getBaseOutputURI();
if (base == null && config.getBooleanProperty(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS)) {
// if calling external functions is allowed, then the stylesheet is trusted, so
// we allow it to write to files relative to the current directory
base = new File(System.getProperty("user.dir")).toURI().toString();
}
if (base != null) {
base = IriToUri.iriToUri(base).toString();
}
cookedPrincipalResultURI = base;
}
return cookedPrincipalResultURI;
}
/**
* Get the principal result destination.
* This method is intended for internal use only. It is typically called by Saxon during the course
* of a transformation, to discover the result that was supplied in the transform() call.
*
* @return the Result object supplied as the principal result destination.
*/
/*@Nullable*/
public Receiver getPrincipalResult() {
return principalResult;
}
/**
* Check that an output destination has not been used before, optionally adding
* this URI to the set of URIs that have been used.
*
* @param uri the URI to be used as the output destination
* @return true if the URI is available for use; false if it has already been used.
*
* This method is intended for internal use only.
*/
public synchronized boolean checkUniqueOutputDestination(/*@Nullable*/ DocumentURI uri) {
if (uri == null) {
return true; // happens when writing say to an anonymous StringWriter
}
if (allOutputDestinations == null) {
allOutputDestinations = new HashSet(20);
}
return !allOutputDestinations.contains(uri);
}
/**
* Add a URI to the set of output destinations that cannot be written to, either because
* they have already been written to, or because they have been read
*
* @param uri A URI that is not available as an output destination
*/
public void addUnavailableOutputDestination(DocumentURI uri) {
if (allOutputDestinations == null) {
allOutputDestinations = new HashSet(20);
}
allOutputDestinations.add(uri);
}
/**
* Remove a URI from the set of output destinations that cannot be written to or read from.
* Used to support saxon:discard-document()
*
* @param uri A URI that is being made available as an output destination
*/
public void removeUnavailableOutputDestination(DocumentURI uri) {
if (allOutputDestinations != null) {
allOutputDestinations.remove(uri);
}
}
/**
* Determine whether an output URI is available for use. This method is intended
* for use by applications, via an extension function.
*
* @param uri A uri that the application is proposing to use in the href attribute of
* xsl:result-document: if this function returns false, then the xsl:result-document
* call will fail saying the URI has already been used.
* @return true if the URI is available for use. Note that this function is not "stable":
* it may return different results for the same URI at different points in the transformation.
*/
public boolean isUnusedOutputDestination(DocumentURI uri) {
return allOutputDestinations == null || !allOutputDestinations.contains(uri);
}
/**
* Check whether an XSLT implicit result tree can be written. This is allowed only if no xsl:result-document
* has been written for the principal output URI
*
* @throws XPathException if the implicit result document cannot be written because an explicit result document
* has been written to the same URI.
*/
public void checkImplicitResultTree() throws XPathException {
String implicitURI = principalResultURI;
if (implicitURI == null) {
implicitURI = ANONYMOUS_PRINCIPAL_OUTPUT_URI;
}
//if (principalResultURI != null) {
DocumentURI documentURI = new DocumentURI(implicitURI);
synchronized(this) {
if (!checkUniqueOutputDestination(documentURI)) {
XPathException err = new XPathException(
"Cannot write an implicit result document if an explicit result document has been written to the same URI: " +
(implicitURI.equals(ANONYMOUS_PRINCIPAL_OUTPUT_URI) ?
"(no URI supplied)" : implicitURI));
err.setErrorCode("XTDE1490");
throw err;
} else {
addUnavailableOutputDestination(documentURI);
}
}
//}
}
/**
* Set that an explicit result tree has been written using xsl:result-document
*/
public void setThereHasBeenAnExplicitResultDocument() {
thereHasBeenAnExplicitResultDocument = true;
}
/**
* Test whether an explicit result tree has been written using xsl:result-document
*
* @return true if the transformation has evaluated an xsl:result-document instruction
*/
public boolean hasThereBeenAnExplicitResultDocument() {
return thereHasBeenAnExplicitResultDocument;
}
/**
* Allocate a SequenceOutputter for a new output destination.
*
* @param size the estimated size of the output sequence
* @return SequenceOutputter the allocated SequenceOutputter
*/
/*@NotNull*/
public synchronized SequenceOutputter allocateSequenceOutputter(int size) {
PipelineConfiguration pipe = makePipelineConfiguration();
return new SequenceOutputter(pipe, size);
}
/**
* Accept a SequenceOutputter that is now available for reuse
*
* @param out the SequenceOutputter that is available for reuse
*/
public void reuseSequenceOutputter(SequenceOutputter out) {
reusableSequenceOutputter = out;
}
/**
* Say whether the principal transformation results should generate a result tree, or should be returned in raw sequence form
*
* @param build set to true if a tree should be built, false if the results should be delivered in raw form
*/
public void setBuildTree(boolean build) {
buildTree = build;
}
/**
* Ask whether the principal transformation results should generate a result tree, or should be returned in raw sequence form
*
* @return true if a tree should be built, false if the results should be delivered in raw form
*/
public boolean isBuildTree() {
return buildTree;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Set the initial named template to be used as the entry point, when the transformation is invoked
* via the {@link #transform(Source, Receiver)} method.
*
* XSLT 2.0 allows a transformation to start by executing a named template, rather than
* by matching an initial context node in a source document. This method may eventually
* be superseded by a standard JAXP method once JAXP supports XSLT 2.0.
*
* Note that any parameters supplied using {@link #initializeController(net.sf.saxon.expr.instruct.GlobalParameterSet)}
* are used as the values of global stylesheet parameters. There is no way to supply values for local parameters
* of the initial template.
*
* @param qName The expanded name of the template, or null to indicate that there should be no initial template.
* @throws XPathException if there is no named template with this name
* @since 8.4. Changed in 9.6 to accept a StructuredQName instead of a QName in Clark notation
*/
public void setInitialTemplate(StructuredQName qName) throws XPathException {
if (qName == null) {
initialTemplate = null;
return;
}
NamedTemplate t = ((PreparedStylesheet) getExecutable()).getNamedTemplate(qName);
if (t == null) {
XPathException err = new XPathException("The requested initial template "
+ qName.getDisplayName() + " does not exist");
err.setErrorCode("XTDE0040");
reportFatalError(err);
throw err;
} else {
initialTemplate = t;
}
}
/**
* Get the initial template
*
* @return the name of the initial template, as an expanded name in Clark format if set, or null otherwise
* @since 8.7. Changed in 9.6 to return a StructuredQName rather than a Clark name
*/
/*@Nullable*/
public StructuredQName getInitialTemplate() {
if (initialTemplate == null) {
return null;
} else {
return initialTemplate.getTemplateName();
}
}
/**
* Set parameters for the initial template (whether this is a named template, or a template
* rule invoked to process the initial input item)
*
* @param params Tunnel or non-tunnel parameters to be supplied to the initial template. The supplied
* values will be converted to the required type using the function conversion rules.
* Any surplus parameters are silently ignored. May be null.
* @param tunnel true if these are tunnel parameters; false if they are non-tunnel parameters
* @since 9.6
*/
public void setInitialTemplateParameters(Map params, boolean tunnel) {
if (tunnel) {
this.initialTemplateTunnelParams = params;
} else {
this.initialTemplateParams = params;
}
}
/**
* Get the parameters for the initial template
*
* @param tunnel true if the parameters to be returned are the tunnel parameters; false for the non-tunnel parameters
* @return the parameters supplied using {@link #setInitialTemplateParameters(java.util.Map, boolean)}.
* May be null.
* @since 9.6
*/
public Map getInitialTemplateParameters(boolean tunnel) {
return tunnel ? initialTemplateTunnelParams : initialTemplateParams;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Make a PipelineConfiguration based on the properties of this Controller.
*
* This interface is intended primarily for internal use, although it may be necessary
* for applications to call it directly if they construct pull or push pipelines
*
* @return a newly constructed PipelineConfiguration holding a reference to this
* Controller as well as other configuration information.
*/
/*@NotNull*/
public PipelineConfiguration makePipelineConfiguration() {
PipelineConfiguration pipe = config.makePipelineConfiguration();
pipe.setURIResolver(userURIResolver == null ? standardURIResolver : userURIResolver);
pipe.getParseOptions().setSchemaValidationMode(validationMode); // added in 9.7
pipe.getParseOptions().setErrorListener(errorListener); // added in 9.7.0.4
pipe.setController(this);
final Executable executable = getExecutable();
if (executable != null) {
// can be null for an IdentityTransformer
pipe.setHostLanguage(executable.getHostLanguage());
}
return pipe;
}
/**
* Set the message receiver class name
*
* @param name the full name of the class to be instantiated to provide a receiver for xsl:message output. The name must
* be the name of a class that implements the {@link net.sf.saxon.event.Receiver} interface
*/
public void setMessageReceiverClassName(String name) {
this.messageReceiverClassName = name;
}
/**
* Make a Receiver to be used for xsl:message output.
*
* This method is intended for internal use only.
*
* @return The newly constructed message Emitter
* @throws XPathException if any dynamic error occurs; in
* particular, if the registered MessageEmitter class is not an
* Emitter
*/
/*@NotNull*/
private Receiver makeMessageReceiver() throws XPathException {
Object messageReceiver = config.getInstance(messageReceiverClassName, null);
if (!(messageReceiver instanceof Receiver)) {
throw new XPathException(messageReceiverClassName + " is not a Receiver");
}
((Receiver) messageReceiver).setPipelineConfiguration(makePipelineConfiguration());
setMessageEmitter((Receiver) messageReceiver);
return (Receiver) messageReceiver;
}
/**
* Set the Receiver to be used for xsl:message output.
*
* Recent versions of the JAXP interface specify that by default the
* output of xsl:message is sent to the registered ErrorListener. Saxon
* does not implement this convention. Instead, the output is sent
* to a default message emitter, which is a slightly customised implementation
* of the standard Saxon Emitter interface.
*
* This interface can be used to change the way in which Saxon outputs
* xsl:message output.
*
* It is not necessary to use this interface in order to change the destination
* to which messages are written: that can be achieved by obtaining the standard
* message emitter and calling its {@link Emitter#setWriter} method.
*
* Although any Receiver
can be supplied as the destination for messages,
* applications may find it convenient to implement a subclass of {@link net.sf.saxon.event.SequenceWriter},
* in which only the abstract write()
method is implemented. This will have the effect that the
* write()
method is called to output each message as it is generated, with the Item
* that is passed to the write()
method being the document node at the root of an XML document
* containing the contents of the message.
*
* This method is intended for use by advanced applications. The Receiver interface
* itself is subject to change in new Saxon releases.
*
* The supplied Receiver will have its open() method called once at the start of
* the transformation, and its close() method will be called once at the end of the
* transformation. Each individual call of an xsl:message instruction is wrapped by
* calls of startDocument() and endDocument(). If terminate="yes" is specified on the
* xsl:message call, the properties argument of the startDocument() call will be set
* to the value {@link ReceiverOptions#TERMINATE}.
*
* @param receiver The receiver to receive xsl:message output.
* @since 8.4; changed in 8.9 to supply a Receiver rather than an Emitter
*/
public void setMessageEmitter(/*@NotNull*/ Receiver receiver) {
messageReceiver = receiver;
receiver.setPipelineConfiguration(makePipelineConfiguration());
if (receiver instanceof Emitter && ((Emitter) receiver).getOutputProperties() == null) {
try {
Properties props = new Properties();
props.setProperty(OutputKeys.METHOD, "xml");
props.setProperty(OutputKeys.INDENT, "yes");
props.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
((Emitter) receiver).setOutputProperties(props);
} catch (XPathException e) {
// no action
}
}
}
/**
* Get the Receiver used for xsl:message output. This returns the emitter
* previously supplied to the {@link #setMessageEmitter} method, or the
* default message emitter otherwise.
*
* @return the Receiver being used for xsl:message output
* @since 8.4; changed in 8.9 to return a Receiver rather than an Emitter
*/
/*@Nullable*/
public Receiver getMessageEmitter() {
return messageReceiver;
}
/**
* Set the policy for handling recoverable XSLT errors.
*
* Since 9.3 this call has no effect unless the error listener in use is a {@link StandardErrorListener}
* or a subclass thereof. Calling this method then results in a call to the StandardErrorListener
* to set the recovery policy, and the action that is taken on calls of the various methods
* error(), fatalError(), and warning() is then the responsibility of the ErrorListener itself.
*
* Since 9.2 the policy for handling the most common recoverable error, namely the ambiguous template
* match that arises when a node matches more than one match pattern, is a compile-time rather than run-time
* setting, and can be controlled using {@link net.sf.saxon.trans.CompilerInfo#setRecoveryPolicy(int)}
*
* @param policy the recovery policy to be used. The options are {@link Configuration#RECOVER_SILENTLY},
* {@link Configuration#RECOVER_WITH_WARNINGS}, or {@link Configuration#DO_NOT_RECOVER}.
* @since 8.7.1
*/
public void setRecoveryPolicy(int policy) {
recoveryPolicy = policy;
if (errorListener instanceof StandardErrorListener) {
((StandardErrorListener) errorListener).setRecoveryPolicy(policy);
}
}
/**
* Get the policy for handling recoverable errors
*
* @return the current policy. If none has been set with this Controller, the value registered with the
* Configuration is returned.
* @since 8.7.1
*/
public int getRecoveryPolicy() {
return recoveryPolicy;
}
/**
* Set the error listener.
*
* @param listener the ErrorListener to be used
*/
public void setErrorListener(ErrorListener listener) {
if (listener instanceof UnfailingErrorListener) {
errorListener = (UnfailingErrorListener) listener;
} else {
errorListener = new DelegatingErrorListener(listener);
}
}
/**
* Get the error listener.
*
* @return the ErrorListener in use. Note that this is not necessarily the ErrorListener that was supplied
* to the {@link #setErrorListener(ErrorListener)} method; if that was not an {@link UnfailingErrorListener},
* it will have been wrapped in a {@link DelegatingErrorListener}, and it is the DelegatingErrorListener
* that this method returns.
*/
public UnfailingErrorListener getErrorListener() {
return errorListener;
}
/**
* Report a recoverable error. This is an XSLT concept: by default, such an error results in a warning
* message, and processing continues. In XQuery, however, there are no recoverable errors so a fatal
* error is reported.
*
* This method is intended for internal use only.
*
* @param err An exception holding information about the error
* @throws XPathException if the error listener decides not to
* recover from the error
*/
public void recoverableError(XPathException err) throws XPathException {
if (executable.getHostLanguage() == Configuration.XQUERY || recoveryPolicy == Configuration.DO_NOT_RECOVER) {
throw err;
} else if (executable.getHostLanguage() == Configuration.XSLT && executable.isAllowXPath30()) {
errorListener.warning(err);
} else {
errorListener.error(err);
}
}
/**
* Report a fatal error
*
* @param err the error to be reported
*/
public void reportFatalError(XPathException err) {
if (!err.hasBeenReported()) {
if (err.getHostLanguage() == null) {
if (executable.getHostLanguage() == Configuration.XSLT) {
err.setHostLanguage("XSLT");
} else if (executable.getHostLanguage() == Configuration.XQUERY) {
err.setHostLanguage("XQuery");
}
}
getErrorListener().fatalError(err);
err.setHasBeenReported(true);
}
}
/**
* Report a warning
*
* @param message the warning message
* @param errorCode the local part of the error code (in the ERR namespace)
*/
public void warning(String message, String errorCode) {
getErrorListener().warning(new XPathException(message, errorCode));
}
/////////////////////////////////////////////////////////////////////////////////////////
// Methods for managing the various runtime control objects
/////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the Executable object.
*
* This method is intended for internal use only.
*
* @return the Executable (which represents the compiled stylesheet)
*/
public Executable getExecutable() {
return executable;
}
/**
* Get the document pool. This is used only for source documents, not for stylesheet modules.
*
* This method is intended for internal use only.
*
* @return the source document pool
*/
public DocumentPool getDocumentPool() {
return sourceDocumentPool;
}
/**
* Clear the document pool.
* This is sometimes useful when re-using the same Transformer
* for a sequence of transformations, but it isn't done automatically, because when
* the transformations use common look-up documents, the caching is beneficial.
*/
public void clearDocumentPool() {
for (PackageData pack : getExecutable().getPackages()) {
sourceDocumentPool.discardIndexes(pack.getKeyManager());
}
sourceDocumentPool = new DocumentPool();
}
/**
* Get the bindery for the global variables in a particular package.
*
* This method is intended for internal use only.
*
* @param packageData the package for which the variables are required
* @return the Bindery (in which values of all variables for the requested package are held)
*/
public Bindery getBindery(PackageData packageData) {
Bindery b = binderies.get(packageData);
if (b == null) {
b = new Bindery(packageData);
binderies.put(packageData, b);
}
return b;
}
/**
* Set the item used as the context for evaluating global variables. This value is used
* as the global context item by XQuery and XPath, and also by XSLT 3.0 interfaces such
* as {@link #applyTemplates(Sequence, Receiver)} and {@link #callTemplate(StructuredQName, Receiver)}.
* It is not used by the {@link #transform(Source, Receiver)} and {@link #transformDocument(NodeInfo, Receiver)}
* methods; by contrast, these overwrite the global context item with a value based on the node supplied
* as the source of the transformation.
*
* @param contextItem the context item for evaluating global variables, or null if there is none
* @since 9.7
*/
public void setGlobalContextItem(Item contextItem) {
if (contextItem instanceof NodeInfo) {
// In XSLT, apply strip-space and strip-type-annotations options
contextItem = prepareInputTree((NodeInfo) contextItem);
}
this.globalContextItem = contextItem;
this.globalContextItemPreset = true;
}
/**
* Get the item used as the context for evaluating global variables. In XQuery this
* is the same as the initial context item; in XSLT 1.0 and 2.0 it is the root of the tree containing
* the initial context node; in XSLT 3.0 it can be set independently of the initial match selection.
*
* @return the context item for evaluating global variables, or null if there is none
* @since 9.7
*/
/*@Nullable*/
public Item getGlobalContextItem() {
return globalContextItem;
// See bug 5224, which points out that the rules for XQuery 1.0 weren't clearly defined
}
/**
* Set an object that will be used to resolve URIs used in
* document(), etc.
*
* @param resolver An object that implements the URIResolver interface, or
* null.
*/
public void setURIResolver(URIResolver resolver) {
userURIResolver = resolver;
if (resolver instanceof StandardURIResolver) {
((StandardURIResolver) resolver).setConfiguration(getConfiguration());
}
}
/**
* Get the URI resolver.
*
* This method changed in Saxon 8.5, to conform to the JAXP specification. If there
* is no user-specified URIResolver, it now returns null; previously it returned the system
* default URIResolver.
*
* @return the user-supplied URI resolver if there is one, or null otherwise.
*/
public URIResolver getURIResolver() {
return userURIResolver;
}
/**
* Get the fallback URI resolver. This is the URIResolver that Saxon uses when
* the user-supplied URI resolver returns null.
*
* This method is intended for internal use only.
*
* @return the the system-defined URIResolver
*/
public URIResolver getStandardURIResolver() {
return standardURIResolver;
}
/**
* Set the URI resolver for secondary output documents.
*
* XSLT 2.0 introduces the xsl:result-document
* In Saxon 9.5, because xsl:result-document is now multi-threaded, the
* supplied resolver is cloned each time a new result document is created.
* The cloned resolved is therefore able to maintain information about
* the specific result document for use when its close() method is called,
* without worrying about thread safety.
*
* @param resolver An object that implements the OutputURIResolver
* interface, or null.
* @since 8.4
*/
public void setOutputURIResolver(/*@Nullable*/ OutputURIResolver resolver) {
if (resolver == null) {
outputURIResolver = config.getOutputURIResolver();
} else {
outputURIResolver = resolver;
}
}
/**
* Get the output URI resolver.
*
* @return the user-supplied URI resolver if there is one, or the
* system-defined one otherwise.
* @see #setOutputURIResolver
* @since 8.4
*/
/*@Nullable*/
public OutputURIResolver getOutputURIResolver() {
return outputURIResolver;
}
/**
* Set an UnparsedTextURIResolver to be used to resolve URIs passed to the XSLT
* unparsed-text() function.
*
* @param resolver the unparsed text URI resolver to be used. This replaces any unparsed text
* URI resolver previously registered.
* @since 8.9
*/
public void setUnparsedTextURIResolver(UnparsedTextURIResolver resolver) {
unparsedTextResolver = resolver;
}
/**
* Get the URI resolver for the unparsed-text() function. This will
* return the UnparsedTextURIResolver previously set using the {@link #setUnparsedTextURIResolver}
* method.
*
* @return the registered UnparsedTextURIResolver
* @since 8.9
*/
public UnparsedTextURIResolver getUnparsedTextURIResolver() {
return unparsedTextResolver;
}
/**
* Set the CollectionURIResolver used for resolving collection URIs.
* Defaults to the CollectionURIResolver registered with the Configuration
*
* @param resolver the resolver for references to collections
* @since 9.4
* @deprecated since 9.7. Use {@link #setCollectionFinder(CollectionFinder)}
*/
public void setCollectionURIResolver(CollectionURIResolver resolver) {
setCollectionFinder(new CollectionURIResolverWrapper(resolver));
}
/**
* Get the CollectionURIResolver used for resolving references to collections.
* If none has been set on the Controller, returns the
* CollectionURIResolver registered with the Configuration
*
* @return the resolver for references to collections
* @since 9.4
* @deprecated since 9.7. Use {@link #getCollectionFinder()}
*/
/*@NotNull*/
public CollectionURIResolver getCollectionURIResolver() {
CollectionFinder finder = getCollectionFinder();
if (finder instanceof CollectionURIResolverWrapper) {
return ((CollectionURIResolverWrapper) finder).getCollectionURIResolver();
} else {
return null;
}
}
/**
* Get the collection finder associated with this configuration. This is used to dereference
* collection URIs used in the fn:collection and fn:uri-collection functions
*
* @return the CollectionFinder to be used
* @since 9.7
*/
public CollectionFinder getCollectionFinder() {
if (collectionFinder == null) {
collectionFinder = config.getCollectionFinder();
}
return collectionFinder;
}
/**
* Set the collection finder associated with this configuration. This is used to dereference
* collection URIs used in the fn:collection and fn:uri-collection functions
*
* @param cf the CollectionFinder to be used
* @since 9.7
*/
public void setCollectionFinder(CollectionFinder cf) {
collectionFinder = cf;
}
/**
* Set the name of the default collection. Defaults to the default collection
* name registered with the Configuration.
*
* @param uri the collection URI of the default collection. May be null, to cause
* fallback to the collection name registered with the Configuration. The name will be passed
* to the collection URI resolver to identify the documents in the collection, unless
* the name is http://saxon.sf.net/collection/empty
which always refers
* to the empty collection.
* @since 9.4
*/
public void setDefaultCollection(String uri) {
defaultCollectionURI = uri;
}
/**
* Get the name of the default collection. Defaults to the default collection
* name registered with the Configuration.
*
* @return the collection URI of the default collection. If no value has been
* set explicitly, the collection URI registered with the Configuration is returned
* @since 9.4
*/
public String getDefaultCollection() {
return defaultCollectionURI == null ? getConfiguration().getDefaultCollection() : defaultCollectionURI;
}
/**
* Ask whether source documents loaded using the doc(), document(), and collection()
* functions, or supplied as a StreamSource or SAXSource to the transform() or addParameter() method
* should be subjected to schema validation
*
* @return the schema validation mode previously set using setSchemaValidationMode(),
* or the default mode (derived from the global Configuration) otherwise.
*/
public int getSchemaValidationMode() {
return validationMode;
}
/**
* Say whether source documents loaded using the doc(), document(), and collection()
* functions, or supplied as a StreamSource or SAXSource to the transform() or addParameter() method,
* should be subjected to schema validation. The default value is taken
* from the corresponding property of the Configuration.
*
* @param validationMode the validation (or construction) mode to be used for source documents.
* One of {@link Validation#STRIP}, {@link Validation#PRESERVE}, {@link Validation#STRICT},
* {@link Validation#LAX}
* @since 9.2
*/
public void setSchemaValidationMode(int validationMode) {
this.validationMode = validationMode;
}
/**
* Get the KeyManager.
*
* This method is intended for internal use only.
*
* @return the KeyManager, which holds details of all key declarations
*/
public KeyManager getKeyManager() {
return executable.getKeyManager();
}
/**
* Set the tree model to use. Default is the tiny tree
*
* @param model typically one of the constants {@link net.sf.saxon.om.TreeModel#TINY_TREE},
* {@link net.sf.saxon.om.TreeModel#TINY_TREE_CONDENSED}, or {@link net.sf.saxon.om.TreeModel#LINKED_TREE}.
* It is also possible to use a user-defined tree model.
* @since 9.2
*/
public void setModel(TreeModel model) {
treeModel = model;
}
/**
* Get the tree model that will be used.
*
* @return typically one of the constants {@link net.sf.saxon.om.TreeModel#TINY_TREE},
* {@link TreeModel#TINY_TREE_CONDENSED}, or {@link TreeModel#LINKED_TREE}.
* It is also possible to use a user-defined tree model.
* @since 9.2
*/
public TreeModel getModel() {
return treeModel;
}
/**
* Make a builder for the selected tree model.
*
* @return an instance of the Builder for the chosen tree model
* @since 8.4
*/
public Builder makeBuilder() {
Builder b = treeModel.makeBuilder(makePipelineConfiguration());
b.setTiming(config.isTiming());
b.setLineNumbering(config.isLineNumbering());
return b;
}
/**
* Say whether the transformation should perform whitespace stripping as defined
* by the xsl:strip-space and xsl:preserve-space declarations in the stylesheet
* in the case where a source tree is supplied to the transformation as a tree
* (typically a DOMSource, or a Saxon NodeInfo).
* The default is true. It is legitimate to suppress whitespace
* stripping if the client knows that all unnecessary whitespace has already been removed
* from the tree before it is processed. Note that this option applies to all source
* documents for which whitespace-stripping is normally applied, that is, both the
* principal source documents, and documents read using the doc(), document(), and
* collection() functions. It does not apply to source documents that are supplied
* in the form of a SAXSource or StreamSource, for which whitespace is stripped
* during the process of tree construction.
* Generally, stripping whitespace speeds up the transformation if it is done
* while building the source tree, but slows it down if it is applied to a tree that
* has already been built. So if the same source tree is used as input to a number
* of transformations, it is better to strip the whitespace once at the time of
* tree construction, rather than doing it on-the-fly during each transformation.
*
* @param strip true if whitespace is to be stripped from supplied source trees
* as defined by xsl:strip-space; false to suppress whitespace stripping
* @since 9.3
*/
public void setStripSourceTrees(boolean strip) {
stripSourceTrees = strip;
}
/**
* Ask whether the transformation will perform whitespace stripping for supplied source trees as defined
* by the xsl:strip-space and xsl:preserve-space declarations in the stylesheet.
*
* @return true unless whitespace stripping has been suppressed using
* {@link #setStripSourceTrees(boolean)}.
* @since 9.3
*/
public boolean isStripSourceTree() {
return stripSourceTrees;
}
/**
* Make a Stripper configured to implement the whitespace stripping rules.
* In the case of XSLT the whitespace stripping rules are normally defined
* by xsl:strip-space
and xsl:preserve-space
* This method is intended for internal use only.
*
* @param doc the root node of the document to be added. Must not be null.
* @param uri the document-URI property of this document. If non-null, the document is registered
* in the document pool with this as its document URI.
* @throws XPathException if an error occurs
*/
public void registerDocument(TreeInfo doc, DocumentURI uri) throws XPathException {
if (!getExecutable().isSchemaAware() && !Untyped.getInstance().equals(doc.getRootNode().getSchemaType())) {
boolean isXSLT = getExecutable().getHostLanguage() == Configuration.XSLT;
String message;
if (isXSLT) {
message = "The source document has been schema-validated, but" +
" the stylesheet is not schema-aware. A stylesheet is schema-aware if" +
" either (a) it contains an xsl:import-schema declaration, or (b) the stylesheet compiler" +
" was configured to be schema-aware.";
} else {
message = "The source document has been schema-validated, but" +
" the query is not schema-aware. A query is schema-aware if" +
" either (a) it contains an 'import schema' declaration, or (b) the query compiler" +
" was configured to be schema-aware.";
}
throw new XPathException(message);
}
if (uri != null) {
sourceDocumentPool.add(doc, uri);
}
}
////////////////////////////////////////////////////////////////////////////////
// Methods for registering and retrieving handlers for template rules
////////////////////////////////////////////////////////////////////////////////
/**
* Get the Rule Manager.
*
* This method is intended for internal use only.
*
* @return the Rule Manager, used to hold details of template rules for
* all modes; or null in the case of a non-XSLT controller
*/
public RuleManager getRuleManager() {
Executable exec = getExecutable();
return exec instanceof PreparedStylesheet ? ((PreparedStylesheet) getExecutable()).getRuleManager() : null;
}
/////////////////////////////////////////////////////////////////////////
// Methods for tracing
/////////////////////////////////////////////////////////////////////////
/**
* Set a TraceListener, replacing any existing TraceListener
* This method has no effect unless the stylesheet or query was compiled
* with tracing enabled.
*
* @param listener the TraceListener to be set. May be null, in which case
* trace events will not be reported
* @since 9.2
*/
public void setTraceListener(TraceListener listener) {
this.traceListener = listener;
}
/**
* Get the TraceListener. By default, there is no TraceListener, and this
* method returns null. A TraceListener may be added using the method
* {@link #addTraceListener}. If more than one TraceListener has been added,
* this method will return a composite TraceListener. Because the form
* this takes is implementation-dependent, this method is not part of the
* stable Saxon public API.
*
* @return the TraceListener used for XSLT or XQuery instruction tracing, or null if absent.
*/
/*@Nullable*/
public TraceListener getTraceListener() {
return traceListener;
}
/**
* Test whether instruction execution is being traced. This will be true
* if (a) at least one TraceListener has been registered using the
* {@link #addTraceListener} method, and (b) tracing has not been temporarily
* paused using the {@link #pauseTracing} method.
*
* @return true if tracing is active, false otherwise
* @since 8.4
*/
public final boolean isTracing() {
return traceListener != null && !tracingPaused;
}
/**
* Pause or resume tracing. While tracing is paused, trace events are not sent to any
* of the registered TraceListeners.
*
* @param pause true if tracing is to pause; false if it is to resume
* @since 8.4
*/
public final void pauseTracing(boolean pause) {
tracingPaused = pause;
}
/**
* Adds the specified trace listener to receive trace events from
* this instance. Note that although TraceListeners can be added
* or removed dynamically, this has no effect unless the stylesheet
* or query has been compiled with tracing enabled. This is achieved
* by calling {@link Configuration#setTraceListener} or by setting
* the attribute {@link net.sf.saxon.lib.FeatureKeys#TRACE_LISTENER} on the
* TransformerFactory. Conversely, if this property has been set in the
* Configuration or TransformerFactory, the TraceListener will automatically
* be added to every Controller that uses that Configuration.
*
* @param trace the trace listener. If null is supplied, the call has no effect.
* @since 8.4
*/
public void addTraceListener(/*@Nullable*/ TraceListener trace) {
if (trace != null) {
traceListener = TraceEventMulticaster.add(traceListener, trace);
}
}
/**
* Removes the specified trace listener so that the listener will no longer
* receive trace events.
*
* @param trace the trace listener.
* @since 8.4
*/
public void removeTraceListener(TraceListener trace) {
traceListener = TraceEventMulticaster.remove(traceListener, trace);
}
/**
* Set the destination for output from the fn:trace() function.
* By default, the destination is System.err. If a TraceListener is in use,
* this is ignored, and the trace() output is sent to the TraceListener.
*
* @param stream the PrintStream to which trace output will be sent. If set to
* null, trace output is suppressed entirely. It is the caller's responsibility
* to close the stream after use.
* @since 9.1. Changed in 9.6 to use a Logger
*/
public void setTraceFunctionDestination(Logger stream) {
traceFunctionDestination = stream;
}
/**
* Get the destination for output from the fn:trace() function.
*
* @return the PrintStream to which trace output will be sent. If no explicitly
* destination has been set, returns System.err. If the destination has been set
* to null to suppress trace output, returns null.
* @since 9.1. Changed in 9.6 to use a Logger
*/
public Logger getTraceFunctionDestination() {
return traceFunctionDestination;
}
/**
* Ask whether assertions (xsl:assert instructions) have been enabled at run time. By default
* they are disabled at compile time. If assertions are enabled at compile time, then by
* default they will also be enabled at run time; but they can be disabled at run time by
* specific request. At compile time, assertions can be enabled for some packages and
* disabled for others; at run-time, they can only be enabled or disabled globally.
*
* @return true if assertions are enabled at run time
* @since 9.7
*/
public boolean isAssertionsEnabled() {
return assertionsEnabled;
}
/**
* Ask whether assertions (xsl:assert instructions) have been enabled at run time. By default
* they are disabled at compile time. If assertions are enabled at compile time, then by
* default they will also be enabled at run time; but they can be disabled at run time by
* specific request. At compile time, assertions can be enabled for some packages and
* disabled for others; at run-time, they can only be enabled or disabled globally.
*
* @param enabled true if assertions are to be enabled at run time; this has no effect
* if assertions were disabled (for a particular package) at compile time
* @since 9.7
*/
public void setAssertionsEnabled(boolean enabled) {
this.assertionsEnabled = enabled;
}
/**
* Initialize the controller ready for a new transformation. This method should not normally be called by
* users (it is done automatically when transform() is invoked). However, it is available as a low-level API
* especially for use with XQuery.
*
* @param params the values of stylesheet parameters. This must include static parameters as well as non-static parameters.
* @throws XPathException if an error occurs, for example if a required parameter is not supplied.
*/
public void initializeController(GlobalParameterSet params) throws XPathException {
// get a new bindery, to clear out any variables from previous runs
//topLevelBindery = new Bindery();
binderies = new HashMap();
// if (executable.getTopLevelPackage() != null) {
// binderies.put(executable.getTopLevelPackage(), topLevelBindery);
// }
//topLevelBindery.allocateGlobals(executable.getGlobalVariableMap());
// if parameters were supplied, set them up
executable.checkAllRequiredParamsArePresent(params);
//topLevelBindery.defineGlobalParameters(params);
globalParameters = params;
// Check the global context item
globalContextItem = executable.checkInitialContextItem(globalContextItem, newXPathContext());
if (traceListener != null) {
traceListener.open(this);
preEvaluateGlobals(newXPathContext());
}
}
public void setApplyFunctionConversionRulesToExternalVariables(boolean applyConversionRules) {
convertParameters = applyConversionRules;
//topLevelBindery.setApplyFunctionConversionRulesToExternalVariables(applyConversionRules);
}
/////////////////////////////////////////////////////////////////////////
// Allow user data to be associated with nodes on a tree
/////////////////////////////////////////////////////////////////////////
/**
* Get user data associated with a key. To retrieve user data, two objects are required:
* an arbitrary object that may be regarded as the container of the data (originally, and
* typically still, a node in a tree), and a name. The name serves to distingush data objects
* associated with the same node by different client applications.
*
* This method is intended primarily for internal use, though it may also be
* used by advanced applications.
*
* @param key an object acting as a key for this user data value. This must be equal
* (in the sense of the equals() method) to the key supplied when the data value was
* registered using {@link #setUserData}.
* @param name the name of the required property
* @return the value of the required property
*/
public Object getUserData(Object key, String name) {
String keyValue = key.hashCode() + " " + name;
// System.err.println("getUserData " + name + " on object returning " + userDataTable.get(key));
return userDataTable.get(keyValue);
}
/**
* Set user data associated with a key. To store user data, two objects are required:
* an arbitrary object that may be regarded as the container of the data (originally, and
* typically still, a node in a tree), and a name. The name serves to distingush data objects
* associated with the same node by different client applications.
*
* This method is intended primarily for internal use, though it may also be
* used by advanced applications.
*
* @param key an object acting as a key for this user data value. This can be any object, for example
* a node or a string. If data for the given object and name already exists, it is overwritten.
* @param name the name of the required property
* @param data the value of the required property. If null is supplied, any existing entry
* for the key is removed.
*/
public void setUserData(Object key, String name, /*@Nullable*/ Object data) {
// System.err.println("setUserData " + name + " on object to " + data);
String keyVal = key.hashCode() + " " + name;
if (data == null) {
userDataTable.remove(keyVal);
} else {
userDataTable.put(keyVal, data);
}
}
private void checkReadiness() throws XPathException {
if (inUse) {
throw new IllegalStateException(
"The Controller is being used recursively or concurrently. This is not permitted.");
}
if (binderies == null) {
throw new IllegalStateException("The Controller has not been initialized");
}
inUse = true;
clearPerTransformationData();
if (executable == null) {
throw new XPathException("Stylesheet has not been prepared");
}
if (!dateTimePreset) {
currentDateTime = null; // reset at start of each transformation
}
}
/**
* Perform a transformation from a Source document to a Result document.
*
* @param source The input for the source tree. May be null if and only if an
* initial template has been supplied.
* @param receiver The destination for the streamed result tree.
* @throws XPathException if the transformation fails. As a
* special case, the method throws a TerminationException (a subclass
* of XPathException) if the transformation was terminated using
* xsl:message terminate="yes".
*/
public void transform(Source source, Receiver receiver) throws XPathException {
checkReadiness();
if (source instanceof TreeInfo) {
source = ((TreeInfo) source).getRootNode();
}
if (source instanceof SAXSource && config.getBooleanProperty(FeatureKeys.IGNORE_SAX_SOURCE_PARSER)) {
// This option is provided to allow the parser set by applications such as Ant to be overridden by
// the parser requested using FeatureKeys.SOURCE_PARSER
((SAXSource) source).setXMLReader(null);
}
boolean close = false;
try {
NodeInfo startNode = null;
boolean wrap = true;
boolean streaming = false;
int validationMode = getSchemaValidationMode();
Source underSource = source;
if (source instanceof AugmentedSource) {
Boolean localWrap = ((AugmentedSource) source).getWrapDocument();
if (localWrap != null) {
wrap = localWrap;
}
close = ((AugmentedSource) source).isPleaseCloseAfterUse();
int localValidate = ((AugmentedSource) source).getSchemaValidation();
if (localValidate != Validation.DEFAULT) {
validationMode = localValidate;
}
if (validationMode == Validation.STRICT || validationMode == Validation.LAX) {
// If validation of a DOMSource or NodeInfo is requested, we must copy it, we can't wrap it
wrap = false;
}
underSource = ((AugmentedSource) source).getContainedSource();
}
Source s2 = config.getSourceResolver().resolveSource(underSource, config);
if (s2 != null) {
underSource = s2;
}
if (wrap && (underSource instanceof NodeInfo || underSource instanceof DOMSource)) {
startNode = prepareInputTree(underSource);
String uri = underSource.getSystemId();
TreeInfo root = startNode.getTreeInfo();
if (root != null) {
registerDocument(startNode.getTreeInfo(), uri == null ? null : new DocumentURI(uri));
}
} else if (source == null) {
if (initialMode != null) {
throw new XPathException("Either a source document or initial match selection must be specified for an initial mode", "XTDE0044");
}
if (initialTemplate == null && initialFunction == null) {
NamedTemplate t = ((PreparedStylesheet) getExecutable()).getNamedTemplate(
new StructuredQName("", NamespaceConstant.XSLT, "initial-template"));
if (t == null) {
throw new XPathException("Either a source document, an initial template or an initial function must be specified");
} else {
initialTemplate = t;
}
}
} else {
Mode mode = getInitialMode();
if (mode == null) {// || (initialMode != null && mode.isEmpty())) {
throw new XPathException("Requested initial mode " +
(initialMode == null ? "" : initialMode.getModeName().getDisplayName()) +
" does not exist", "XTDE0045");
}
if (mode.isDeclaredStreamable()) {
if (source instanceof StreamSource || source instanceof SAXSource) {
streaming = true;
transformStream(source, mode, receiver);
} else {
throw new XPathException("Requested initial mode " +
(initialMode == null ? "" : initialMode.getModeName().getDisplayName()) +
" is streamable: must supply a SAXSource or StreamSource", "SXST0061");
}
} else {
// The input is a SAXSource or StreamSource or AugmentedSource, or
// a DOMSource with wrap=no: build the document tree
startNode = makeSourceTree(source, close, validationMode);
}
}
if (!streaming) {
transformDocument(startNode, receiver);
}
} catch (TerminationException err) {
//System.err.println("Processing terminated using xsl:message");
if (!err.hasBeenReported()) {
reportFatalError(err);
}
throw err;
} catch (XPathException err) {
Throwable cause = err.getException();
if (cause != null && cause instanceof SAXParseException) {
// This generally means the error was already reported.
// But if a RuntimeException occurs in Saxon during a callback from
// the Crimson parser, Crimson wraps this in a SAXParseException without
// reporting it further.
SAXParseException spe = (SAXParseException) cause;
cause = spe.getException();
if (cause instanceof RuntimeException) {
reportFatalError(err);
}
} else {
reportFatalError(err);
}
throw err;
} finally {
inUse = false;
if (close && source instanceof AugmentedSource) {
((AugmentedSource) source).close();
}
if (messageReceiver != null) {
messageReceiver.close();
}
//principalResultURI = null;
}
}
/**
* Make a source tree from a source supplied as a StreamSource or SAXSource
* @param source the source
* @param close true if the source is to be closed after use
* @param validationMode indicates whether the source should be schema-validated
* @return the root of the constructed tree
* @throws XPathException if tree construction fails
*/
public NodeInfo makeSourceTree(Source source, boolean close, int validationMode) throws XPathException {
Builder sourceBuilder = makeBuilder();
if (sourceBuilder instanceof TinyBuilder) {
((TinyBuilder) sourceBuilder).setStatistics(Statistics.SOURCE_DOCUMENT_STATISTICS);
}
//Sender sender = new Sender(sourceBuilder.getPipelineConfiguration());
Receiver r = sourceBuilder;
if (config.isStripsAllWhiteSpace() || executable.stripsWhitespace() ||
validationMode == Validation.STRICT || validationMode == Validation.LAX) {
r = makeStripper(sourceBuilder);
}
if (executable.stripsInputTypeAnnotations()) {
r = config.getAnnotationStripper(r);
}
PipelineConfiguration pipe = sourceBuilder.getPipelineConfiguration();
pipe.getParseOptions().setSchemaValidationMode(validationMode);
r.setPipelineConfiguration(pipe);
Sender.send(source, r, null);
if (close) {
((AugmentedSource) source).close();
}
NodeInfo doc = sourceBuilder.getCurrentRoot();
globalContextItem = doc;
sourceBuilder.reset();
if (source.getSystemId() != null) {
registerDocument(doc.getTreeInfo(), new DocumentURI(source.getSystemId()));
}
return doc;
}
/**
* Perform a transformation from a Source document to a Result document.
*
* @param source The input for the source tree. May be null if and only if an
* initial template has been supplied.
* @param outputDestination The destination for the result sequence.
* @throws XPathException if the transformation fails. As a
* special case, the method throws a TerminationException (a subclass
* of XPathException) if the transformation was terminated using
* xsl:message terminate="yes".
*/
public void applyTemplates(Sequence source, Receiver outputDestination) throws XPathException {
checkReadiness();
boolean close = false;
try {
Mode mode = initialMode;
if (mode == null) {
mode = ((PreparedStylesheet) executable).getRuleManager().getUnnamedMode();
}
if (mode == null) {
throw new XPathException("Requested initial mode " +
(initialMode == null ? "#unnamed" : initialMode.getModeName().getDisplayName()) +
" does not exist", "XTDE0045");
}
if (mode.isDeclaredStreamable()) {
throw new XPathException("Requested initial mode " +
(initialMode == null ? "#unnamed" : initialMode.getModeName().getDisplayName()) +
" is streamable: must supply a StreamSource or SAXSource");
}
openMessageEmitter();
// In tracing/debugging mode, evaluate all the global variables first
// if (traceListener != null) {
// preEvaluateGlobals(initialContext);
// }
// Determine whether we need to close the output stream at the end. We
// do this if the Result object is a StreamResult and is supplied as a
// system ID, not as a Writer or OutputStream
boolean mustClose = false;
// boolean mustClose = (outputDestination instanceof StreamResult &&
// ((StreamResult) outputDestination).getOutputStream() == null);
principalResult = outputDestination;
if (principalResultURI == null) {
principalResultURI = outputDestination.getSystemId();
}
// Process the source document by applying template rules to the initial context node
ParameterSet ordinaryParams = null;
if (initialTemplateParams != null) {
ordinaryParams = new ParameterSet(initialTemplateParams);
}
ParameterSet tunnelParams = null;
if (initialTemplateTunnelParams != null) {
tunnelParams = new ParameterSet(initialTemplateTunnelParams);
}
XPathContextMajor initialContext = newXPathContext();
initialContext.createThreadManager();
initialContext.setOrigin(this);
SequenceIterator iter = source.iterate();
initialContext.setCurrentIterator(new FocusTrackingIterator(iter));
Item next;
while ((next = iter.next()) != null) {
if (next instanceof NodeInfo) {
NodeInfo startNode = (NodeInfo) next;
if (startNode.getConfiguration() == null) {
// must be a non-standard document implementation
throw new XPathException("The supplied source document must be associated with a Configuration");
}
if (!startNode.getConfiguration().isCompatible(executable.getConfiguration())) {
throw new XPathException(
"Source document and stylesheet must use the same or compatible Configurations",
SaxonErrorCode.SXXP0004);
}
if (startNode.getTreeInfo().isTyped() && !executable.isSchemaAware()) {
throw new XPathException("Cannot use a schema-validated source document unless the stylesheet is schema-aware");
}
}
}
// Apply xsl:strip-space and @strip-type-annotations attributes from the stylesheet
// TODO: these should also be applied in the case of non-document nodes
if (stripSourceTrees && executable.stripsWhitespace()) {
ItemMappingFunction- spaceStripper =
new ItemMappingFunction
- () {
public Item mapItem(Item item) throws XPathException {
if (item instanceof NodeInfo && ((NodeInfo) item).getNodeKind() == Type.DOCUMENT) {
return new SpaceStrippedDocument(
((NodeInfo) item).getTreeInfo(), executable.getStripperRules()).getRootNode();
} else {
return item;
}
}
};
iter = new ItemMappingIterator(iter, spaceStripper);
}
if (executable.stripsInputTypeAnnotations()) {
ItemMappingFunction
- typeStripper =
new ItemMappingFunction
- () {
public Item mapItem(Item item) throws XPathException {
if (item instanceof NodeInfo && ((NodeInfo) item).getNodeKind() == Type.DOCUMENT) {
return new TypeStrippedDocument(((NodeInfo) item).getTreeInfo()).getRootNode();
} else {
return item;
}
}
};
iter = new ItemMappingIterator(iter, typeStripper);
}
outputDestination = openResult(outputDestination, initialContext);
initialContext.setCurrentIterator(new FocusTrackingIterator(iter.getAnother()));
initialContext.setCurrentMode(mode.getDeclaringComponent()); // TODO: it should be the top-level override of the mode
initialContext.setCurrentComponent(mode.getDeclaringComponent());
TailCall tc = mode.applyTemplates(ordinaryParams, tunnelParams, initialContext, ExplicitLocation.UNKNOWN_LOCATION);
while (tc != null) {
tc = tc.processLeavingTail();
}
initialContext.waitForChildThreads();
closeResult(outputDestination, mustClose, initialContext);
} catch (TerminationException err) {
//System.err.println("Processing terminated using xsl:message");
if (!err.hasBeenReported()) {
reportFatalError(err);
}
throw err;
} catch (XPathException err) {
Throwable cause = err.getException();
if (cause != null && cause instanceof SAXParseException) {
// This generally means the error was already reported.
// But if a RuntimeException occurs in Saxon during a callback from
// the Crimson parser, Crimson wraps this in a SAXParseException without
// reporting it further.
SAXParseException spe = (SAXParseException) cause;
cause = spe.getException();
if (cause instanceof RuntimeException) {
reportFatalError(err);
}
} else {
reportFatalError(err);
}
throw err;
} finally {
inUse = false;
closeMessageEmitter();
if (traceListener != null) {
traceListener.close();
}
if (close && source instanceof AugmentedSource) {
((AugmentedSource) source).close();
}
principalResultURI = null;
}
}
/**
* Prepare an input tree for processing. This is used when either the initial
* input, or a Source returned by the document() function, is a NodeInfo or a
* DOMSource. The preparation consists of wrapping a DOM document inside a wrapper
* that implements the NodeInfo interface, and/or adding a space-stripping wrapper
* if the stylesheet strips whitespace nodes, and/or adding a type-stripping wrapper
* if the stylesheet strips input type annotations.
*
* This method is intended for internal use.
*
* @param source the input tree. Must be either a DOMSource or a NodeInfo
* @return the NodeInfo representing the input node, suitably wrapped.
*/
public NodeInfo prepareInputTree(Source source) {
NodeInfo start = getConfiguration().unravel(source);
if (stripSourceTrees && executable.stripsWhitespace()) {
TreeInfo docInfo = start.getTreeInfo();
SpaceStrippedDocument strippedDoc = new SpaceStrippedDocument(docInfo, getSpaceStrippingRule());
start = strippedDoc.wrap(start);
}
if (executable.stripsInputTypeAnnotations()) {
TreeInfo docInfo = start.getTreeInfo();
if (!Untyped.getInstance().equals(docInfo.getRootNode().getSchemaType())) {
TypeStrippedDocument strippedDoc = new TypeStrippedDocument(docInfo);
start = strippedDoc.wrap(start);
}
}
return start;
}
/**
* Transform a source XML document supplied as a tree.
*
* This method is intended for internal use. External applications should use
* the {@link #transform} method, which is part of the JAXP interface. Note that
* NodeInfo
implements the JAXP Source
interface, so
* it may be supplied directly to the transform() method.
*
* @param startNode A Node that identifies the source document to be
* transformed and the node where the transformation should start.
* May be null if the transformation is to start using an initial template.
* @param outputDestination The output destination
* @throws XPathException if any dynamic error occurs
*/
public void transformDocument(NodeInfo startNode, Receiver outputDestination)
throws XPathException {
if (executable == null) {
throw new XPathException("Stylesheet has not been compiled");
}
openMessageEmitter();
// Determine whether we need to close the output stream at the end. We
// do this if the Result object is a StreamResult and is supplied as a
// system ID, not as a Writer or OutputStream
boolean mustClose = false;
// boolean mustClose = (outputDestination instanceof StreamResult &&
// ((StreamResult) outputDestination).getOutputStream() == null);
try {
principalResult = outputDestination;
if (principalResultURI == null) {
principalResultURI = outputDestination.getSystemId();
}
XPathContextMajor initialContext = newXPathContext();
initialContext.createThreadManager();
initialContext.setOrigin(this);
if (startNode != null) {
globalContextItem = startNode.getRoot();
if (startNode.getConfiguration() == null) {
// must be a non-standard document implementation
throw new XPathException("The supplied source document must be associated with a Configuration");
}
if (!startNode.getConfiguration().isCompatible(executable.getConfiguration())) {
throw new XPathException(
"Source document and stylesheet must use the same or compatible Configurations",
SaxonErrorCode.SXXP0004);
}
if (startNode.getTreeInfo().isTyped() && !executable.isSchemaAware()) {
throw new XPathException("Cannot use a schema-validated source document unless the stylesheet is schema-aware");
}
UnfailingIterator currentIter = SingletonIterator.makeIterator(startNode);
FocusIterator focus = new FocusTrackingIterator(currentIter);
if (initialTemplate != null) {
focus.next();
}
initialContext.setCurrentIterator(focus);
}
// In tracing/debugging mode, evaluate all the global variables first
/* See bug 2606
if (traceListener != null) {
preEvaluateGlobals(initialContext);
} */
outputDestination = openResult(outputDestination, initialContext);
// Process the source document by applying template rules to the initial context node
ParameterSet ordinaryParams = null;
if (initialTemplateParams != null) {
ordinaryParams = new ParameterSet(initialTemplateParams);
}
ParameterSet tunnelParams = null;
if (initialTemplateTunnelParams != null) {
tunnelParams = new ParameterSet(initialTemplateTunnelParams);
}
if (initialTemplate == null) {
Mode mode = initialMode;
if (mode == null) {
mode = ((PreparedStylesheet) executable).getRuleManager().getUnnamedMode();
}
// if (initialMode != null && mode.isEmpty()) {
// throw new XPathException("Requested initial mode " +
// (initialMode == null ? "" : initialMode.getModeName().getDisplayName()) +
// " does not exist", "XTDE0045");
// }
if (mode.isDeclaredStreamable()) {
throw new XPathException("Requested initial mode " +
(initialMode == null ? "" : initialMode.getModeName().getDisplayName()) +
" is streamable: must supply a StreamSource or SAXSource");
}
if (startNode.getNodeKind() == Type.DOCUMENT && !getConfiguration().getBooleanProperty(FeatureKeys.SUPPRESS_XSLT_NAMESPACE_CHECK)) {
// Check for the common error where the source document is in a namespace, but the stylesheet is not
// designed to match that namespace.
NodeInfo topElement = startNode.iterateAxis(AxisInfo.CHILD, NodeKindTest.ELEMENT).next();
if (topElement != null) {
String uri = topElement.getURI();
Set explicitNamespaces = mode.getExplicitNamespaces(getConfiguration().getNamePool());
if (!explicitNamespaces.isEmpty() && !explicitNamespaces.contains(uri)) {
// We've established that there are no template rules matching the top-level element.
// But that's not a strong enough test, especially with document types that have an envelope/payload
// structure. We only report a warning if the set of namespaces in stylesheet patterns is completely
// disjoint from the set of namespaces on element names in the source document.
AxisIterator allElements = startNode.iterateAxis(AxisInfo.DESCENDANT, NodeKindTest.ELEMENT);
boolean found = false;
NodeInfo element;
while ((element = allElements.next()) != null) {
String ns = element.getURI();
if (explicitNamespaces.contains(ns)) {
found = true;
break;
}
}
if (!found) {
String suffix = "(Use --suppressXsltNamespaceCheck:on to avoid this warning)";
if (explicitNamespaces.size() == 1 && explicitNamespaces.contains("")) {
warning("The source document is in namespace " + uri +
", but all the template rules match elements in no namespace " + suffix, SaxonErrorCode.SXXP0005);
} else if (uri.equals("")) {
warning("The source document is in no namespace" +
", but the template rules all expect elements in a namespace " + suffix, SaxonErrorCode.SXXP0005);
} else {
warning("The source document is in namespace " + uri +
", but none of the template rules match elements in this namespace " + suffix, SaxonErrorCode.SXXP0005);
}
}
}
}
}
// TODO: should start with the top-level override of the mode
initialContext.setCurrentMode(mode.getDeclaringComponent());
initialContext.setCurrentComponent(mode.getDeclaringComponent());
TailCall tc = mode.applyTemplates(ordinaryParams, tunnelParams, initialContext, ExplicitLocation.UNKNOWN_LOCATION);
while (tc != null) {
tc = tc.processLeavingTail();
}
} else {
NamedTemplate t = initialTemplate;
XPathContextMajor c2 = initialContext.newContext();
initialContext.setOrigin(this);
c2.setCurrentComponent(t.getDeclaringComponent());
c2.openStackFrame(t.getStackFrameMap());
c2.setLocalParameters(ordinaryParams);
c2.setTunnelParameters(tunnelParams);
TailCall tc = t.expand(c2);
while (tc != null) {
tc = tc.processLeavingTail();
}
}
initialContext.waitForChildThreads();
closeResult(outputDestination, mustClose, initialContext);
} finally {
closeMessageEmitter();
if (traceListener != null) {
traceListener.close();
}
}
}
/**
* Transform a source XML document supplied as a tree.
*
* This method is intended for internal use. External applications should use
* the {@link #transform} method, which is part of the JAXP interface. Note that
* NodeInfo
implements the JAXP Source
interface, so
* it may be supplied directly to the transform() method.
*
* @param initialTemplateName the entry point, the name of a named template
* @param outputDestination The output destination
* @throws XPathException if any dynamic error occurs
*/
public void callTemplate(StructuredQName initialTemplateName, Receiver outputDestination)
throws XPathException {
checkReadiness();
if (initialMode != null && !initialMode.getModeName().equals(
((StylesheetPackage)getExecutable().getTopLevelPackage()).getDefaultMode())) {
throw new XPathException("Initial mode and template cannot both be defined", "XTDE0047");
}
openMessageEmitter();
// Determine whether we need to close the output stream at the end. We
// do this if the Result object is a StreamResult and is supplied as a
// system ID, not as a Writer or OutputStream
boolean mustClose = false;
try {
principalResult = outputDestination;
if (principalResultURI == null) {
principalResultURI = outputDestination.getSystemId();
}
XPathContextMajor initialContext = newXPathContext();
initialContext.createThreadManager();
initialContext.setOrigin(this);
if (globalContextItem != null) {
if (globalContextItem instanceof NodeInfo) {
NodeInfo startNode = (NodeInfo) globalContextItem;
if (startNode.getConfiguration() == null) {
// must be a non-standard document implementation
throw new XPathException("The supplied source document must be associated with a Configuration");
}
if (!startNode.getConfiguration().isCompatible(executable.getConfiguration())) {
throw new XPathException(
"Source document and stylesheet must use the same or compatible Configurations",
SaxonErrorCode.SXXP0004);
}
if (startNode.getTreeInfo().isTyped() && !executable.isSchemaAware()) {
throw new XPathException("Cannot use a schema-validated source document unless the stylesheet is schema-aware");
}
}
initialContext.setCurrentIterator(new ManualIterator(globalContextItem));
}
// In tracing/debugging mode, evaluate all the global variables first
/* See bug 2606
if (traceListener != null) {
preEvaluateGlobals(initialContext);
} */
outputDestination = openResult(outputDestination, initialContext);
// Process the source document by applying template rules to the initial context node
ParameterSet ordinaryParams = null;
if (initialTemplateParams != null) {
ordinaryParams = new ParameterSet(initialTemplateParams);
}
ParameterSet tunnelParams = null;
if (initialTemplateTunnelParams != null) {
tunnelParams = new ParameterSet(initialTemplateTunnelParams);
}
StylesheetPackage pack = (StylesheetPackage) executable.getTopLevelPackage();
Component initialComponent = pack.getComponent(new SymbolicName(StandardNames.XSL_TEMPLATE, initialTemplateName));
if (initialComponent == null) {
throw new XPathException("Template " + initialTemplateName.getDisplayName() + " does not exist (or is not public)", "XTDE0040");
}
//NamedTemplate t = ((PreparedStylesheet) executable).getNamedTemplate(initialTemplateName);
NamedTemplate t = (NamedTemplate) initialComponent.getCode();
XPathContextMajor c2 = initialContext.newContext();
initialContext.setOrigin(this);
c2.setCurrentComponent(initialComponent);
c2.openStackFrame(t.getStackFrameMap());
c2.setLocalParameters(ordinaryParams);
c2.setTunnelParameters(tunnelParams);
TailCall tc = t.expand(c2);
while (tc != null) {
tc = tc.processLeavingTail();
}
initialContext.waitForChildThreads();
closeResult(outputDestination, mustClose, initialContext);
} finally {
if (traceListener != null) {
traceListener.close();
}
closeMessageEmitter();
inUse = false;
}
}
/**
* Transform a source XML document supplied as a stream, in streaming mode.
*
* This method is intended for internal use. External applications should use
* the {@link #transform} method, which is part of the JAXP interface. Note that
* NodeInfo
implements the JAXP Source
interface, so
* it may be supplied directly to the transform() method.
*
* @param source the principal input document, supplied as a
* {@link SAXSource} or {@link StreamSource}
* @param mode the initial mode, which must be a streaming mode
* @param result The output destination
* @throws XPathException if any dynamic error occurs
*/
private void transformStream(Source source, Mode mode, Receiver result)
throws XPathException {
openMessageEmitter();
// Determine whether we need to close the output stream at the end. We
// do this if the Result object is a StreamResult and is supplied as a
// system ID, not as a Writer or OutputStream
boolean mustClose = result instanceof StreamResult &&
((StreamResult) result).getOutputStream() == null;
try {
principalResult = result;
if (principalResultURI == null) {
principalResultURI = result.getSystemId();
}
XPathContextMajor initialContext = newXPathContext();
initialContext.setOrigin(this);
globalContextItem = null;
result = openResult(result, initialContext);
// Process the source document by applying template rules to the initial context node
if (!mode.isDeclaredStreamable()) {
if (config.getBooleanProperty(FeatureKeys.STREAMING_FALLBACK)) {
warning("Mode is not streamable; attempting fallback to non-streamed evaluation", "");
TreeInfo doc = config.buildDocumentTree(source);
initialMode = mode;
transformDocument(doc.getRootNode(), result);
return;
} else {
throw new XPathException("mode supplied to transformStream() must be streamable");
}
}
Receiver despatcher = config.makeStreamingTransformer(initialContext, mode);
if (despatcher == null) {
throw new XPathException("Streaming requires Saxon-EE");
}
if (config.isStripsAllWhiteSpace() || executable.stripsWhitespace()) {
despatcher = makeStripper(despatcher);
}
PipelineConfiguration pipe = despatcher.getPipelineConfiguration();
pipe.getParseOptions().setSchemaValidationMode(validationMode);
boolean verbose = getConfiguration().isTiming();
if (verbose) {
getConfiguration().getLogger().info("Streaming " + source.getSystemId());
}
try {
Sender.send(source, despatcher, null);
} catch (QuitParsingException e) {
if (verbose) {
getConfiguration().getLogger().info("Streaming " + source.getSystemId() + " : early exit");
}
}
closeResult(result, mustClose, initialContext);
} finally {
if (traceListener != null) {
traceListener.close();
}
closeMessageEmitter();
}
}
/**
* Get a receiver to which the input to this transformation can be supplied
* as a stream of events, causing the transformation to be executed in streaming mode.
*
* This method is intended for internal use. External applications should use
* the {@link #transform} method, which is part of the JAXP interface. Note that
* NodeInfo
implements the JAXP Source
interface, so
* it may be supplied directly to the transform() method.
*
*
* @param mode the initial mode, which must be a streaming mode
* @param result The output destination
* @return a receiver to which events can be streamed
* @throws XPathException if any dynamic error occurs
*/
/*@Nullable*/
public Receiver getStreamingReceiver(Mode mode, Receiver result)
throws XPathException {
// TODO: this method does not check that the Controller is not already in use, nor does it mark it as being in use.
// System.err.println("*** TransformDocument");
if (executable == null) {
throw new XPathException("Stylesheet has not been compiled");
}
openMessageEmitter();
// Determine whether we need to close the output stream at the end. We
// do this if the Result object is a StreamResult and is supplied as a
// system ID, not as a Writer or OutputStream
final boolean mustClose =
result instanceof StreamResult && ((StreamResult) result).getOutputStream() == null;
principalResult = result;
if (principalResultURI == null) {
principalResultURI = result.getSystemId();
}
final XPathContextMajor initialContext = newXPathContext();
initialContext.setOrigin(this);
globalContextItem = null;
final Result result2 = openResult(result, initialContext);
// Process the source document by applying template rules to the initial context node
if (!mode.isDeclaredStreamable()) {
throw new XPathException("mode supplied to getStreamingReceiver() must be streamable");
}
Receiver despatcher = config.makeStreamingTransformer(initialContext, mode);
if (despatcher == null) {
throw new XPathException("Streaming requires Saxon-EE");
}
if (config.isStripsAllWhiteSpace() || executable.stripsWhitespace()) {
despatcher = makeStripper(despatcher);
}
despatcher.setPipelineConfiguration(makePipelineConfiguration());
return new ProxyReceiver(despatcher) {
public void close() throws XPathException {
if (traceListener != null) {
traceListener.close();
}
closeResult(result2, mustClose, initialContext);
closeMessageEmitter();
}
};
}
private void closeResult(Result result, boolean mustClose, XPathContextMajor initialContext) throws XPathException {
Receiver out = initialContext.getReceiver();
if (buildTree) {
out.endDocument();
}
out.close();
if (mustClose && result instanceof StreamResult) {
OutputStream os = ((StreamResult) result).getOutputStream();
if (os != null) {
try {
os.close();
} catch (IOException err) {
throw new XPathException(err);
}
}
}
}
private Receiver openResult(Receiver result, XPathContextMajor initialContext) throws XPathException {
Receiver receiver = result;
// if this is the implicit XSLT result document, and if the executable is capable
// of creating a secondary result document, then add a filter to check the first write
boolean openNow = false;
if (buildTree && getExecutable().createsSecondaryResult()) {
receiver = new ImplicitResultChecker(receiver, this);
receiver.setPipelineConfiguration(makePipelineConfiguration());
} else {
openNow = true;
}
receiver.getPipelineConfiguration().setController(this);
SequenceReceiver out;
if (result instanceof ComplexContentOutputter) {
out = (SequenceReceiver) receiver;
} else if (buildTree) {
out = ComplexContentOutputter.makeComplexContentReceiver(receiver, null);
} else if (receiver instanceof SequenceReceiver) {
out = (SequenceReceiver) receiver;
} else {
out = new TreeReceiver(receiver);
}
initialContext.setReceiver(out);
if (openNow) {
out.open();
if (buildTree) {
out.startDocument(0);
}
}
return result;
}
private void openMessageEmitter() throws XPathException {
if (getMessageEmitter() == null) {
Receiver me = makeMessageReceiver();
setMessageEmitter(me);
if (me instanceof Emitter && ((Emitter) me).getWriter() == null) {
((Emitter) me).setStreamResult(getConfiguration().getLogger().asStreamResult());
}
}
getMessageEmitter().open();
}
private void closeMessageEmitter() throws XPathException {
Receiver me = getMessageEmitter();
if (me != null) {
me.close();
}
}
/**
* Pre-evaluate global variables (when debugging/tracing).
*
* This method is intended for internal use.
*
* @param context the dynamic context for evaluating the global variables
* @throws XPathException - should not happen.
*/
public void preEvaluateGlobals(XPathContext context) throws XPathException {
for (PackageData pack : getExecutable().getPackages()) {
for (GlobalVariable var : pack.getGlobalVariableList()) {
if (!var.isUnused()) {
try {
var.evaluateVariable(context, var.getDeclaringComponent());
} catch (XPathException err) {
// Don't report an exception unless the variable is actually evaluated
SingletonClosure closure = new SingletonClosure(new ErrorExpression(err), context);
getBindery(var.getPackageData()).setGlobalVariable(var, closure);
}
}
}
}
}
/**
* Register the dependency of one variable ("one") upon another ("two"), throwing an exception if this would establish
* a cycle of dependencies.
*
* @param one the first (dependent) variable
* @param two the second (dependee) variable
* @throws XPathException if adding this dependency creates a cycle of dependencies among global variables.
*/
public synchronized void registerGlobalVariableDependency(GlobalVariable one, GlobalVariable two) throws XPathException {
if (one == two) {
throw new XPathException.Circularity("Circular dependency among global variables: "
+ one.getVariableQName().getDisplayName() + " depends on its own value");
}
Set transitiveDependencies = globalVariableDependencies.get(two);
if (transitiveDependencies != null) {
if (transitiveDependencies.contains(one)) {
throw new XPathException.Circularity("Circular dependency among variables: "
+ one.getVariableQName().getDisplayName() + " depends on the value of "
+ two.getVariableQName().getDisplayName() + ", which depends directly or indirectly on the value of "
+ one.getVariableQName().getDisplayName());
}
for (GlobalVariable var : transitiveDependencies) {
// register the transitive dependencies
registerGlobalVariableDependency(one, var);
}
}
Set existingDependencies = globalVariableDependencies.get(one);
if (existingDependencies == null) {
existingDependencies = new HashSet();
globalVariableDependencies.put(one, existingDependencies);
}
existingDependencies.add(two);
}
/**
* Set the current date and time for this query or transformation.
* This method is provided primarily for testing purposes, to allow tests to be run with
* a fixed date and time. The supplied date/time must include a timezone, which is used
* as the implicit timezone.
*
* Note that comparisons of date/time values currently use the implicit timezone
* taken from the system clock, not from the value supplied here.
*
* @param dateTime the date/time value to be used as the current date and time
* @throws IllegalStateException if a current date/time has already been
* established by calling getCurrentDateTime(), or by a previous call on setCurrentDateTime()
* @throws net.sf.saxon.trans.XPathException if the supplied dateTime contains no timezone
*/
public void setCurrentDateTime(/*@NotNull*/ DateTimeValue dateTime) throws XPathException {
if (currentDateTime == null) {
if (dateTime.getComponent(AccessorFn.Component.TIMEZONE) == null) {
throw new XPathException("No timezone is present in supplied value of current date/time");
}
currentDateTime = dateTime;
dateTimePreset = true;
} else {
throw new IllegalStateException(
"Current date and time can only be set once, and cannot subsequently be changed");
}
}
/**
* Get the current date and time for this query or transformation.
* All calls during one transformation return the same answer.
*
* @return Get the current date and time. This will deliver the same value
* for repeated calls within the same transformation
*/
/*@Nullable*/
public DateTimeValue getCurrentDateTime() {
if (currentDateTime == null) {
currentDateTime = new DateTimeValue(new GregorianCalendar(), true);
}
return currentDateTime;
}
/**
* Get the implicit timezone for this query or transformation
*
* @return the implicit timezone as an offset in minutes
*/
public int getImplicitTimezone() {
return getCurrentDateTime().getTimezoneInMinutes();
}
/////////////////////////////////////////
// Methods for handling dynamic context
/////////////////////////////////////////
/**
* Make an XPathContext object for expression evaluation.
*
* This method is intended for internal use.
*
* @return the new XPathContext
*/
public XPathContextMajor newXPathContext() {
XPathContextMajor c = new XPathContextMajor(this);
c.setCurrentOutputUri(principalResultURI);
return c;
}
/**
* Set the last remembered node, for node numbering purposes.
*
* This method is strictly for internal use only.
*
* @param node the node in question
* @param number the number of this node
*/
public void setRememberedNumber(NodeInfo node, int number) {
lastRememberedNode = node;
lastRememberedNumber = number;
}
/**
* Get the number of a node if it is the last remembered one.
*
* This method is strictly for internal use only.
*
* @param node the node for which remembered information is required
* @return the number of this node if known, else -1.
*/
public int getRememberedNumber(NodeInfo node) {
if (lastRememberedNode == node) {
return lastRememberedNumber;
}
return -1;
}
/**
* Indicate whether document projection should be used, and supply the PathMap used to control it.
* Note: this is available only under Saxon-EE.
*
* @param pathMap a path map to be used for projecting source documents
*/
public void setUseDocumentProjection(PathMap pathMap) {
this.pathMap = pathMap;
}
/**
* Get the path map used for document projection, if any.
*
* @return the path map to be used for document projection, if one has been supplied; otherwise null
*/
/*@Nullable*/
public PathMap getPathMapForDocumentProjection() {
return pathMap;
}
/**
* Get the stack of attribute sets being evaluated (for detection of cycles)
*
* @return the attribute set evaluation stack
*/
public Stack getAttributeSetEvaluationStack() {
return attributeSetEvaluationStack;
}
/**
* Get the cache of stylesheets (cached during calls on fn:transform()) for this query or transformation.
*
* @return the stylesheet cache
*/
public synchronized StylesheetCache getStylesheetCache() {
if (stylesheetCache == null) {
this.stylesheetCache = new StylesheetCache();
}
return stylesheetCache;
}
}