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

net.sf.saxon.s9api.AbstractXsltTransformer 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.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.event.*;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.instruct.GlobalParameterSet;
import net.sf.saxon.functions.ResolveURI;
import net.sf.saxon.lib.*;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.serialize.SerializationProperties;
import net.sf.saxon.trans.SaxonErrorCode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.trans.XsltController;
import net.sf.saxon.transpile.CSharpInnerClass;
import net.sf.saxon.transpile.CSharpModifiers;
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.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * A class that exists to contain common code shared between XsltTransformer and Xslt30Transformer
 */

//@CSharpInjectMembers(code = {
//        "    public void setErrorReporter(System.Action reporter) {"
//                + "        setErrorReporter(new Saxon.Impl.Helpers.ErrorReportingAction(reporter));"
//                + "    }"
//})
@CSharpModifiers(code = {"abstract", "internal"})
public abstract class AbstractXsltTransformer {
    protected Processor processor;
    protected XsltController controller;
    protected boolean baseOutputUriWasSet = false;
    private MessageListener2 messageListener2;

    AbstractXsltTransformer(Processor processor, XsltController controller) {
        this.processor = processor;
        this.controller = controller;
    }

    /**
     * Set the base output URI.
     * 

This defaults to the base URI of the {@link Destination} for the principal output * of the transformation if a destination is supplied and its base URI is known.

*

If a base output URI is supplied using this method then it takes precedence * over any base URI defined in the supplied {@code Destination} object, and * it may cause the base URI of the {@code Destination} object to be modified in situ.

*

The base output URI is used for resolving relative URIs in the href attribute * of the xsl:result-document instruction; it is accessible to XSLT stylesheet * code using the XPath {@code current-output-uri()} function

* * @param uri the base output URI */ public synchronized 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 */ 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. * @deprecated since 11.1. Use {@link #setResourceResolver} in preference. */ public void setURIResolver(URIResolver resolver) { controller.setResourceResolver(new ResourceResolverWrappingURIResolver(resolver)); } /** * Set the ResourceResolver to be used during stylesheet execution. * The ResourceResolver is used for dereferencing * an absolute URI (after URI resolution) to return a {@link javax.xml.transform.Source} representing the * required resource. *

This ResourceResolver is used to dereference the URIs appearing in the doc(), * doc-available(), and document() functions: in these cases it may return any * supported Source object.

*

It is also used to dereference the URI supplied to the xsl:source-document * instruction. In this case the resource request passed to the resolver indicates whether the * instruction has the attribute streamable="yes"; if it does, the returned * Source object must be a StreamSource or SAXSource.

*

The resolver is also used for a number of other cases where URIs are dereferenced during * stylesheet execution, for example in the fn:transform function

* * @param resolver the ResourceResolver to be used during stylesheet execution. */ public void setResourceResolver(ResourceResolver resolver) { controller.setResourceResolver(resolver); } /** * Get the resource resolver. * * @return the user-supplied resource resolver if there is one, or null otherwise */ public ResourceResolver getResourceResolver() { return controller.getResourceResolver(); } /** * Get the URI resolver. * * @return the user-supplied URI resolver if there is one, or null otherwise */ public URIResolver getURIResolver() { ResourceResolver resolver = controller.getResourceResolver(); if (resolver instanceof ResourceResolverWrappingURIResolver) { return ((ResourceResolverWrappingURIResolver)resolver).getWrappedURIResolver(); } return null; } /** * Set an object that will be used to resolve URIs used in * fn:unparsed-text() and related functions. * * @param resolver An object that implements the UnparsedTextURIResolver interface, or * null. * @since 11 */ public void setUnparsedTextResolver(UnparsedTextURIResolver resolver) { controller.setUnparsedTextURIResolver(resolver); } /** * Get the URI resolver used for fn:unparsed-text() and related functions. * * @return the user-supplied URI resolver if there is one, or the * system-defined one otherwise * @since 11 */ public UnparsedTextURIResolver getUnparsedTextURIResolver() { return controller.getUnparsedTextURIResolver(); } /** * 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. */ public void setErrorListener(ErrorListener listener) { controller.setErrorReporter(new ErrorReporterToListener(listener)); } /** * Get the ErrorListener being used during this transformation * * @return the error listener in use. If no user-supplied {@code ErrorListener} has been set the method will return * a system-supplied ErrorListener that delegates to the {@link ErrorReporter}. * If an explicit {@code 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. */ public ErrorListener getErrorListener() { ErrorReporter uel = controller.getErrorReporter(); if (uel instanceof ErrorReporterToListener) { return ((ErrorReporterToListener) uel).getErrorListener(); } else { return null; } } /** * Set a callback that will be used when reporting a dynamic error or warning. This is relevant primarily * for non-fatal errors * @param reporter the callback */ public void setErrorReporter(ErrorReporter reporter) { controller.setErrorReporter(reporter); } /** * Get the callback that will be used when reporting a dynamic error or warning * @return the callback */ public ErrorReporter getErrorReporter() { return controller.getErrorReporter(); } /** * Set a callback function that will be used when {@code xsl:result-document} is evaluated. The argument * is a function that takes a URI as input (specifically, the value of the {@code href} argument * to {@code xsl:result-document}, resolved against the base output URI of the transformation), * and returns a {@link Destination}, which will be used as the destination for the result document. *

If the {@code href} argument of the {@code xsl:result-document} instruction is absent or if * it is set to a zero length string, then the callback function is not normally called; instead * a {@code Receiver} for the secondary output is obtained by making a second call on * {@link Destination#getReceiver(PipelineConfiguration, SerializationProperties)} * for the principal destination of the transformation. In that situation, this result document handler * is invoked only if the call on {@link Destination#getReceiver(PipelineConfiguration, SerializationProperties)} * returns null.

*

If the base output URI is absent (perhaps because the principal output destination for the * transformation was supplied as a {@link OutputStream} or {@link Writer} with no associated * URI or systemId), then the value of the {@code href} attribute is used as is if it * is an absolute URI; if it is a relative URI (including the case where it is absent or zero-length) * then the callback function is not called; instead a dynamic error is raised (code * {@link SaxonErrorCode#SXRD0002}).

*

If the callback function throws a {@link SaxonApiUncheckedException}, this will result * in the {@code xsl:result-document} instruction failing with a dynamic error, which can be caught * using {@code xsl:try/xsl:catch}. The error code, by default, will be "err:SXRD0001".

*

The application can request to be notified when the {@code Destination} is closed by setting * a {@link Destination#onClose(Action)} callback on the {@code Destination} object.

* * @param handler the callback function to be invoked whenever an {@code xsl:result-document} * instruction is evaluated. */ @CSharpInnerClass( outer=false, extra={"System.Func handler"}) public void setResultDocumentHandler(java.util.function.Function handler) { controller.setResultDocumentResolver(new ResultDocumentResolver() { @Override public Receiver resolve( XPathContext context, String href, String baseUri, SerializationProperties properties) throws XPathException { try { URI abs = ResolveURI.makeAbsolute(href, baseUri); Destination destination; try { destination = handler.apply(abs); } catch (SaxonApiUncheckedException e) { XPathException xe = XPathException.makeXPathException(e); xe.maybeSetErrorCode("SXRD0001"); throw xe; } try { PipelineConfiguration pipe = context.getController().makePipelineConfiguration(); return destination.getReceiver(pipe, properties); } catch (SaxonApiException e) { throw XPathException.makeXPathException(e); } } catch (URISyntaxException e) { throw XPathException.makeXPathException(e); } } }); } /** * Set the MessageListener to be notified of {@code xsl:message} and {@code xsl:assert} output. * @param listener the MessageListener to be notified */ public synchronized void setMessageListener(MessageListener2 listener) { messageListener2 = listener; setMessageHandler(message -> listener.message( message.getContent(), message.getErrorCode(), message.isTerminate(), message.getLocation()) ); } /** * Set a message handler to be notified of {@code xsl:message} and {@code xsl:assert} output. * @param messageHandler the message handler to be notified * @since 11 */ public void setMessageHandler(Consumer messageHandler) { controller.setMessageHandler(messageHandler); } /** * Get the MessageListener2 to be notified whenever the stylesheet evaluates an * xsl:message instruction. If no MessageListener2 has been nominated, * return null * * @return the user-supplied MessageListener2, or null if none has been supplied */ public MessageListener2 getMessageListener2() { return messageListener2; } /** * Say whether assertions (xsl:assert instructions) should be 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) { controller.setAssertionsEnabled(enabled); } /** * 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 controller.isAssertionsEnabled(); } /** * 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. */ 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 */ 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. */ 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. */ public Logger getTraceFunctionDestination() { return controller.getTraceFunctionDestination(); } protected void applyTemplatesToSource(Source source, Receiver out) throws XPathException { Objects.requireNonNull(source); Objects.requireNonNull(out); if (controller.getInitialMode().isDeclaredStreamable()) { controller.applyStreamingTemplates(source, out); } else { NodeInfo node; if (source instanceof NodeInfo) { node = (NodeInfo)source; } else { node = controller.makeSourceTree(source, controller.getSchemaValidationMode()); } controller.applyTemplates(node, out); } } protected boolean isStreamableSource(Source source) { if (source instanceof AugmentedSource) { return isStreamableSource (((AugmentedSource) source).getContainedSource()); } Configuration config = controller.getConfiguration(); try { source = config.getSourceResolver().resolveSource(source, config); } catch (XPathException e) { return false; } return source instanceof SAXSource || source instanceof StreamSource || source instanceof EventSource; } /** * 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 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 SaxonApiException if the requested mode is not defined in the stylesheet, or if * it is defined with visibility="private". * @since Changed in 9.6 to throw an exception if the mode is not defined in the stylesheet. * Changed in 9.7 so that null means the default mode, not necessarily the unnamed mode. * Changed in 11 to throw SaxonApiException rather than IllegalArgumentException, to * prevent the error code being lost. */ public void setInitialMode(QName modeName) throws SaxonApiException { try { controller.setInitialMode(modeName == null ? null : modeName.getStructuredQName()); } catch (XPathException e) { throw new SaxonApiException(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); } } /** * 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 {@link Controller} */ public XsltController getUnderlyingController() { return controller; } /** * Get a Receiver corresponding to the chosen Destination for the transformation * @param controller the Controller for the transformation * @param destination the destination for the results of this transformation * @return a receiver that sends the results to this destination * @throws SaxonApiException if anything goes wrong */ public Receiver getDestinationReceiver(XsltController controller, Destination destination) throws SaxonApiException { Receiver receiver; controller.setPrincipalDestination(destination); PipelineConfiguration pipe = controller.makePipelineConfiguration(); SerializationProperties params = controller.getExecutable().getPrimarySerializationProperties(); receiver = destination.getReceiver(pipe, params); if (Configuration.isAssertionsEnabled()) { receiver = new RegularSequenceChecker(receiver, true); } //receiver = new TracingFilter(receiver); receiver.getPipelineConfiguration().setController(controller); if (baseOutputUriWasSet) { try { if (destination.getDestinationBaseURI() == null) { destination.setDestinationBaseURI(new URI(controller.getBaseOutputURI())); } } catch (URISyntaxException e) { // no action } } else if (destination.getDestinationBaseURI() != null) { controller.setBaseOutputURI(destination.getDestinationBaseURI().toASCIIString()); } receiver.setSystemId(controller.getBaseOutputURI()); return receiver; } /** * 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.

* * @param controller the Controller for the transformation * @param parameters the global parameters * @param finalDestination the destination for the results of this transformation * @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 */ protected Receiver getReceivingTransformer(XsltController controller, GlobalParameterSet parameters, Destination finalDestination) throws SaxonApiException { Configuration config = controller.getConfiguration(); if (controller.getInitialMode().isDeclaredStreamable()) { Receiver sOut = getDestinationReceiver(controller, finalDestination); try { controller.initializeController(parameters); return controller.getStreamingReceiver(controller.getInitialMode(), sOut); } catch (TransformerException e) { throw new SaxonApiException(e); } } else { final Builder sourceTreeBuilder = controller.makeBuilder(); if (sourceTreeBuilder instanceof TinyBuilder) { ((TinyBuilder) sourceTreeBuilder).setStatistics(config.getTreeStatistics().SOURCE_DOCUMENT_STATISTICS); } Receiver stripper = controller.makeStripper(sourceTreeBuilder); if (controller.isStylesheetStrippingTypeAnnotations()) { stripper = controller.getConfiguration().getAnnotationStripper(stripper); } return makeTreeReceiver(controller, parameters, finalDestination, sourceTreeBuilder, stripper); } } @CSharpInnerClass(outer=true, extra={ "[email protected] sourceTreeBuilder", "Saxon.Hej.trans.XsltController controller", "Saxon.Hej.s9api.Destination finalDestination", "Saxon.Hej.expr.instruct.GlobalParameterSet parameters"}) private TreeReceiver makeTreeReceiver(XsltController controller, GlobalParameterSet parameters, Destination finalDestination, Builder sourceTreeBuilder, Receiver stripper) { return new TreeReceiver(stripper) { boolean closed = false; @Override public void close() throws XPathException { if (!closed) { try { NodeInfo doc = sourceTreeBuilder.getCurrentRoot(); if (doc != null) { doc.getTreeInfo().setSpaceStrippingRule(controller.getSpaceStrippingRule()); Receiver result = getDestinationReceiver(controller, finalDestination); try { controller.setGlobalContextItem(doc); controller.initializeController(parameters); controller.applyTemplates(doc, result); } catch (TransformerException e) { throw new SaxonApiException(e); } } } catch (SaxonApiException e) { throw XPathException.makeXPathException(e); } closed = true; } } }; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy