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

net.sf.saxon.trans.XsltController 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.trans;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.PreparedStylesheet;
import net.sf.saxon.event.*;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.accum.AccumulatorManager;
import net.sf.saxon.expr.instruct.*;
import net.sf.saxon.expr.parser.Loc;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.*;
import net.sf.saxon.s9api.Destination;
import net.sf.saxon.s9api.Message;
import net.sf.saxon.serialize.MessageEmitter;
import net.sf.saxon.serialize.PrincipalOutputGatekeeper;
import net.sf.saxon.style.StylesheetPackage;
import net.sf.saxon.trace.TemplateRuleTraceListener;
import net.sf.saxon.transpile.CSharpInnerClass;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.tree.iter.ManualIterator;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.tree.wrapper.SpaceStrippedDocument;
import net.sf.saxon.tree.wrapper.SpaceStrippedNode;
import net.sf.saxon.tree.wrapper.TypeStrippedDocument;

import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Stack;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * This class is an extension of the Controller class, used when executing XSLT stylesheets.
 *
 * @since 9.9
 */

public class XsltController extends Controller {

    private final Map messageCounters = new HashMap<>();
    private boolean assertionsEnabled = true;
    private ResultDocumentResolver resultDocumentResolver;
    private HashSet allOutputDestinations;
    private Component.M initialMode = null;
    private Map initialTemplateParams;
    private Map initialTemplateTunnelParams;
    private final Map> attributeSetEvaluationStacks = new HashMap<>();
    private AccumulatorManager accumulatorManager = new AccumulatorManager();
    private PrincipalOutputGatekeeper gatekeeper = null;
    private Destination principalDestination;
    private TemplateRuleTraceListener templateRuleTraceListener = null;
    private Consumer messageHandler;

    public XsltController(Configuration config, PreparedStylesheet pss) {
        super(config, pss);
        initMessageHandler(config);
    }

    @CSharpReplaceBody(code="Saxon.Callbacks.StandardMessageHandler smh = new (config); setMessageHandler(message => smh.accept(message));")
    private void initMessageHandler(Configuration config) {
        messageHandler = new StandardMessageHandler(config);
    }

    /**
     * 

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 */ @Override public void reset() { super.reset(); Configuration config = getConfiguration(); validationMode = config.getSchemaValidationMode(); accumulatorManager = new AccumulatorManager(); traceListener = null; 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; initialMode = null; clearPerTransformationData(); } /** * Reset variables that need to be reset for each transformation if the controller * is serially reused */ @Override protected synchronized void clearPerTransformationData() { super.clearPerTransformationData(); principalResult = null; allOutputDestinations = null; if (messageCounters != null) { messageCounters.clear(); } } /** * 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)) { Mode initial = ((PreparedStylesheet) executable).getRuleManager().obtainMode(Mode.UNNAMED_MODE_NAME, true); initialMode = initial.getDeclaringComponent(); } else { StylesheetPackage topLevelPackage = (StylesheetPackage) executable.getTopLevelPackage(); if (expandedModeName.equals(Mode.DEFAULT_MODE_NAME)) { StructuredQName defaultModeName = topLevelPackage.getDefaultMode(); if (!expandedModeName.equals(defaultModeName)) { setInitialMode(defaultModeName); } } else { boolean declaredModes = topLevelPackage.isDeclaredModes(); SymbolicName sn = new SymbolicName(StandardNames.XSL_MODE, expandedModeName); Component.M c = (Component.M) topLevelPackage.getComponent(sn); if (c == null) { throw new XPathException("Requested initial mode " + expandedModeName + " is not defined in the stylesheet", "XTDE0045"); } if (!((PreparedStylesheet) executable).isEligibleInitialMode(c)) { throw new XPathException("Requested initial mode " + expandedModeName + " is private in the top-level package", "XTDE0045"); } initialMode = c; if (!declaredModes && initialMode.getActor().isEmpty() && !expandedModeName.equals(topLevelPackage.getDefaultMode())) { throw new XPathException("Requested initial mode " + expandedModeName + " contains no template rules", "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.getActor().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) { StylesheetPackage top = (StylesheetPackage) executable.getTopLevelPackage(); StructuredQName defaultMode = top.getDefaultMode(); if (defaultMode == null) { defaultMode = Mode.UNNAMED_MODE_NAME; } Component.M c = (Component.M) top.getComponent(new SymbolicName(StandardNames.XSL_MODE, defaultMode)); initialMode = c; return c.getActor(); } else { return initialMode.getActor(); } } /** * 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 AccumulatorManager getAccumulatorManager() { return accumulatorManager; } /** * Check that an output destination has not been used before, optionally adding * this URI to the set of URIs that have been used. *

This method is intended for internal use only.

* * @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. */ public synchronized boolean checkUniqueOutputDestination(/*@Nullable*/ DocumentKey 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 synchronized void addUnavailableOutputDestination(DocumentKey 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 synchronized void removeUnavailableOutputDestination(DocumentKey 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 synchronized boolean isUnusedOutputDestination(DocumentKey uri) { return allOutputDestinations == null || !allOutputDestinations.contains(uri); } /** * 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(Map, boolean)}. * May be null. * @since 9.6 */ public Map getInitialTemplateParameters(boolean tunnel) { return tunnel ? initialTemplateTunnelParams : initialTemplateParams; } /** * Provide a user-supplied handler for {@code xsl:message} and {@code xsl:assert} * output. *

Note that the same message handler may be called to process messages * emanating from multiple threads. The message handler must therefore * be thread-safe; and the order in which messages are passed to the message * handler is not always predictable.

* @param handler a consumer of {@link Message} objects. * @since 11. Replaces a variety of previous interfaces including * {@code setMessageEmitter} and {@code setMessageFactory}. */ public void setMessageHandler(Consumer handler) { messageHandler = handler; } /** * Get the user-supplied or system-supplied handler for {@code xsl:message} and {@code xsl:assert} * output. * @return a consumer of {@link Message} objects. * @since 11. */ public Consumer getMessageHandler() { return messageHandler; } /** * Supply a factory function that is called every time xsl:message is executed. * @param messageReceiverFactory a factory function whose job it is to create a {@link Outputter} for * xsl:message output. * @deprecated since Saxon 11. The method has no effect. Use {@link #setMessageHandler} */ @Deprecated public void setMessageFactory(Supplier messageReceiverFactory) { } /** * Set the message receiver class name. This is an alternative way (retained for compatibility) * of providing a factory for message receivers; it causes a message factory to be established * that instantiates the supplied class * * @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 Receiver} interface, and that has a zero-argument public * constructor. * @deprecated since Saxon 11. The method has no effect. Use {@link #setMessageHandler} */ @Deprecated public void setMessageReceiverClassName(String name) { // No actioin } /** * Make a Receiver to be used for xsl:message output. *

This method is intended for internal use only. From 9.9.0.2 (bug 3979) this method * is called to obtain a new Receiver each time an xsl:message instruction is evaluated.

* * @return The newly constructed message Receiver * @deprecated since Saxon 11. The method returns null. Use {@link #setMessageHandler} */ /*@NotNull*/ @Deprecated public Receiver makeMessageReceiver() { return null; } /** * 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 MessageEmitter#setWriter} method.

*

* Although any Receiver can be supplied as the destination for messages, * applications may find it convenient to implement a subclass of {@link 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 ReceiverOption#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. Changed * in 9.9.0.2 so it is no longer supported in a configuration that allows multi-threading. * @deprecated since Saxon 11. The method has no effect. Use {@link #setMessageHandler} */ @Deprecated public void setMessageEmitter(Receiver receiver) { // 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 * @deprecated since 9.9.0.2; always returns null. */ /*@Nullable*/ @Deprecated public Receiver getMessageEmitter() { return null; } /** * Increment a counter in the message counters. This method is called automatically * when xsl:message is executed, to increment the counter associated with a given * error code used in the xsl:message. The counters are available (in Saxon-PE and -EE) * using the saxon:message-counter() extension function. * * @param code the error code whose counter is to be incremented */ public void incrementMessageCounter(StructuredQName code) { synchronized(messageCounters) { int n = messageCounters.getOrDefault(code, 0); messageCounters.put(code, n+1); } } /** * Get the message counters * * @return a snapshot copy of the table of message counters, indicating the number of times xsl:message * has been called for each distinct error code */ public Map getMessageCounters() { synchronized(messageCounters) { return new HashMap<>(messageCounters); } } /** * 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. Retained for backwards compatibility in 9.9; superseded by * {@link #getResultDocumentResolver()} */ /*@Nullable*/ public OutputURIResolver getOutputURIResolver() { if (resultDocumentResolver instanceof OutputURIResolverWrapper) { return ((OutputURIResolverWrapper) resultDocumentResolver).getOutputURIResolver(); } else { return getConfiguration().getOutputURIResolver(); } } /** * Set the URI resolver for secondary output documents. *

XSLT 2.0 introduces the xsl:result-document instruction, * allowing a transformation to have multiple result documents. JAXP does * not yet support this capability. This method allows an OutputURIResolver * to be specified that takes responsibility for deciding the destination * (and, if it wishes, the serialization properties) of secondary output files.

*

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. Retained for backwards compatibility in 9.9, but superseded * by {@link #setResultDocumentResolver(ResultDocumentResolver)} */ public void setOutputURIResolver(/*@Nullable*/ OutputURIResolver resolver) { OutputURIResolver our = resolver == null ? getConfiguration().getOutputURIResolver() : resolver; setResultDocumentResolver(new OutputURIResolverWrapper(our)); } /** * Get the resolver for URIs referring to secondary output documents. *

XSLT 2.0 introduced the xsl:result-document instruction, * allowing a transformation to have multiple result documents. JAXP does * not support this capability.

* * @return An object that implements the ResultDocumentResolver interface, or null. * The value is null (indicating that a system-supplied resolver will be used) * unless an explicit value is set. * @since 9.9. */ public ResultDocumentResolver getResultDocumentResolver() { return resultDocumentResolver; } /** * Set the resolver for URIs referring to secondary output documents. *

XSLT 2.0 introduced the xsl:result-document instruction, * allowing a transformation to have multiple result documents. JAXP does * not support this capability. This method allows an {@code ResultDocumentResolver} * to be specified that takes responsibility for deciding the destination * (and, if it wishes, the serialization properties) of secondary output files.

* * @param resultDocumentResolver An object that implements the ResultDocumentResolver * interface, or null. * @since 9.9. */ public void setResultDocumentResolver(ResultDocumentResolver resultDocumentResolver) { this.resultDocumentResolver = resultDocumentResolver; } /** * Supply the Controller with information about the principal destination of the transformation * * @param destination the principal destination */ public void setPrincipalDestination(Destination destination) { principalDestination = destination; } /** * Get the principal destination that was supplied to the Controller * @return the principal destination */ public Destination getPrincipalDestination() { return principalDestination; } public TemplateRuleTraceListener getTemplateRuleTraceListener() { return templateRuleTraceListener; } public void setTemplateRuleTraceListener(TemplateRuleTraceListener templateRuleTraceListener) { this.templateRuleTraceListener = templateRuleTraceListener; } /** * 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; } @Override public void preEvaluateGlobals(XPathContext context) throws XPathException { //super.preEvaluateGlobals(context); } /** * 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 out The destination for the result sequence. The events that are written to this * {@code Receiver} will form a regular event sequence * as defined in {@link RegularSequenceChecker}. This event sequence represents * the raw results of the transformation; subsequent processing * such as sequence normalization (to construct a document node) is the * responsibility of the Receiver itself. * @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 out) throws XPathException { checkReadiness(); try { ComplexContentOutputter dest = prepareOutputReceiver(out); XPathContextMajor initialContext = newXPathContext(); initialContext.createThreadManager(); initialContext.setOrigin(this); Mode mode = getInitialMode(); if (mode == null) { throw new XPathException("Requested initial mode " + (initialMode == null ? "#unnamed" : initialMode.getActor().getModeName().getDisplayName()) + " does not exist", "XTDE0045"); } if (!((PreparedStylesheet) executable).isEligibleInitialMode(initialMode)) { throw new XPathException("Requested initial mode " + (mode.getModeName().getDisplayName()) + " is not public or final", "XTDE0045"); } warningIfStreamable(mode); // 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); } SequenceIterator iter = source.iterate(); MappingFunction preprocessor = getInputPreprocessor(mode); iter = new MappingIterator(iter, preprocessor); initialContext.trackFocus(iter); initialContext.setCurrentMode(initialMode); initialContext.setCurrentComponent(initialMode); TailCall tc = mode.applyTemplates(ordinaryParams, tunnelParams, null, dest, initialContext, Loc.NONE); while (tc != null) { tc = tc.processLeavingTail(); } initialContext.waitForChildThreads(); dest.close(); } catch (TerminationException err) { //System.err.println("Processing terminated using xsl:message"); if (!err.hasBeenReported()) { reportFatalError(err); } throw err; } catch (UncheckedXPathException err) { handleXPathException(err.getXPathException()); } catch (XPathException err) { handleXPathException(err); } finally { inUse = false; if (traceListener != null) { traceListener.close(); } principalResultURI = null; } } /** * Takes a Receiver that expects raw output (such as might be obtained from {@link Destination#getReceiver}) * and prepends a pipeline of receivers needed to support transformation tasks, especially a * {@link ComplexContentOutputter}. * @param out a receiver expecting raw output * @return a receiver to which instructions in the stylesheet can write events. This receiver will have * been opened. * @throws XPathException if any failure occurs */ private ComplexContentOutputter prepareOutputReceiver(Receiver out) throws XPathException { principalResult = out; if (principalResultURI == null) { principalResultURI = out.getSystemId(); } if (getExecutable().createsSecondaryResult()) { // This is for the case where the stylesheet writes no output to the primary destination, // and then calls xsl:result-document with a null or empty href, in which case the xsl:result-document // output is sent to the primary output destination, but with different serialization properties. out = this.gatekeeper = new PrincipalOutputGatekeeper(this, out); } //out = new RegularSequenceChecker(out); // uncomment for debugging //out = new TracingFilter(out); // uncomment for debugging //NamespaceReducer nr = new NamespaceReducer(out); ComplexContentOutputter cco = new ComplexContentOutputter(out); cco.setSystemId(out.getSystemId()); cco.open(); return cco; } /** * Get the Gatekeeper object, which is used to ensure that we don't write a secondary * result document to the same destination as the principal result document * @return the gatekeeper object, or null if none is in use */ public PrincipalOutputGatekeeper getGatekeeper() { return gatekeeper; } private MappingFunction getInputPreprocessor(Mode finalMode) { return SequenceMapper.of(item -> { if (item instanceof NodeInfo) { NodeInfo node = (NodeInfo) item; if (node.getConfiguration() == null) { // must be a non-standard document implementation throw new XPathException("The supplied source document must be associated with a Configuration"); } if (!node.getConfiguration().isCompatible(executable.getConfiguration())) { throw new XPathException( "Source document and stylesheet must use the same or compatible Configurations", SaxonErrorCode.SXXP0004); } if (node.getTreeInfo().isTyped() && !executable.isSchemaAware()) { throw new XPathException("Cannot use a schema-validated source document unless the stylesheet is schema-aware"); } if (isStylesheetStrippingTypeAnnotations() && node != globalContextItem) { TreeInfo docInfo = node.getTreeInfo(); if (docInfo.isTyped()) { TypeStrippedDocument strippedDoc = new TypeStrippedDocument(docInfo); node = strippedDoc.wrap(node); } } SpaceStrippingRule spaceStrippingRule = getSpaceStrippingRule(); if (isStylesheetContainingStripSpace() && isStripSourceTree() && !(node instanceof SpaceStrippedNode) && node != globalContextItem && node.getTreeInfo().getSpaceStrippingRule() != spaceStrippingRule) { SpaceStrippedDocument strippedDoc = new SpaceStrippedDocument(node.getTreeInfo(), spaceStrippingRule); // Edge case: the item might itself be a whitespace text node that is stripped if (!SpaceStrippedNode.isPreservedNode(node, strippedDoc, node.getParent())) { return EmptyIterator.getInstance(); } node = strippedDoc.wrap(node); } if (getAccumulatorManager() != null) { getAccumulatorManager().setApplicableAccumulators(node.getTreeInfo(), finalMode.getAccumulators()); } return SingletonIterator.makeIterator(node); } else { return SingletonIterator.makeIterator(item); } }); } private void warningIfStreamable(Mode mode) { if (mode.isDeclaredStreamable()) { warning((initialMode == null ? "" : getInitialMode().getModeTitle(true)) + " is streamable, but the input is not supplied as a stream", SaxonErrorCode.SXWN9045, Loc.NONE); } } /** * Execute a transformation by evaluating a named template and delivering * the "raw result". * *

If any child threads are started (as a result of calls to xsl:result-document) * the method will wait for these to finish before returning to the caller.

* * @param initialTemplateName the entry point, the name of a named template * @param out The destination for the result sequence. The events that are written to this * {@code Receiver} will form a regular event sequence * as defined in {@link RegularSequenceChecker}. This event sequence represents * the raw results of the transformation; subsequent processing * such as sequence normalization (to construct a document node) is the * responsibility of the Receiver itself. * @throws XPathException if any dynamic error occurs (in this thread or in a child thread). * This includes the case where the named template does not exist. */ public void callTemplate(StructuredQName initialTemplateName, Receiver out) throws XPathException { checkReadiness(); try { ComplexContentOutputter dest = prepareOutputReceiver(out); XPathContextMajor initialContext = newXPathContext(); initialContext.createThreadManager(); initialContext.setOrigin(this); if (globalContextItem != null) { initialContext.setCurrentIterator(new ManualIterator(globalContextItem)); } // Process the source document by invoking the initial named template 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", "XTDE0040"); } if (!pack.isImplicitPackage() && !(initialComponent.getVisibility() == Visibility.PUBLIC || initialComponent.getVisibility() == Visibility.FINAL)) { throw new XPathException("Template " + initialTemplateName.getDisplayName() + " is " + Err.describeVisibility(initialComponent.getVisibility()), "XTDE0040"); } NamedTemplate t = (NamedTemplate) initialComponent.getActor(); XPathContextMajor c2 = initialContext.newContext(); initialContext.setOrigin(this); c2.setCurrentComponent(initialComponent); c2.openStackFrame(t.getStackFrameMap()); c2.setLocalParameters(ordinaryParams); c2.setTunnelParameters(tunnelParams); TailCall tc = t.expand(dest, c2); while (tc != null) { tc = tc.processLeavingTail(); } initialContext.waitForChildThreads(); dest.close(); } catch (UncheckedXPathException err) { handleXPathException(err.getXPathException()); } catch (XPathException err) { handleXPathException(err); } finally { if (traceListener != null) { traceListener.close(); } inUse = false; } } /** * Perform a transformation by applying templates in a streamable mode to a streamable * input document. * * @param source The input for the source tree. * @param out The destination for the result sequence. The events that are written to this * {@code Receiver} will form a regular event sequence * as defined in {@link RegularSequenceChecker}. This event sequence represents * the raw results of the transformation; subsequent processing * such as sequence normalization (to construct a document node) is the * responsibility of the Receiver itself. * @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 applyStreamingTemplates(Source source, Receiver out) throws XPathException { checkReadiness(); ComplexContentOutputter dest = prepareOutputReceiver(out); boolean close = false; try { int validationMode = getSchemaValidationMode(); Source underSource = source; if (source instanceof AugmentedSource) { close = ((AugmentedSource) source).isPleaseCloseAfterUse(); int localValidate = ((AugmentedSource) source).getSchemaValidation(); if (localValidate != Validation.DEFAULT) { validationMode = localValidate; } underSource = ((AugmentedSource) source).getContainedSource(); } Configuration config = getConfiguration(); Source s2 = config.getSourceResolver().resolveSource(underSource, config); if (s2 != null) { underSource = s2; } // if (!(source instanceof SAXSource || source instanceof StreamSource || source instanceof EventSource)) { // throw new IllegalArgumentException("Streaming requires a SAXSource, StreamSource, or EventSource"); // } if (!initialMode.getActor().isDeclaredStreamable()) { throw new IllegalArgumentException("Initial mode is not streamable"); } 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); } XPathContextMajor initialContext = newXPathContext(); initialContext.createThreadManager(); initialContext.setOrigin(this); // 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); } Receiver despatcher = config.makeStreamingTransformer(initialMode.getActor(), ordinaryParams, tunnelParams, dest, initialContext); if (config.isStripsAllWhiteSpace() || isStylesheetContainingStripSpace()) { despatcher = makeStripper(despatcher); } PipelineConfiguration pipe = despatcher.getPipelineConfiguration(); pipe.setParseOptions(pipe.getParseOptions().withSchemaValidationMode(this.validationMode)); boolean verbose = getConfiguration().isTiming(); if (verbose) { getConfiguration().getLogger().info("Streaming " + source.getSystemId()); } try { Sender.send(underSource, despatcher, null); } catch (QuitParsingException e) { if (verbose) { getConfiguration().getLogger().info("Streaming " + source.getSystemId() + " : early exit"); } } initialContext.waitForChildThreads(); dest.close(); } catch (TerminationException err) { //System.err.println("Processing terminated using xsl:message"); if (!err.hasBeenReported()) { reportFatalError(err); } throw err; } catch (UncheckedXPathException err) { handleXPathException(err.getXPathException()); } catch (XPathException err) { handleXPathException(err); } finally { inUse = false; if (close && source instanceof AugmentedSource) { ((AugmentedSource) source).close(); } if (traceListener != null) { traceListener.close(); } } } /** * 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.
* * @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*/ @CSharpInnerClass(outer=true, extra="[email protected] finalResult") public Receiver getStreamingReceiver(Mode mode, Receiver result) throws XPathException { // System.err.println("*** TransformDocument"); checkReadiness(); // 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 ComplexContentOutputter dest = prepareOutputReceiver(result); final XPathContextMajor initialContext = newXPathContext(); initialContext.setOrigin(this); //initialContext.setReceiver(dest); globalContextItem = null; // 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"); } Configuration config = getConfiguration(); Receiver despatcher = config.makeStreamingTransformer(mode, null, null, dest, initialContext); if (despatcher == null) { throw new XPathException("Streaming requires Saxon-EE"); } if (config.isStripsAllWhiteSpace() || isStylesheetContainingStripSpace()) { despatcher = makeStripper(despatcher); } despatcher.setPipelineConfiguration(makePipelineConfiguration()); final Outputter finalResult = dest; return new ProxyReceiver(despatcher) { @Override public void close() throws XPathException { if (traceListener != null) { traceListener.close(); } finalResult.close(); inUse = false; } }; } /** * Get the stack of attribute sets being evaluated (for detection of cycles) * * @return the attribute set evaluation stack */ public synchronized Stack getAttributeSetEvaluationStack() { long thread = Thread.currentThread().getId(); //noinspection Convert2Diamond return attributeSetEvaluationStacks.computeIfAbsent(thread, k -> new Stack()); } public synchronized void releaseAttributeSetEvaluationStack() { long thread = Thread.currentThread().getId(); attributeSetEvaluationStacks.remove(thread); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy