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

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

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

package net.sf.saxon.s9api;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.event.Builder;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.event.TreeReceiver;
import net.sf.saxon.expr.instruct.GlobalContextRequirement;
import net.sf.saxon.expr.instruct.GlobalParameterSet;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.serialize.ReconfigurableSerializer;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.linked.DocumentImpl;
import net.sf.saxon.tree.tiny.Statistics;
import net.sf.saxon.tree.tiny.TinyBuilder;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;

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

*

An 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.

*

*

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

*

*

An XsltTransformer is itself a Destination. This means it is possible to use * one 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.

* * @since 9.0 */ public class XsltTransformer implements Destination { // TODO: when input is piped into an XsltTransformer, do a streamed transformation where appropriate. private Processor processor; private Controller controller; private GlobalParameterSet parameters; /*@Nullable*/ private Source initialSource; private Destination destination; private Builder sourceTreeBuilder; boolean baseOutputUriWasSet = false; /** * 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, Controller controller, GlobalParameterSet staticParameters) { this.processor = processor; this.controller = controller; parameters = new GlobalParameterSet(staticParameters); } /** * 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 * @throws SaxonApiException if there is no named template with this name */ public void setInitialTemplate(QName templateName) throws SaxonApiException { try { controller.setInitialTemplate( templateName == null ? null : templateName.getStructuredQName()); } catch (XPathException e) { throw new SaxonApiException(e); } } /** * 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() { StructuredQName templateName = controller.getInitialTemplate(); return templateName == null ? null : new QName(templateName); } /** * Set the initial mode for the transformation * * @param modeName the name of the initial mode. 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 default mode (which defaults to the unnamed * mode, but can be set differently in an XSLT 3.0 stylesheet). * @throws IllegalArgumentException if the requested mode is not defined in the stylesheet * @since changed in 9.6 to throw an exception if the mode is not defined in the stylesheet. * Chaned in 9.7 so that null means the default mode, not necessarily the unnamed mode. */ public void setInitialMode(QName modeName) throws IllegalArgumentException { try { controller.setInitialMode(modeName == null ? null : modeName.getStructuredQName()); } catch (XPathException e) { throw new IllegalArgumentException(e); } } /** * Get the name of the initial mode for the transformation, if one has been set. * * @return the initial mode for the transformation. Returns null if no mode has been set, * or if the mode was set to null to represent the default (unnamed) mode */ public QName getInitialMode() { StructuredQName mode = controller.getInitialModeName(); if (mode == null) { return null; } else { return new QName(mode); } } /** * Set the schema validation mode for the transformation. This indicates how source documents * loaded specifically for this transformation will be handled. This applies to the * principal source document if supplied as a SAXSource or StreamSource, and to all * documents loaded during the transformation using the doc(), document(), * or collection() functions. * * @param mode the validation mode. Passing null causes no change to the existing value. * Passing {@link ValidationMode#DEFAULT} resets to the initial value, which determines * the validation requirements from the Saxon Configuration. */ public void setSchemaValidationMode(ValidationMode mode) { if (mode != null) { controller.setSchemaValidationMode(mode.getNumber()); } } /** * Get the schema validation mode for the transformation. This indicates how source documents * loaded specifically for this transformation will be handled. This applies to the * principal source document if supplied as a SAXSource or StreamSource, and to all * documents loaded during the transformation using the doc(), document(), * or collection() functions. * * @return the validation mode. */ public ValidationMode getSchemaValidationMode() { return ValidationMode.get(controller.getSchemaValidationMode()); } /** * 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}. *

* * @param source the principal source document for the transformation * @throws SaxonApiException if for example the Source is an unrecognized type of source */ public void setSource(Source source) throws SaxonApiException { 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 { setInitialContextNode(processor.newDocumentBuilder().wrap(source)); } } else { initialSource = source; } } /** * Set the initial context node for the transformation. *

This 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 */ public void setInitialContextNode(XdmNode node) { if (node == null) { initialSource = null; controller.setGlobalContextItem(null); } else { initialSource = node.getUnderlyingNode(); controller.setGlobalContextItem(node.getUnderlyingNode()); } } /** * 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 * * @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 */ public void setParameter(QName name, XdmValue value) { parameters.put(name.getStructuredQName(), value == null ? null : value.getUnderlyingValue()); } /** * Clear the values of all parameters that have been set */ public 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 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

*

*

If the destination is a {@link Serializer}, then this call has two side-effects:

*

*

    *
  • It sets the base output URI for the transformation. This acts as the base URI for resolving * the href attribute of any xsl:result-document instruction.
  • *
  • It modifies the supplied Serializer to make it aware of the serialization properties * defined in the default xsl:output declaration of the stylesheet. * The serialization parameters defined in the Serializer override any * serialization parameters defined using xsl:output for the principal * output of the transformation. However, they have no effect on any output produced * using xsl:result-document.
  • *
* * @param destination the destination to be used */ public void setDestination(Destination destination) { this.destination = destination; if (destination instanceof Serializer) { Properties declaredProperties = controller.getExecutable().getDefaultOutputProperties(); String nextInChain = declaredProperties.getProperty(SaxonOutputKeys.NEXT_IN_CHAIN); if (nextInChain != null && !nextInChain.isEmpty()) { try { String base = declaredProperties.getProperty(SaxonOutputKeys.NEXT_IN_CHAIN_BASE_URI); Source nextStylesheet = getURIResolver().resolve(nextInChain, base); XsltTransformer next = processor.newXsltCompiler().compile(nextStylesheet).load(); next.setDestination(destination); this.setDestination(next); } catch (Exception e) { final Exception err = e; this.setDestination(new Destination() { public Receiver getReceiver(Configuration config) throws SaxonApiException { throw new SaxonApiException("Failed to configure next-in-chain stylesheet", err); } public void close() throws SaxonApiException { } }); } } else { Serializer serializer = (Serializer) destination; serializer.setDefaultOutputProperties(declaredProperties); serializer.setCharacterMap(controller.getExecutable().getCharacterMapIndex()); } } } /** * 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; } /** * Set the base output URI. *

*

This defaults to the system ID of the Destination for the principal output * of the transformation if this is known; if it is not known, it defaults * to the current directory.

*

*

If no base output URI is supplied, but the Destination of the transformation * is a Serializer that writes to a file, then the URI of this file is used as * the base output URI.

*

*

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 9.2 */ public void setBaseOutputURI(String uri) { controller.setBaseOutputURI(uri); baseOutputUriWasSet = uri != null; } /** * 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 9.2 */ public String getBaseOutputURI() { return controller.getBaseOutputURI(); } /** * Set an object that will be used to resolve URIs used in * fn:doc() and related functions. * * @param resolver An object that implements the URIResolver interface, or * null. * @since 9.3 */ public void setURIResolver(URIResolver resolver) { controller.setURIResolver(resolver); } /** * Get the URI resolver. * * @return the user-supplied URI resolver if there is one, or null otherwise * @since 9.3 */ public URIResolver getURIResolver() { return controller.getURIResolver(); } /** * Set the ErrorListener to be used during this transformation * * @param listener The error listener to be used. This is notified of all dynamic errors detected during the * transformation. * @since 9.2 */ public void setErrorListener(ErrorListener listener) { controller.setErrorListener(listener); } /** * Get the ErrorListener being used during this compilation episode * * @return listener The error listener in use. This is notified of all dynamic errors detected during the * transformation. If no user-supplied ErrorListener has been set the method will return a system-supplied * ErrorListener. If an explicit ErrorListener has been set using {@link #setErrorListener(ErrorListener)}, * then that ErrorListener will generally be returned, unless the internal ErrorListener has been changed * by some other mechanism. * @since 9.2 */ public ErrorListener getErrorListener() { UnfailingErrorListener uel = controller.getErrorListener(); if (uel instanceof DelegatingErrorListener) { return ((DelegatingErrorListener) uel).getBaseErrorListener(); } else { return uel; } } /** * Set the MessageListener to be notified whenever the stylesheet evaluates an * xsl:message instruction. If no MessageListener is nominated, * the output of xsl:message instructions will be serialized and sent * to the standard error stream. * * @param listener the MessageListener to be used * @since 9.1 */ public void setMessageListener(MessageListener listener) { controller.setMessageEmitter(new MessageListenerProxy(listener, controller.makePipelineConfiguration())); } /** * Get the MessageListener to be notified whenever the stylesheet evaluates an * xsl:message instruction. If no MessageListener has been nominated, * return null * * @return the user-supplied MessageListener, or null if none has been supplied * @since 9.1 */ public MessageListener getMessageListener() { Receiver r = controller.getMessageEmitter(); if (r instanceof MessageListenerProxy) { return ((MessageListenerProxy) r).getMessageListener(); } else { return null; } } /** * Set a TraceListener to be notified of all events occurring during the transformation. * This will only be effective if the stylesheet was compiled with trace code enabled * (see {@link XsltCompiler#setCompileWithTracing(boolean)}) * * @param listener the TraceListener to be used. Note that the TraceListener has access to * interal Saxon interfaces which may vary from one release to the next. It is also possible that * the TraceListener interface itself may be changed in future releases. * @since 9.2 */ public void setTraceListener(TraceListener listener) { controller.setTraceListener(listener); } /** * Get the TraceListener to be notified of all events occurring during the transformation. * If no TraceListener has been nominated, return null * * @return the user-supplied TraceListener, or null if none has been supplied * @since 9.2 */ public TraceListener getTraceListener() { return controller.getTraceListener(); } /** * 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.6 */ public void setTraceFunctionDestination(Logger stream) { controller.setTraceFunctionDestination(stream); } /** * Get the destination for output from the fn:trace() function. * * @return the Logger 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.6 */ public Logger getTraceFunctionDestination() { return controller.getTraceFunctionDestination(); } /** * Perform the transformation. If this method is used, a destination must have been supplied * previously * * @throws SaxonApiException if any dynamic error occurs during the transformation * @throws IllegalStateException if no destination has been supplied */ public void transform() throws SaxonApiException { if (destination == null) { throw new IllegalStateException("No destination has been supplied"); } if (baseOutputUriWasSet && destination instanceof XdmDestination && ((XdmDestination) destination).getBaseURI() == null && controller.getBaseOutputURI() != null ) { try { ((XdmDestination) destination).setBaseURI(new URI(controller.getBaseOutputURI())); } catch (URISyntaxException e) { // no action } } try { Receiver out = getDestinationReceiver(); GlobalContextRequirement gcr = controller.getExecutable().getGlobalContextRequirement(); if ((gcr == null || gcr.mayBeSupplied) && initialSource != null) { if (initialSource instanceof NodeInfo) { controller.setGlobalContextItem((NodeInfo)initialSource); } else if (initialSource instanceof DOMSource) { NodeInfo node = controller.prepareInputTree(initialSource); controller.setGlobalContextItem(node); initialSource = node; } else { boolean close = (initialSource instanceof AugmentedSource && ((AugmentedSource)initialSource).isPleaseCloseAfterUse()); NodeInfo node = controller.makeSourceTree(initialSource, close, getSchemaValidationMode().getNumber()); controller.setGlobalContextItem(node); initialSource = node; } } controller.initializeController(parameters); controller.transform(initialSource, out); destination.close(); } catch (XPathException e) { if (!e.hasBeenReported()) { try { getErrorListener().error(e); } catch (TransformerException e1) { // no action } } throw new SaxonApiException(e); } } private Receiver getDestinationReceiver() throws SaxonApiException { if (destination instanceof Serializer) { Serializer serializer = (Serializer) destination; Object dest = serializer.getOutputDestination(); if (!baseOutputUriWasSet) { if (dest instanceof File) { controller.setBaseOutputURI(((File) dest).toURI().toString()); } } PipelineConfiguration pipe = controller.makePipelineConfiguration(); Receiver r = ((Serializer)destination).getReceiver(pipe); return new ReconfigurableSerializer(r, serializer.getLocalOutputProperties(), serializer.getResult()); } else { Receiver r = destination.getReceiver(controller.getConfiguration()); PipelineConfiguration pipe = r.getPipelineConfiguration(); pipe.setController(controller); return r; } } /** * 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 * XsltTransformer implements 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 XsltTransformer is used as a Destination, the initial * context node set on that XsltTransformer using {@link #setInitialContextNode(XdmNode)} is ignored, * as is the source set using {@link #setSource(javax.xml.transform.Source)}.

* * @param config 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. * @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 */ public Receiver getReceiver(Configuration config) throws SaxonApiException { if (destination == null) { throw new IllegalStateException("No destination has been supplied"); } try { controller.initializeController(parameters); } catch (XPathException e) { throw new SaxonApiException(e); } if (controller.getInitialMode().isDeclaredStreamable()) { try { return controller.getStreamingReceiver(controller.getInitialMode(), getDestinationReceiver()); } catch (TransformerException e) { throw new SaxonApiException(e); } } else { sourceTreeBuilder = controller.makeBuilder(); if (sourceTreeBuilder instanceof TinyBuilder) { ((TinyBuilder) sourceTreeBuilder).setStatistics(Statistics.SOURCE_DOCUMENT_STATISTICS); } Receiver stripper = controller.makeStripper(sourceTreeBuilder); if (controller.getExecutable().stripsInputTypeAnnotations()) { stripper = controller.getConfiguration().getAnnotationStripper(stripper); } return new TreeReceiver(stripper); } } /** * 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. */ public void close() throws SaxonApiException { if (sourceTreeBuilder != null) { NodeInfo doc = sourceTreeBuilder.getCurrentRoot(); sourceTreeBuilder = null; if (doc != null) { Receiver result = getDestinationReceiver(); try { controller.transformDocument(doc, result); } catch (TransformerException e) { throw new SaxonApiException(e); } } destination.close(); } } /** * Get the underlying Controller used to implement this XsltTransformer. This provides access * to lower-level methods not otherwise available in the s9api interface. Note that classes * and methods obtained by this route cannot be guaranteed stable from release to release. * @return the underlying Controller * @since 9.0.0.4 */ public Controller getUnderlyingController() { return controller; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy