net.sf.saxon.PreparedStylesheet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of saxon-he Show documentation
Show all versions of saxon-he Show documentation
An OSGi bundle for Saxon-HE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon;
import net.sf.saxon.event.*;
import net.sf.saxon.expr.instruct.Executable;
import net.sf.saxon.expr.instruct.GlobalParameterSet;
import net.sf.saxon.expr.instruct.Template;
import net.sf.saxon.expr.instruct.UserFunction;
import net.sf.saxon.functions.ExecutableFunctionLibrary;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.functions.FunctionLibraryList;
import net.sf.saxon.lib.AugmentedSource;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.lib.ParseOptions;
import net.sf.saxon.lib.Validation;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.om.StylesheetSpaceStrippingRule;
import net.sf.saxon.style.*;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.*;
import net.sf.saxon.tree.linked.DocumentImpl;
import net.sf.saxon.tree.linked.LinkedTreeBuilder;
import net.sf.saxon.value.DecimalValue;
import net.sf.saxon.value.Whitespace;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXSource;
import java.io.Serializable;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* This PreparedStylesheet class represents a Stylesheet that has been
* prepared for execution (or "compiled").
*
* Note that the PreparedStylesheet object does not contain a reference to the source stylesheet
* tree (rooted at an XSLStyleSheet object). This allows the source tree to be garbage-collected
* when it is no longer required.
*/
public class PreparedStylesheet extends Executable implements Templates, Serializable {
private CompilerInfo compilerInfo;
private transient StyleNodeFactory nodeFactory;
private int errorCount = 0;
private HashMap nextStylesheetCache;
// cache for stylesheets named as "saxon:next-in-chain"
// definitions of decimal formats
private DecimalFormatManager decimalFormatManager;
// definitions of template rules (XSLT only)
private RuleManager ruleManager;
// index of named templates.
private HashMap namedTemplateTable;
// a boolean flag indicating whether the stylesheet makes any use of tunnel parameters
private boolean usesTunnel = false;
// manager class for accumulator rules (XSLT 3.0 only)
private IAccumulatorManager accumulatorManager = null;
// manager list of variables coming from xsl:param and xsl:variable relating to statis attribute
private GlobalParameterSet variableList = new GlobalParameterSet();
/**
* Constructor - deliberately protected
* @param config The Configuration set up by the TransformerFactory
* @param info Compilation options
*/
protected PreparedStylesheet(Configuration config, CompilerInfo info) {
super(config);
nodeFactory = config.makeStyleNodeFactory();
nodeFactory.setXsltProcessorVersion(info.getXsltVersion());
nodeFactory.makeAccumulatorManager(this);
RuleManager rm = new RuleManager();
rm.setRecoveryPolicy(info.getRecoveryPolicy());
setRuleManager(rm);
setHostLanguage(Configuration.XSLT, info.getXsltVersion().equals(DecimalValue.THREE));
setCollationMap(info.getCollationMap());
setSchemaAware(info.isSchemaAware());
compilerInfo = info;
if (compilerInfo.getErrorListener() == null) {
compilerInfo.setErrorListener(config.getErrorListener());
}
}
/**
* Factory method to make a PreparedStylesheet
* @param source the source of this principal stylesheet module
* @param config the Saxon configuration
* @param info compile-time options for this stylesheet compilation
* @return the prepared stylesheet
* @throws javax.xml.transform.TransformerConfigurationException if there is a static error in the stylesheet
*/
public static PreparedStylesheet compile(Source source, Configuration config, CompilerInfo info)
throws TransformerConfigurationException {
PreparedStylesheet pss = new PreparedStylesheet(config, info);
pss.prepare(source);
return pss;
}
/**
* Make a Transformer from this Templates object.
* @return the new Transformer (always a Controller)
* @see net.sf.saxon.Controller
*/
public Transformer newTransformer() {
Controller c = new Controller(getConfiguration(), this);
c.setPreparedStylesheet(this);
c.setRecoveryPolicy(compilerInfo.getRecoveryPolicy());
if (compilerInfo.getDefaultInitialTemplate() != null) {
try {
c.setInitialTemplate(compilerInfo.getDefaultInitialTemplate().getClarkName());
} catch (XPathException err) {
// ignore error if there is no template with this name
}
}
if (compilerInfo.getDefaultInitialMode() != null) {
c.setInitialMode(compilerInfo.getDefaultInitialMode().getClarkName());
}
return c;
}
/**
* Set the configuration in which this stylesheet is compiled.
* Intended for internal use.
* @param config the configuration to be used.
*/
public void setConfiguration(Configuration config) {
super.setConfiguration(config);
this.compilerInfo = config.getDefaultXsltCompilerInfo();
}
/**
* Get the StyleNodeFactory in use. The StyleNodeFactory determines which subclass of StyleElement
* to use for each element node in the stylesheet tree.
* @return the StyleNodeFactory
*/
public StyleNodeFactory getStyleNodeFactory() {
return nodeFactory;
}
/**
* Set the DecimalFormatManager which handles decimal-format definitions
* @param dfm the DecimalFormatManager containing the named xsl:decimal-format definitions
*/
public void setDecimalFormatManager(DecimalFormatManager dfm) {
decimalFormatManager = dfm;
}
/**
* Get the DecimalFormatManager which handles decimal-format definitions
* @return the DecimalFormatManager containing the named xsl:decimal-format definitions
*/
public DecimalFormatManager getDecimalFormatManager() {
if (decimalFormatManager == null) {
decimalFormatManager = new DecimalFormatManager();
}
return decimalFormatManager;
}
/**
* Get the class that manages accumulator functions
* @return the class that manages accumulators. Always null in Saxon-HE
*/
public IAccumulatorManager getAccumulatorManager() {
return accumulatorManager;
}
/**
* Set the class that manages accumulator functions
* @param accumulatorManager the manager of accumulator functions
*/
public void setAccumulatorManager(IAccumulatorManager accumulatorManager) {
this.accumulatorManager = accumulatorManager;
}
/**
* Say that the stylesheet uses tunnel parameters. (This information is used by the bytecode generator,
* which avoids generating code to pass tunnel parameters on every apply-templates call if there are no
* tunnel parameters anywhere in the stylesheet).
*/
public void setUsesTunnelParameters() {
usesTunnel = true;
}
/**
* Ask whether the stylesheet uses tunnel parameters. (Called by the bytecode generator).
* @return true if the stylesheet uses tunnel parameters.
*/
public boolean usesTunnelParameters() {
return usesTunnel;
}
/**
* Prepare a stylesheet from a Source document
* @param styleSource the source document containing the stylesheet
* @throws TransformerConfigurationException
* if compilation of the
* stylesheet fails for any reason
*/
protected void prepare(Source styleSource) throws TransformerConfigurationException {
DocumentImpl doc;
try {
doc = loadStylesheetModule(styleSource);
setStylesheetDocument(doc);
} catch (XPathException e) {
try {
compilerInfo.getErrorListener().fatalError(e);
} catch (TransformerException e2) {
// ignore an exception thrown by the error handler
}
if (errorCount == 0) {
errorCount++;
}
}
if (errorCount > 0) {
throw new TransformerConfigurationException(
"Failed to compile stylesheet. " +
errorCount +
(errorCount == 1 ? " error " : " errors ") +
"detected.");
}
}
/**
* Build the tree representation of a stylesheet module
* @param styleSource the source of the module
* @return the root Document node of the tree containing the stylesheet
* module
* @throws XPathException if XML parsing or tree
* construction fails
*/
public DocumentImpl loadStylesheetModule(Source styleSource)
throws XPathException {
StyleNodeFactory nodeFactory = getStyleNodeFactory();
PipelineConfiguration pipe = getConfiguration().makePipelineConfiguration();
LinkedTreeBuilder styleBuilder = new LinkedTreeBuilder(pipe);
pipe.setURIResolver(compilerInfo.getURIResolver());
pipe.setErrorListener(compilerInfo.getErrorListener());
styleBuilder.setSystemId(styleSource.getSystemId());
styleBuilder.setNodeFactory(nodeFactory);
styleBuilder.setLineNumbering(true);
UseWhenFilter useWhenFilter = new UseWhenFilter(this, styleBuilder);
StartTagBuffer startTagBuffer = new StartTagBuffer(useWhenFilter);
useWhenFilter.setStartTagBuffer(startTagBuffer);
StylesheetSpaceStrippingRule rule = new StylesheetSpaceStrippingRule(getConfiguration().getNamePool());
Stripper styleStripper = new Stripper(rule, startTagBuffer);
CommentStripper commentStripper = new CommentStripper(styleStripper);
// build the stylesheet document
DocumentImpl doc;
ParseOptions options;
if (styleSource instanceof AugmentedSource) {
options = ((AugmentedSource)styleSource).getParseOptions();
styleSource = ((AugmentedSource)styleSource).getContainedSource();
} else {
options = new ParseOptions();
}
options.setErrorListener(compilerInfo.getErrorListener());
options.setSchemaValidationMode(Validation.STRIP);
options.setDTDValidationMode(Validation.STRIP);
options.setLineNumbering(true);
options.setStripSpace(Whitespace.NONE);
try {
if (options.getXMLReader() == null && Configuration.getPlatform().isJava()) {
XMLReader styleParser = getConfiguration().getStyleParser();
options.setXMLReader(styleParser);
Sender.send(styleSource, commentStripper, options);
getConfiguration().reuseStyleParser(styleParser);
} else {
Sender.send(styleSource, commentStripper, options);
}
doc = (DocumentImpl)styleBuilder.getCurrentRoot();
styleBuilder.reset();
return doc;
} finally {
if (options.isPleaseCloseAfterUse()) {
ParseOptions.close(styleSource);
}
}
}
/**
* Create a PreparedStylesheet from a supplied DocumentInfo
* Note: the document must have been built using the StyleNodeFactory
* @param doc the document containing the stylesheet module
* @throws XPathException if the document supplied
* is not a stylesheet
*/
protected void setStylesheetDocument(DocumentImpl doc)
throws XPathException {
DocumentImpl styleDoc = doc;
// If top-level node is a literal result element, stitch it into a skeleton stylesheet
StyleElement topnode = (StyleElement)styleDoc.getDocumentElement();
if (topnode instanceof LiteralResultElement) {
styleDoc = ((LiteralResultElement)topnode).makeStylesheet(this);
}
if (!(styleDoc.getDocumentElement() instanceof XSLStylesheet)) {
throw new XPathException(
"Outermost element of stylesheet is not xsl:stylesheet or xsl:transform or literal result element");
}
XSLStylesheet top = (XSLStylesheet)styleDoc.getDocumentElement();
if (compilerInfo.isVersionWarning() &&
top.getEffectiveVersion().compareTo(getStyleNodeFactory().getXsltProcessorVersion()) != 0) {
try {
TransformerException w = new TransformerException(
"Running an XSLT " + top.getEffectiveVersion() + " stylesheet with an XSLT " +
getStyleNodeFactory().getXsltProcessorVersion() + " processor");
w.setLocator(topnode);
getConfiguration().getErrorListener().warning(w);
} catch (TransformerException e) {
throw XPathException.makeXPathException(e);
}
}
if (getStyleNodeFactory().getXsltProcessorVersion().compareTo(DecimalValue.TWO) > 0) {
// The condition is checked again later in non-open code, but we can give a better error message here
getConfiguration().checkLicensedFeature(Configuration.LicenseFeature.PROFESSIONAL_EDITION, "XSLT 3.0");
}
PrincipalStylesheetModule psm = new PrincipalStylesheetModule(top, 0);
psm.setPreparedStylesheet(this);
psm.setVersion(top.getAttributeValue("version"));
psm.createFunctionLibrary(compilerInfo);
// Preprocess the stylesheet, performing validation and preparing template definitions
//executable.setLocationMap(psm.getLocationMap());
top.setPrincipalStylesheetModule(psm);
try {
psm.preprocess();
} catch (XPathException e) {
Throwable e2 = e.getException();
if (e2 instanceof XPathException) {
try {
compilerInfo.getErrorListener().fatalError((XPathException)e2);
} catch (TransformerException e3) {
// ignore an error thrown by the ErrorListener
}
}
throw e;
}
// Compile the stylesheet, retaining the resulting executable
psm.compileStylesheet();
}
/**
* Set the RuleManager that handles template rules
*
* @param rm the RuleManager containing details of all the template rules
*/
public void setRuleManager(RuleManager rm) {
ruleManager = rm;
}
/**
* Get the RuleManager which handles template rules
*
* @return the RuleManager registered with setRuleManager
*/
public RuleManager getRuleManager() {
return ruleManager;
}
/**
* Get the named template with a given name.
*
* @param qName The template name
* @return The template (of highest import precedence) with this name if there is one;
* null if none is found.
*/
/*@Nullable*/ public Template getNamedTemplate(StructuredQName qName) {
if (namedTemplateTable == null) {
return null;
}
return namedTemplateTable.get(qName);
}
/**
* Register the named template with a given name
* @param templateName the name of a named XSLT template
* @param template the template
*/
public void putNamedTemplate(StructuredQName templateName, Template template) {
if (namedTemplateTable == null) {
namedTemplateTable = new HashMap(32);
}
namedTemplateTable.put(templateName, template);
}
/**
* Iterate over all the named templates defined in this Executable
* @return an iterator, the items returned being of class {@link net.sf.saxon.expr.instruct.Template}
*/
public Iterator iterateNamedTemplates() {
if (namedTemplateTable == null) {
List list = Collections.emptyList();
return list.iterator();
} else {
return namedTemplateTable.values().iterator();
}
}
/**
* Explain the expression tree for named templates in a stylesheet
* @param presenter destination for the explanatory output
*/
public void explainNamedTemplates(ExpressionPresenter presenter) {
presenter.startElement("namedTemplates");
if (namedTemplateTable != null) {
for (Template t : namedTemplateTable.values()) {
presenter.startElement("template");
presenter.emitAttribute("name", t.getTemplateName().getDisplayName());
presenter.emitAttribute("line", t.getLineNumber() + "");
presenter.emitAttribute("module", t.getSystemId());
if (t.getBody() != null) {
t.getBody().explain(presenter);
}
presenter.endElement();
}
}
presenter.endElement();
}
/**
* Determine whether trace hooks are included in the compiled code.
* @return true if trace hooks are included, false if not.
* @since 8.9
*/
public boolean isCompileWithTracing() {
return compilerInfo.isCompileWithTracing();
}
/**
* Get the properties for xsl:output. JAXP method. The object returned will
* be a clone of the internal values, and thus it can be mutated
* without mutating the Templates object, and then handed in to
* the process method.
* In Saxon, the properties object is a new, empty, Properties object that is
* backed by the live properties to supply default values for missing properties.
* This means that the property values must be read using the getProperty() method.
* Calling the get() method on the underlying Hashtable will return null.
* In Saxon 8.x, this method gets the output properties for the unnamed output
* format in the stylesheet.
* @return A Properties object reflecting the output properties defined
* for the default (unnamed) output format in the stylesheet. It may
* be mutated and supplied to the setOutputProperties() method of the
* Transformer, without affecting other transformations that use the
* same stylesheet.
* @see javax.xml.transform.Transformer#setOutputProperties
*/
public Properties getOutputProperties() {
Properties details = getDefaultOutputProperties();
return new Properties(details);
}
/**
* Report a compile time error. This calls the errorListener to output details
* of the error, and increments an error count.
* @param err the exception containing details of the error
* @throws TransformerException if the ErrorListener decides that the
* error should be reported
*/
public void reportError(TransformerException err) throws TransformerException {
if (err instanceof XPathException) {
if (!((XPathException)err).hasBeenReported()) {
errorCount++;
try {
compilerInfo.getErrorListener().fatalError(err);
((XPathException)err).setHasBeenReported(true);
} catch (Exception err2) {
// ignore secondary error
}
} else if (errorCount == 0) {
errorCount++;
}
} else {
errorCount++;
compilerInfo.getErrorListener().fatalError(err);
}
}
/**
* Get the number of errors reported so far
* @return the number of errors reported
*/
public int getErrorCount() {
return errorCount;
}
/**
* Report a compile time warning. This calls the errorListener to output details
* of the warning.
* @param err an exception holding details of the warning condition to be
* reported
*/
public void reportWarning(TransformerException err) {
try {
compilerInfo.getErrorListener().warning(err);
} catch (TransformerException err2) {
// ignore secondary error
}
}
/**
* Get a "next in chain" stylesheet. This method is intended for internal use.
* @param href the relative URI of the next-in-chain stylesheet
* @param baseURI the baseURI against which this relativeURI is to be resolved
* @return the cached stylesheet if present in the cache, or null if not
*/
/*@Nullable*/ public PreparedStylesheet getCachedStylesheet(String href, String baseURI) {
URI abs = null;
try {
abs = new URI(baseURI).resolve(href);
} catch (URISyntaxException err) {
//
}
PreparedStylesheet result = null;
if (abs != null && nextStylesheetCache != null) {
result = nextStylesheetCache.get(abs);
}
return result;
}
/**
* Save a "next in chain" stylesheet in compiled form, so that it can be reused repeatedly.
* This method is intended for internal use.
* @param href the relative URI of the stylesheet
* @param baseURI the base URI against which the relative URI is resolved
* @param pss the prepared stylesheet object to be cached
*/
public void putCachedStylesheet(String href, String baseURI, PreparedStylesheet pss) {
URI abs = null;
try {
abs = new URI(baseURI).resolve(href);
} catch (URISyntaxException err) {
//
}
if (abs != null) {
if (nextStylesheetCache == null) {
nextStylesheetCache = new HashMap(4);
}
nextStylesheetCache.put(abs, pss);
}
}
/**
* Get the CompilerInfo containing details of XSLT compilation options
* @return the CompilerInfo containing compilation options
* @since 9.2
*/
public CompilerInfo getCompilerInfo() {
return compilerInfo;
}
/**
* Produce an XML representation of the compiled and optimized stylesheet
* @param presenter defines the destination and format of the output
*/
public void explain(ExpressionPresenter presenter) {
presenter.startElement("stylesheet");
getKeyManager().explainKeys(presenter);
explainGlobalVariables(presenter);
ruleManager.explainTemplateRules(presenter);
explainNamedTemplates(presenter);
FunctionLibraryList libList = getFunctionLibrary();
List libraryList = libList.getLibraryList();
presenter.startElement("functions");
for (FunctionLibrary lib : libraryList) {
if (lib instanceof ExecutableFunctionLibrary) {
for (Iterator f = ((ExecutableFunctionLibrary) lib).iterateFunctions(); f.hasNext(); ) {
UserFunction func = (UserFunction) f.next();
presenter.startElement("function");
presenter.emitAttribute("name", func.getFunctionName().getDisplayName());
presenter.emitAttribute("line", func.getLineNumber() + "");
presenter.emitAttribute("module", func.getSystemId());
func.getBody().explain(presenter);
presenter.endElement();
}
}
}
presenter.endElement();
presenter.endElement();
}
/**
* Get the stylesheet specification(s) associated
* via the xml-stylesheet processing instruction (see
* http://www.w3.org/TR/xml-stylesheet/) with the document
* document specified in the source parameter, and that match
* the given criteria. Note that it is possible to return several
* stylesheets, in which case they are applied as if they were
* a list of imports or cascades.
* @param config The Saxon Configuration
* @param source The XML source document.
* @param media The media attribute to be matched. May be null, in which
* case the prefered templates will be used (i.e. alternate = no).
* @param title The value of the title attribute to match. May be null.
* @param charset The value of the charset attribute to match. May be null.
* @return A Source object suitable for passing to the TransformerFactory.
* @throws TransformerConfigurationException
* if any problems occur
*/
public static Source getAssociatedStylesheet(
Configuration config, Source source, String media, String title, String charset)
throws TransformerConfigurationException {
PIGrabber grabber = new PIGrabber(new Sink(config.makePipelineConfiguration()));
grabber.setFactory(config);
grabber.setCriteria(media, title);
grabber.setBaseURI(source.getSystemId());
grabber.setURIResolver(config.getURIResolver());
try {
Sender.send(source, grabber, null);
// this parse will be aborted when the first start tag is found
} catch (XPathException err) {
if (grabber.isTerminated()) {
// do nothing
} else {
throw new TransformerConfigurationException(
"Failed while looking for xml-stylesheet PI", err);
}
}
try {
Source[] sources = grabber.getAssociatedStylesheets();
if (sources == null) {
throw new TransformerConfigurationException(
"No matching processing instruction found");
}
return compositeStylesheet(config, source.getSystemId(), sources);
} catch (TransformerException err) {
if (err instanceof TransformerConfigurationException) {
throw (TransformerConfigurationException)err;
} else {
throw new TransformerConfigurationException(err);
}
}
}
/**
* Process a series of stylesheet inputs, treating them in import or cascade
* order. This is mainly for support of the getAssociatedStylesheets
* method, but may be useful for other purposes.
* @param config the Saxon configuration
* @param baseURI the base URI to be used for the synthesized composite stylesheet
* @param sources An array of Source objects representing individual stylesheets.
* @return A Source object representing a composite stylesheet.
* @throws javax.xml.transform.TransformerConfigurationException if there is a static error
* in the stylesheet
*/
private static Source compositeStylesheet(Configuration config, String baseURI, Source[] sources)
throws TransformerConfigurationException {
if (sources.length == 1) {
return sources[0];
} else if (sources.length == 0) {
throw new TransformerConfigurationException(
"No stylesheets were supplied");
}
// create a new top-level stylesheet that imports all the others
StringBuilder sb = new StringBuilder(250);
sb.append("");
for (Source source : sources) {
sb.append(" ");
}
sb.append(" ");
InputSource composite = new InputSource();
composite.setSystemId(baseURI);
composite.setCharacterStream(new StringReader(sb.toString()));
return new SAXSource(config.getSourceParser(), composite);
}
}