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

net.sf.saxon.s9api.XsltTransformer Maven / Gradle / Ivy

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

package net.sf.saxon.s9api;

import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.event.SequenceNormalizerWithSpaceSeparator;
import net.sf.saxon.expr.instruct.GlobalContextRequirement;
import net.sf.saxon.expr.instruct.GlobalParameterSet;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.serialize.SerializationProperties;
import net.sf.saxon.trans.UncheckedXPathException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.trans.XmlProcessingException;
import net.sf.saxon.trans.XsltController;
import net.sf.saxon.transpile.CSharpModifiers;
import net.sf.saxon.tree.linked.DocumentImpl;

import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import java.net.URI;

/**
 * An {@code XsltTransformer} represents a compiled and loaded stylesheet ready for execution.
 * The {@code XsltTransformer} holds details of the dynamic evaluation context for the stylesheet.
 * 

* An {@code XsltTransformer} must not be used concurrently in multiple threads. * It is safe, however, to reuse the object within a single thread to run the same * stylesheet several times. Running the stylesheet does not change the context * that has been established. Some of the public methods are synchronized: this is not because * multi-threaded execution is supported, rather it is to reduce the damage if multi-threaded * execution is attempted. *

* An {@code XsltTransformer} is always constructed by running the {@code Load} * method of an {@link XsltExecutable}. *

* An {@code XsltTransformer} is itself a {@link Destination}. This means it is possible to use * one {@code XsltTransformer} as the destination to receive the results of another transformation, * this providing a simple way for transformations to be chained into a pipeline. Note however that a * when the input to a transformation is supplied in this way, it will always be built as a tree in * memory, rather than the transformation being streamed. As a {@code Destination}, the transformer * performs Sequence Normalization on its input; that is, it converts the input to a single * document node. (The main reason for this is that when chaining XSLT transformations, the raw * output of the first stylesheet is often an element node, but the second stylesheet traditionally * expects a document node.) * * @since 9.0 */ @CSharpModifiers(code = {"internal"}) public class XsltTransformer extends AbstractXsltTransformer implements Destination { private QName initialTemplateName; private GlobalParameterSet parameters; private Source initialSource; private Destination destination; private final DestinationHelper destinationHelper; private URI destinationBaseUri; /** * Protected constructor * * @param processor the S9API processor * @param controller the Saxon controller object * @param staticParameters the static parameters supplied at stylesheet compile time */ protected XsltTransformer(Processor processor, XsltController controller, GlobalParameterSet staticParameters) { super(processor, controller); parameters = new GlobalParameterSet(/*staticParameters*/); destinationHelper = new DestinationHelper(this); } /** * Set the base URI of the resource being written to this destination * * @param baseURI the base URI to be used */ @Override public void setDestinationBaseURI(URI baseURI) { this.destinationBaseUri = baseURI; } /** * Get the base URI of the resource being written to this destination * * @return the baseURI, or null if none is known */ @Override public URI getDestinationBaseURI() { return destinationBaseUri; } @Override public void onClose(Action listener) { destinationHelper.onClose(listener); } @Override public void closeAndNotify() throws SaxonApiException { destinationHelper.closeAndNotify(); } /** * Set the initial named template for the transformation * * @param templateName the name of the initial template, or null to indicate * that there should be no initial named template. Changed * in 9.9: the method no longer checks that the named * template exists. */ public void setInitialTemplate(QName templateName) { initialTemplateName = templateName; } /** * Get the initial named template for the transformation, if one has been set * * @return the name of the initial template, or null if none has been set */ public QName getInitialTemplate() { return initialTemplateName; } /** * Set the source document for the transformation. *

If the source is an instance of {@link net.sf.saxon.om.NodeInfo}, the supplied node is used * directly as the initial context item of the transformation.

*

If the source is an instance of {@link javax.xml.transform.dom.DOMSource}, the DOM node identified * by the DOMSource is wrapped as a Saxon node, and this is then used as the context item.

*

In other cases a new Saxon tree will be built by the transformation engine when the * transformation starts, unless it is a Saxon-EE streaming transformation, in which case the * document is processed in streaming fashion as it is being parsed.

*

To run a transformation in streaming mode, the source should be supplied as an instance * of {@link javax.xml.transform.stream.StreamSource} or {@link javax.xml.transform.sax.SAXSource}. *

*

Some kinds of {@code Source} (for example {@code StreamSource} and * {@code SAXSource}are consumed by use; others (such as {@code DOMSource}) are immutable. * In the general case, therefore, the {@code Source} object that is supplied by this method * does not survive a call on {@link #transform()}.

* * @param source the principal source document for the transformation */ public synchronized void setSource(Source source) { if (source instanceof NodeInfo) { setInitialContextNode(new XdmNode((NodeInfo) source)); } else if (source instanceof DOMSource) { if (((DOMSource)source).getNode() == null) { DocumentImpl doc = new DocumentImpl(); doc.setConfiguration(controller.getConfiguration()); setInitialContextNode(new XdmNode(doc)); } else { NodeInfo n = processor.getUnderlyingConfiguration().unravel(source); setInitialContextNode(new XdmNode(n)); } } else { initialSource = source; } } /** * Set the initial context node for the transformation. *

In XSLT 3.0 terms, this sets the initial match selection (the sequence to which the * initial implicit call of xsl:applyTemplates is applied). It also determines how the * global context item for evaluating global variables is set: following the XSLT 1.0 and 2.0 rules * (XSLT 2.0 section 9.5): "For a global variable or the default value of a stylesheet parameter, * the expression or sequence constructor specifying the variable value is evaluated with a singleton * focus based on the root node of the tree containing the initial context node."

*

This value is ignored in the case where the {@link XsltTransformer} is used as the * {@link Destination} of another process. In that case the initial context node will always * be the document node of the document that is being streamed to this destination.

*

Calling this method has the side-effect of setting the initial source to null.

* * @param node the initial context node, or null if there is to be no initial context node * @throws SaxonApiUncheckedException if the node is unsuitable, for example if it was * built using the wrong Configuration */ public synchronized void setInitialContextNode(XdmNode node) throws SaxonApiUncheckedException { try { if (node == null) { initialSource = null; controller.setGlobalContextItem(null); } else { initialSource = node.getUnderlyingNode().asActiveSource(); controller.setGlobalContextItem(node.getUnderlyingNode().getRoot()); } } catch (XPathException e) { throw new SaxonApiUncheckedException(e); } } /** * Get the initial context node for the transformation, if one has been set * * @return the initial context node, or null if none has been set. This will not necessarily * be the same {@link XdmNode} instance as was supplied, but it will be an XdmNode object that represents * the same underlying node. */ public XdmNode getInitialContextNode() { if (initialSource instanceof NodeInfo) { return (XdmNode) XdmValue.wrap((NodeInfo) initialSource); } else { return null; } } /** * Set the value of a stylesheet parameter. * *

If the stylesheet does not have a parameter with this name, then the supplied value will * simply be ignored (no error occurs)

* *

If the stylesheet has a parameter with this name, and the supplied value does not match the * required type, then no error will be reported at this stage, but a dynamic error will occur * when the parameter value is first used. Supplied values are converted to the required type * using the function conversion rules.

* *

If the stylesheet has a parameter with this name, and the parameter is declared * with static="yes", or if a parameter with the same name was supplied to the * {@link XsltCompiler}, then no error will be reported at this stage, but an error * will be reported when the transformation is initiated. Static parameters must be initialized * using {@link XsltCompiler#setParameter(QName, XdmValue)}.

* * @param name the name of the stylesheet parameter, as a QName * @param value the value of the stylesheet parameter, or null to clear a previously set value * @throws SaxonApiUncheckedException if the value is lazily evaluated, and evaluation fails */ public synchronized void setParameter(QName name, XdmValue value) { try { parameters.put(name.getStructuredQName(), value == null ? null : ((Sequence) value.getUnderlyingValue()).materialize()); } catch (UncheckedXPathException e) { throw new SaxonApiUncheckedException(e); } } /** * Clear the values of all parameters that have been set */ public synchronized void clearParameters() { parameters = new GlobalParameterSet(); } /** * Get the value that has been set for a stylesheet parameter * * @param name the parameter whose name is required * @return the value that has been set for the parameter, or null if no value has been set */ public synchronized XdmValue getParameter(QName name) { Sequence oval = parameters.get(name.getStructuredQName()); return oval == null ? null : XdmValue.wrap(oval); } /** * Set the destination to be used for the result of the transformation. *

* This method can be used to chain transformations into a pipeline, by using one * {@link XsltTransformer} as the destination of another *

* The {@code Destination} object will generally be modified by a transformation * (that is, by a call on {@link #transform()}), and in general a {@code Destination} * cannot be used more than once. Therefore, if this {@code XsltTransformer} is used * for multiple transformations then a new {@code Destination} should be set for each one. * * @param destination the destination to be used for the result of this transformation */ public void setDestination(Destination destination) { this.destination = destination; } /** * Get the destination that was specified in a previous call of {@link #setDestination} * * @return the destination, or null if none has been supplied */ public Destination getDestination() { return destination; } /** * Perform the transformation. If this method is used, a destination must have been supplied * previously. * *

Calling this method will in general consume any {@code Source} and {@code Destination} * that have been supplied, so a new {@code Source} and {@code Destination} are needed for each * transformation. Other properties of this {@code XsltTransformer} (for example, the values * of parameters, the initial template, and initial mode) are left unchanged after the * transformation completes.

* *

If no source has been supplied (using {@link #setSource(Source)}), then the method * looks to see whether an initial named template has been supplied (using {@link #setInitialTemplate(QName)}, * and if so, the transformation starts with that named template. In the absence of an initial named template, * it looks to see if the stylesheet includes a template named {@code xsl:initial-template}, and if so, * uses that as the entry point. If there is no source and no initial template, the method fails.

* * @throws SaxonApiException if any dynamic error occurs during the transformation * @throws IllegalStateException if no destination has been supplied */ public synchronized void transform() throws SaxonApiException { Source initialSelection = initialSource; boolean reset = false; if (destination == null) { throw new IllegalStateException("No destination has been supplied"); } try { Receiver out = getDestinationReceiver(controller, destination); GlobalContextRequirement gcr = controller.getExecutable().getGlobalContextRequirement(); if ((gcr == null || !gcr.isAbsentFocus()) && initialSelection != null) { if (initialSelection instanceof NodeInfo) { reset = maybeSetGlobalContextItem((NodeInfo) initialSelection); } else if (initialSelection instanceof DOMSource) { NodeInfo node = controller.prepareInputTree(initialSelection); reset = maybeSetGlobalContextItem(node); initialSelection = node.asActiveSource(); } else { NodeInfo node = controller.makeSourceTree(initialSelection, getSchemaValidationMode().getNumber()); reset = maybeSetGlobalContextItem(node); initialSelection = node.asActiveSource(); } } if (baseOutputUriWasSet) { out.setSystemId(getBaseOutputURI()); } controller.initializeController(parameters); if (initialTemplateName != null) { controller.callTemplate(initialTemplateName.getStructuredQName(), out); } else if (initialSelection != null) { applyTemplatesToSource(initialSelection, out); } else { QName entryPoint = new QName("xsl", NamespaceConstant.XSLT, "initial-template"); controller.callTemplate(entryPoint.getStructuredQName(), out); } destination.closeAndNotify(); } catch (XPathException e) { if (!e.hasBeenReported()) { getErrorReporter().report(new XmlProcessingException(e)); e.setHasBeenReported(true); } throw new SaxonApiException(e); } finally { if (reset) { controller.clearGlobalContextItem(); } } } private boolean maybeSetGlobalContextItem(Item item) throws XPathException { if (controller.getGlobalContextItem() == null) { controller.setGlobalContextItem(item, true); return true; } else { return false; } } /** * Return a Receiver which can be used to supply the principal source document for the transformation. * This method is intended primarily for internal use, though it can also * be called by a user application that wishes to feed events into the transformation engine. *

* Saxon calls this method to obtain a Receiver, to which it then sends * a sequence of events representing the content of an XML document. This method is provided so that * {@code XsltTransformer} implements {@code Destination}, allowing one transformation * to receive the results of another in a pipeline. *

* Before calling this method, the {@link #setDestination} method must be called to supply a destination * for the transformation. *

* Note that when an {@code XsltTransformer} is used as a {@code Destination}, the initial * context node set on that {@code XsltTransformer} using {@link #setInitialContextNode(XdmNode)} is ignored, * as is the source set using {@link #setSource(javax.xml.transform.Source)}. * * @param pipe The Saxon configuration. This is supplied so that the destination can * use information from the configuration (for example, a reference to the name pool) * to construct or configure the returned Receiver. * @param params serialization parameters (not relevant here since we aren't serializing; except * possibly for the item-separator property) * @return the Receiver to which events are to be sent. * @throws SaxonApiException if the Receiver cannot be created * @throws IllegalStateException if no Destination has been supplied */ @Override public Receiver getReceiver(PipelineConfiguration pipe, SerializationProperties params) throws SaxonApiException { if (destination == null) { throw new IllegalStateException("No destination has been supplied"); } Receiver rt = getReceivingTransformer(controller, parameters, destination); rt = new SequenceNormalizerWithSpaceSeparator(rt); rt.setPipelineConfiguration(pipe); return rt; } /** * Close this destination, allowing resources to be released. Used when this XsltTransformer is acting * as the destination of another transformation. Saxon calls this method when it has finished writing * to the destination. */ @Override public void close() { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy