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

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

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon;

import net.sf.saxon.event.*;
import net.sf.saxon.expr.ContextOriginator;
import net.sf.saxon.expr.PackageData;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.XPathContextMajor;
import net.sf.saxon.expr.instruct.Bindery;
import net.sf.saxon.expr.instruct.Executable;
import net.sf.saxon.expr.instruct.GlobalParameterSet;
import net.sf.saxon.expr.instruct.GlobalVariable;
import net.sf.saxon.expr.parser.Loc;
import net.sf.saxon.expr.parser.PathMap;
import net.sf.saxon.expr.sort.GroupIterator;
import net.sf.saxon.functions.AccessorFn;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.*;
import net.sf.saxon.regex.RegexIterator;
import net.sf.saxon.s9api.HostLanguage;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.trace.TraceEventMulticaster;
import net.sf.saxon.trans.*;
import net.sf.saxon.trans.rules.RuleManager;
import net.sf.saxon.transpile.CSharp;
import net.sf.saxon.transpile.CSharpModifiers;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.tree.iter.GroundedIterator;
import net.sf.saxon.tree.tiny.TinyBuilder;
import net.sf.saxon.tree.wrapper.SpaceStrippedDocument;
import net.sf.saxon.tree.wrapper.SpaceStrippedNode;
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.SequenceType;
import net.sf.saxon.z.IntHashMap;
import org.xml.sax.SAXParseException;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * 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

* * @since 8.4. From 9.6 this class should no longer be considered a public API. In 9.9, the class * was split into two: XSLT-specific functionality has been moved into the subclass {@link XsltController}. */ //@CSharpInjectMembers(code = { // " public void setErrorReporter(System.Action reporter) {" // + " setErrorReporter(new Saxon.Impl.Helpers.ErrorReportingAction(reporter));" // + " }" //}) public class Controller implements ContextOriginator { private final Configuration config; protected Executable executable; protected Item globalContextItem; private boolean globalContextItemPreset; private Map binderies; private GlobalParameterSet globalParameters; private boolean convertParameters = true; private final Map> globalVariableDependencies = new HashMap<>(); protected TraceListener traceListener; private boolean tracingPaused; private Logger traceFunctionDestination; private ResourceResolver resourceResolver; protected Receiver principalResult; protected String principalResultURI; private UnparsedTextURIResolver unparsedTextResolver; private String defaultCollectionURI; private ErrorReporter errorReporter; private TreeModel treeModel = TreeModel.TINY_TREE; private DocumentPool sourceDocumentPool; private IntHashMap> localIndexes; private HashMap userDataTable; private NodeInfo lastRememberedNode = null; private int lastRememberedNumber = -1; private DateTimeValue currentDateTime; private boolean dateTimePreset = false; private PathMap pathMap = null; protected int validationMode = Validation.DEFAULT; protected boolean inUse = false; private boolean stripSourceTrees = true; //protected boolean buildTree = true; private CollectionFinder collectionFinder = null; public final static String ANONYMOUS_PRINCIPAL_OUTPUT_URI = "dummy:/anonymous/principal/result"; private StylesheetCache stylesheetCache = null; private Function focusTrackerFactory = CSharp.constructorRef(FocusTrackingIterator::new, 1); private Function multiThreadedFocusTrackerFactory; /** * 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 Transformers * thus saving resources associated with the creation of new Transformers.

*

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} Objects, 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(); focusTrackerFactory = config.getFocusTrackerFactory(executable, false); multiThreadedFocusTrackerFactory = config.getFocusTrackerFactory(executable, true); //standardURIResolver = config.getSystemURIResolver(); resourceResolver = null; unparsedTextResolver = config.getUnparsedTextURIResolver(); validationMode = config.getSchemaValidationMode(); errorReporter = config.makeErrorReporter(); 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; currentDateTime = null; dateTimePreset = false; clearPerTransformationData(); } /** * Reset variables that need to be reset for each transformation if the controller * is serially reused */ protected synchronized void clearPerTransformationData() { userDataTable = new HashMap<>(20); principalResult = null; tracingPaused = false; lastRememberedNode = null; lastRememberedNumber = -1; stylesheetCache = null; localIndexes = null; 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; } /** * 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. * @throws XPathException if a problem is found with the supplied parameter value */ public GroundedValue getConvertedParameter(StructuredQName name, SequenceType requiredType, XPathContext context) throws XPathException { GroundedValue 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 NodeInfo && !config.isCompatible(((NodeInfo) 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()) { DocumentKey key = new DocumentKey(systemId); DocumentPool pool = getDocumentPool(); if (pool.find(key) == null) { pool.add(((NodeInfo) val).getTreeInfo(), key); } } } catch (URISyntaxException err) { // ignore it } } val = val.materialize(); } 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 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; } /** * Allocate a SequenceOutputter for a new output destination. * * @return the allocated SequenceOutputter */ public SequenceCollector allocateSequenceOutputter() { PipelineConfiguration pipe = makePipelineConfiguration(); return new SequenceCollector(pipe, 20); } /** * Allocate a SequenceCollector for a new output destination. * * @param size the estimated size of the output sequence * @return SequenceOutputter the allocated SequenceOutputter */ public SequenceCollector allocateSequenceOutputter(int size) { PipelineConfiguration pipe = makePipelineConfiguration(); return new SequenceCollector(pipe, size); } /////////////////////////////////////////////////////////////////////////////// /** * 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. */ public PipelineConfiguration makePipelineConfiguration() { ParseOptions parseOptions = getConfiguration().getParseOptions() .withSchemaValidationMode(validationMode) .withErrorReporter(errorReporter); PipelineConfiguration pipe = new PipelineConfiguration(getConfiguration(), parseOptions); pipe.setController(this); return pipe; } /** * Set a callback that will be used when reporting a dynamic error or warning * @param reporter the error reporter to be notified */ public void setErrorReporter(ErrorReporter reporter) { errorReporter = reporter; } public ErrorReporter getErrorReporter() { return errorReporter; } /** * 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() == HostLanguage.XSLT) { err.setHostLanguage("XSLT"); } else if (executable.getHostLanguage() == HostLanguage.XQUERY) { err.setHostLanguage("XQuery"); } } getErrorReporter().report(new XmlProcessingException(err)); err.setHasBeenReported(true); } } /** * Report a run-time warning * * @param message the warning message * @param errorCode the local part of the error code (in the ERR namespace). May be null. * @param locator the location in the source code. May be null. */ public void warning(String message, String errorCode, Location locator) { if (locator == null) { locator = Loc.NONE; } if (errorCode == null) { errorCode = SaxonErrorCode.SXWN9000; } if (message == null) { message = "Unspecified warning"; } XmlProcessingIncident warning = new XmlProcessingIncident(message, errorCode, locator).asWarning(); errorReporter.report(warning); } @CSharpReplaceBody(code="reportFatalError(err); throw err;") protected void handleXPathException(XPathException err) throws XPathException { Throwable cause = err.getException(); if (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; } ///////////////////////////////////////////////////////////////////////////////////////// // 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 synchronized 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, XSLT and XPath. * * @param contextItem the context item for evaluating global variables, or null if there is none * @throws XPathException if the supplied context item is a node, and if it (a) does not belong * to the right Configuration, or (b) is schema-validated, when the stylesheet or query is * not compiled with schema-awareness enabled * @since 9.7. Changed in 9.9 to raise an exception if the context item is inappropriate. */ public void setGlobalContextItem(Item contextItem) throws XPathException { setGlobalContextItem(contextItem, false); } /** * Set the item used as the context for evaluating global variables. This value is used * as the global context item by XQuery, XSLT, and XPath. * * @param contextItem the context item for evaluating global variables, or null if there is none * @param alreadyStripped true if any stripping of type annotations or whitespace text node specified * in the stylesheet has already been carried out * @throws XPathException if the supplied context item is a node, and if it (a) does not belong * to the right Configuration, or (b) is schema-validated, when the stylesheet or query is * not compiled with schema-awareness enabled * @since 9.7. Changed in 9.9 to raise an exception if the context item is inappropriate. */ public void setGlobalContextItem(Item contextItem, boolean alreadyStripped) throws XPathException { if (!alreadyStripped) { // Bug 2929 - don't do space-stripping twice if (globalContextItem instanceof SpaceStrippedNode && ((SpaceStrippedNode) globalContextItem).getUnderlyingNode() == contextItem) { return; } if (contextItem instanceof NodeInfo) { // In XSLT, apply strip-space and strip-type-annotations options NodeInfo node = (NodeInfo) contextItem; contextItem = prepareInputTree(node.asActiveSource()); if (node.getNodeKind() == Type.DOCUMENT && node.getSystemId() != null) { DocumentKey key = new DocumentKey(node.getSystemId()); if (getDocumentPool().find(key) == null) { getDocumentPool().add(node.getTreeInfo(), key); } } } } if (contextItem instanceof NodeInfo) { NodeInfo startNode = (NodeInfo) contextItem; 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"); } } this.globalContextItem = contextItem; this.globalContextItemPreset = true; } /** * Reset the global context item to null. This clears any previous setting of the global context * item. */ public void clearGlobalContextItem() { this.globalContextItem = null; this.globalContextItemPreset = false; } /** * 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 W3C 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 ResourceResolver interface, or * null. */ public void setResourceResolver(ResourceResolver resolver) { resourceResolver = resolver; } /** * 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 ResourceResolver getResourceResolver() { return resourceResolver; } /** * 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; } /** * 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; } /** * 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; } /** * Ask whether the executable is a stylesheet whose top-level package * contains an xsl:strip-space declaration requesting stripping of whitespace * from the principal source document to the transformation * * @return true if whitespace stripping has been requested */ protected boolean isStylesheetContainingStripSpace() { SpaceStrippingRule rule; return executable instanceof PreparedStylesheet && (rule = ((PreparedStylesheet) executable).getTopLevelPackage().getSpaceStrippingRule()) != null && rule != NoElementsSpaceStrippingRule.getInstance(); } /** * Ask whether the executable is a stylesheet whose top-level package * contains requests stripping of type annotations * * @return true if stripping of type annotations has been requested */ public boolean isStylesheetStrippingTypeAnnotations() { return executable instanceof PreparedStylesheet && ((PreparedStylesheet) executable).getTopLevelPackage().isStripsTypeAnnotations(); } /** * 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 elements * in the stylesheet. Alternatively, stripping of all whitespace text nodes * may be defined at the level of the Configuration, using the code * {@code Configuration.getParseOptions().setSpaceStrippingRules(AllElementsSpaceStrippingRule.getInstance()}. * * @param next the Receiver to which the events filtered by this stripper are * to be sent (often a Builder). May be null if the stripper is not being used for filtering * into a Builder or other Receiver. * @return the required Stripper. A Stripper may be used in two ways. It acts as * a filter applied to an event stream, that can be used to remove the events * representing whitespace text nodes before they reach a Builder. Alternatively, * it can be used to define a view of an existing tree in which the whitespace * text nodes are dynamically skipped while navigating the XPath axes. * @since 8.4 - Generalized in 8.5 to accept any Receiver as an argument */ public Stripper makeStripper(/*@Nullable*/ Receiver next) { if (next == null) { next = new Sink(makePipelineConfiguration()); } return new Stripper(getSpaceStrippingRule(), next); } /** * Return the default whitespace-stripping rules that apply to this transformation or query. * * @return If the configuration-level whitespace-stripping rule is to strip whitespace for * all elements, then AllElementsSpaceStrippingRule.getInstance(). Otherwise, */ public SpaceStrippingRule getSpaceStrippingRule() { if (config.getParseOptions().getSpaceStrippingRule() == AllElementsSpaceStrippingRule.getInstance()) { return AllElementsSpaceStrippingRule.getInstance(); } else if (executable instanceof PreparedStylesheet) { SpaceStrippingRule rule = ((PreparedStylesheet) executable).getTopLevelPackage().getSpaceStrippingRule(); if (rule != null) { return rule; } } return NoElementsSpaceStrippingRule.getInstance(); } /** * Add a document to the document pool, and check that it is suitable for use in this query or * transformation. This check rejects the document if document has been validated (and thus carries * type annotations) but the query or transformation is not schema-aware. *

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, DocumentKey uri) throws XPathException { if (!getExecutable().isSchemaAware() && !Untyped.getInstance().equals(doc.getRootNode().getSchemaType())) { boolean isXSLT = getExecutable().getHostLanguage() == HostLanguage.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; } /** * 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. Changed in 9.9.1.1 so this no longer includes * static parameters (which are already available in the {@link PreparedStylesheet}). * @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 binderies = new HashMap<>(); // if parameters were supplied, set them up try { executable.checkSuppliedParameters(params); } catch (XPathException e) { if (!e.hasBeenReported()) { getErrorReporter().report(new XmlProcessingException(e)); throw e; } } 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 synchronized 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 synchronized 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); } } /** * Get the table of local indexes supporting xsl:key (or implicit keys created * by the optimizer). Indexes are held at Controller level (rather than being * shared across transformations) if the key definition is dependent on local * information, for example stylesheet parameters. * @return the index of indexes. The master index is created if it does not * already exist. The master index is a two-level index: the first level is indexed * by the integer fingerprint of the key name; the second level is indexed by * the document number (a long) for the specific document or temporary tree. */ public synchronized IntHashMap> getLocalIndexes() { if (localIndexes == null) { localIndexes = new IntHashMap<>(); } return localIndexes; } /** * 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 synchronized 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 synchronized int getRememberedNumber(NodeInfo node) { if (lastRememberedNode == node) { return lastRememberedNumber; } return -1; } protected 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 } } /** * Make a source tree from a source supplied as a StreamSource or SAXSource * * @param source the source * @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, int validationMode) throws XPathException { if (source instanceof NodeSource) { return ((NodeSource) source).getNode(); } else if (source instanceof NodeInfo) { return ((NodeInfo)source); } else if (source instanceof SAXSource && config.getBooleanProperty(Feature.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); } else if (source instanceof StreamSource && source.getSystemId() != null && ((StreamSource) source).getInputStream() == null && ((StreamSource) source).getReader() == null) { // Check to see if the document is already in the document pool. This can happen when a Transformer // is reused to perform multiple transformations on the same source document. Bug 4837. DocumentKey key = new DocumentKey(source.getSystemId()); TreeInfo existing = sourceDocumentPool.find(key); if (existing != null) { return existing.getRootNode(); } } Builder sourceBuilder = makeBuilder(); sourceBuilder.setUseEventLocation(true); if (sourceBuilder instanceof TinyBuilder) { ((TinyBuilder) sourceBuilder).setStatistics(config.getTreeStatistics().SOURCE_DOCUMENT_STATISTICS); } Receiver r = sourceBuilder; SpaceStrippingRule spaceStrippingRule = NoElementsSpaceStrippingRule.getInstance(); if (config.isStripsAllWhiteSpace() || isStylesheetContainingStripSpace() || validationMode == Validation.STRICT || validationMode == Validation.LAX) { r = makeStripper(sourceBuilder); spaceStrippingRule = getSpaceStrippingRule(); } if (isStylesheetStrippingTypeAnnotations()) { r = config.getAnnotationStripper(r); } PipelineConfiguration pipe = sourceBuilder.getPipelineConfiguration(); pipe.setParseOptions(pipe.getParseOptions().withSchemaValidationMode(validationMode)); r.setPipelineConfiguration(pipe); Sender.send(source, r, null); if (source instanceof AugmentedSource && ((AugmentedSource)source).isPleaseCloseAfterUse()) { ((AugmentedSource) source).close(); } NodeInfo doc = sourceBuilder.getCurrentRoot(); //globalContextItem = doc; sourceBuilder.reset(); if (source.getSystemId() != null) { registerDocument(doc.getTreeInfo(), new DocumentKey(source.getSystemId())); } doc.getTreeInfo().setSpaceStrippingRule(spaceStrippingRule); return doc; } /** * 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 NodeSource * @return the NodeInfo representing the input node, suitably wrapped. Exceptionally, * if the source is a whitespace text node that is itself stripped, return null. */ public NodeInfo prepareInputTree(Source source) { NodeInfo start = getConfiguration().unravel(source); // Stripping type annotations happens before stripping of whitespace if (isStylesheetStrippingTypeAnnotations()) { TreeInfo docInfo = start.getTreeInfo(); if (docInfo.isTyped()) { TypeStrippedDocument strippedDoc = new TypeStrippedDocument(docInfo); start = strippedDoc.wrap(start); } } if (stripSourceTrees && isStylesheetContainingStripSpace()) { TreeInfo docInfo = start.getTreeInfo(); SpaceStrippingRule spaceStrippingRule = getSpaceStrippingRule(); if (docInfo.getSpaceStrippingRule() != spaceStrippingRule) { // if not already space-stripped SpaceStrippedDocument strippedDoc = new SpaceStrippedDocument(docInfo, spaceStrippingRule); // Edge case: the global context item might itself be a whitespace text node that is stripped if (!SpaceStrippedNode.isPreservedNode(start, strippedDoc, start.getParent())) { return null; } start = strippedDoc.wrap(start); } } return start; } /** * 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 if pre-evaluation is attempted and fails with a dynamic error */ 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 getBindery(var.getPackageData()).setGlobalVariable(var, new Bindery.FailureValue(err)); } } } } } /** * 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. */ @SuppressWarnings("Java8MapApi") 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. The returned dateTime * value will have a timezone, which will be the default/local timezone * determined by the platform on which the application is running. */ /*@Nullable*/ public DateTimeValue getCurrentDateTime() { if (currentDateTime == null) { currentDateTime = DateTimeValue.now(); } return currentDateTime; } /** * Get the implicit timezone offset for this query or transformation * * @return the implicit timezone as an offset in minutes. This will be the default/local timezone * determined by the platform on which the application is running. The value will be unchanged for * repeated calls within the same transformation. */ 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; } /** * 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 cache of stylesheets (cached during calls on fn:transform()) for this query or transformation. * * @return the stylesheet cache */ @CSharpModifiers(code={"internal"}) public synchronized StylesheetCache getStylesheetCache() { if (stylesheetCache == null) { this.stylesheetCache = new StylesheetCache(); } return stylesheetCache; } /** * Get the factory function that is used to create new instances of FocusTrackingIterator. * The standard function for instantiating a FocusTrackingIterator can be overridden to deliver * one with extra diagnostic capability for use in debuggers * @param multithreaded true if the focus tracker must be suitable for executing a multi-threaded * xsl:for-each iteration * @return a factory function that is used to create FocusTrackingIterator instances */ public Function getFocusTrackerFactory(boolean multithreaded) { return multithreaded && multiThreadedFocusTrackerFactory != null ? multiThreadedFocusTrackerFactory : focusTrackerFactory; } /** * Set a factory function that will be used to create new instances of FocusTrackingIterator. * The standard function for instantiating a FocusTrackingIterator can be overridden to deliver * one with extra diagnostic capability for use in debuggers. * * @param focusTrackerFactory a factory function that is used to create FocusTrackingIterator instances */ public void setFocusTrackerFactory(Function focusTrackerFactory) { this.focusTrackerFactory = focusTrackerFactory; } /** * Set a factory function that will be used to create new instances of FocusTrackingIterator for * multithreaded operation. * The standard function for instantiating a FocusTrackingIterator can be overridden to deliver * one with extra diagnostic capability for use in debuggers. * * @param focusTrackerFactory a factory function that is used to create FocusTrackingIterator instances */ public void setMultithreadedFocusTrackerFactory(Function focusTrackerFactory) { this.multiThreadedFocusTrackerFactory = focusTrackerFactory; } /** * Set the focus tracker factory function to a function that creates a memoizing focus tracker, which * has the effect that all items read by the focus iterator are accessible to a debugger at any stage * while iterating over the sequence */ public void setMemoizingFocusTrackerFactory() { setFocusTrackerFactory(base -> { FocusTrackingIterator fti; if (!(base instanceof GroundedIterator && ((GroundedIterator)base).isActuallyGrounded()) && !(base instanceof GroupIterator) && !(base instanceof RegexIterator)) { try { MemoSequence ms = new MemoSequence(base); fti = FocusTrackingIterator.track(ms.iterate()); } catch (UncheckedXPathException e) { fti = FocusTrackingIterator.track(base); } } else { fti = FocusTrackingIterator.track(base); } return fti; }); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy