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

net.sf.saxon.lib.SerializerFactory 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.lib;

import net.sf.saxon.Configuration;
import net.sf.saxon.event.*;
import net.sf.saxon.functions.ResolveURI;
import net.sf.saxon.om.NameChecker;
import net.sf.saxon.om.TreeInfo;
import net.sf.saxon.query.SequenceWrapper;
import net.sf.saxon.serialize.*;
import net.sf.saxon.stax.StAXResultHandlerImpl;
import net.sf.saxon.str.StringView;
import net.sf.saxon.str.UnicodeWriter;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.SaxonErrorCode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharpModifiers;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.BigDecimalValue;
import net.sf.saxon.value.StringValue;
import net.sf.saxon.value.Whitespace;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stream.StreamResult;
import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.regex.Pattern;

/**
 * Helper class to construct a serialization pipeline for a given result destination
 * and a given set of output properties. The pipeline is represented by a Receiver object
 * to which result tree events are sent.
 * 

Since Saxon 8.8 is is possible to write a subclass of SerializerFactory and register it * with the Configuration, allowing customisation of the Serializer pipeline.

*

The class includes methods for instantiating each of the components used on the Serialization * pipeline. This allows a customized SerializerFactory to replace any or all of these components * by subclasses that refine the behaviour.

*/ public class SerializerFactory { Configuration config; PipelineConfiguration pipe; /** * Create a SerializerFactory * * @param config the Saxon Configuration */ public SerializerFactory(Configuration config) { this.config = config; } public SerializerFactory(PipelineConfiguration pipe) { this.pipe = pipe; this.config = pipe.getConfiguration(); } public Configuration getConfiguration() { return config; } /** * Create a serializer with given output properties, and return * an XMLStreamWriter that can be used to feed events to the serializer. * * @param result the destination of the serialized output (wraps a Writer, an OutputStream, or a File) * @param properties the serialization properties to be used * @return a serializer in the form of an XMLStreamWriter * @throws net.sf.saxon.trans.XPathException * if any error occurs */ public StreamWriterToReceiver getXMLStreamWriter( StreamResult result, Properties properties) throws XPathException { Receiver r = getReceiver(result, new SerializationProperties(properties)); r = new NamespaceReducer(r); return new StreamWriterToReceiver(r); } /** * Get a Receiver that wraps a given Result object. Saxon calls this method to construct * a serialization pipeline. The method can be overridden in a subclass; alternatively, the * subclass can override the various methods used to instantiate components of the serialization * pipeline. *

Note that this method ignores the {@link SaxonOutputKeys#WRAP} output property. If * wrapped output is required, the user must create a {@link net.sf.saxon.query.SequenceWrapper} directly.

*

The effect of the method changes in Saxon 9.7 so that for serialization methods other than * "json" and "adaptive", the returned Receiver performs the function of "sequence normalization" as * defined in the Serialization specification. Previously the client code handled this by wrapping the * result in a ComplexContentOutputter (usually as a side-effect of called XPathContext.changeOutputDestination()). * Wrapping in a ComplexContentOutputter is no longer necessary, though it does no harm because the ComplexContentOutputter * is idempotent.

* *

Changed in 9.9 so that no character maps are used. Previously the character maps from the Executable * associated with the Controller referenced from the PipelineConfiguration were used.

* * @param result The final destination of the serialized output. Usually a StreamResult, * but other kinds of Result are possible. * @param pipe The PipelineConfiguration. * @param props The serialization properties. If this includes the property {@link SaxonOutputKeys#USE_CHARACTER_MAPS} * then the PipelineConfiguration must contain a non-null Controller, and the Executable associated with this Controller * must have a CharacterMapIndex which is used to resolve the names of the character maps appearing in this property. * @return the newly constructed Receiver that performs the required serialization * @throws net.sf.saxon.trans.XPathException * if any failure occurs * @deprecated since Saxon 9.9: use one of the other {@code getReceiver} methods */ @Deprecated public Receiver getReceiver(Result result, PipelineConfiguration pipe, Properties props) throws XPathException { return getReceiver(result, new SerializationProperties(props), pipe); } /** * Get a Receiver that wraps a given Result object. Saxon calls this method to construct * a serialization pipeline. The method can be overridden in a subclass; alternatively, the * subclass can override the various methods used to instantiate components of the serialization * pipeline. *

This version of the method calls {@link #getReceiver(Result, SerializationProperties, PipelineConfiguration)} * supplying default output properties, and a {@code PipelineConfiguration} newly constructed using * {@link Configuration#makePipelineConfiguration()}.

* * @param result The final destination of the serialized output. Usually a StreamResult, * but other kinds of Result are possible. * @throws XPathException if a serializer cannot be created */ public Receiver getReceiver(Result result) throws XPathException { return getReceiver(result, new SerializationProperties(), config.makePipelineConfiguration()); } /** * Get a Receiver that wraps a given Result object. Saxon calls this method to construct * a serialization pipeline. The method can be overridden in a subclass; alternatively, the * subclass can override the various methods used to instantiate components of the serialization * pipeline. *

This version of the method calls {@link #getReceiver(Result, SerializationProperties, PipelineConfiguration)} * supplying a {@code PipelineConfiguration} newly constructed using {@link Configuration#makePipelineConfiguration()}.

* * @param result The final destination of the serialized output. Usually a StreamResult, * but other kinds of Result are possible. * @param params The serialization properties, including character maps * @return the newly constructed Receiver that performs the required serialization * @throws XPathException if a serializer cannot be created */ public Receiver getReceiver(Result result, SerializationProperties params) throws XPathException { return getReceiver(result, params, config.makePipelineConfiguration()); } /** * Get a Receiver that wraps a given Result object. Saxon calls this method to construct * a serialization pipeline. The method can be overridden in a subclass; alternatively, the * subclass can override the various methods used to instantiate components of the serialization * pipeline. *

Note that this method ignores the {@link SaxonOutputKeys#WRAP} output property. If * wrapped output is required, the user must create a {@link net.sf.saxon.query.SequenceWrapper} directly.

*

The effect of the method changes in Saxon 9.7 so that for serialization methods other than * "json" and "adaptive", the returned Receiver performs the function of "sequence normalization" as * defined in the Serialization specification. Previously the client code handled this by wrapping the * result in a ComplexContentOutputter (usually as a side-effect of called XPathContext.changeOutputDestination()). * Wrapping in a ComplexContentOutputter is no longer necessary, though it does no harm because the ComplexContentOutputter * is idempotent.

* * @param result The final destination of the serialized output. Usually a StreamResult, * but other kinds of Result are possible. * @param params The serialization properties, including character maps * @param pipe The PipelineConfiguration. * @return the newly constructed Receiver that performs the required serialization * @throws XPathException if a serializer cannot be created */ @CSharpModifiers(code = {"public", "virtual"}) public Receiver getReceiver(Result result, SerializationProperties params, PipelineConfiguration pipe) throws XPathException { Objects.requireNonNull(result); Objects.requireNonNull(params); Objects.requireNonNull(pipe); Properties props = params.getProperties(); CharacterMapIndex charMapIndex = params.getCharacterMapIndex(); if (charMapIndex == null) { charMapIndex = new CharacterMapIndex(); } String nextInChain = props.getProperty(SaxonOutputKeys.NEXT_IN_CHAIN); if (nextInChain != null && !nextInChain.isEmpty()) { String href = props.getProperty(SaxonOutputKeys.NEXT_IN_CHAIN); String base = props.getProperty(SaxonOutputKeys.NEXT_IN_CHAIN_BASE_URI); if (base == null) { base = ""; } Properties sansNext = new Properties(props); sansNext.setProperty(SaxonOutputKeys.NEXT_IN_CHAIN, ""); return prepareNextStylesheet(pipe, href, base, result); } String paramDoc = props.getProperty(SaxonOutputKeys.PARAMETER_DOCUMENT); if (paramDoc != null && !paramDoc.isEmpty()) { String base = props.getProperty(SaxonOutputKeys.PARAMETER_DOCUMENT_BASE_URI); if (base == null) { base = result.getSystemId(); } Properties props2 = new Properties(props); props2.setProperty(SaxonOutputKeys.PARAMETER_DOCUMENT, ""); ResourceRequest rr = new ResourceRequest(); rr.relativeUri = paramDoc; rr.baseUri = base; try { rr.uri = ResolveURI.makeAbsolute(paramDoc, base).toString(); } catch (URISyntaxException err) { throw XPathException.makeXPathException(err); } rr.nature = NamespaceConstant.OUTPUT; rr.purpose = "serialization"; Source source = rr.resolve(config.getResourceResolver(), new DirectResourceResolver(config)); ParseOptions options = new ParseOptions(); options.setSchemaValidationMode(Validation.LAX); options.setDTDValidationMode(Validation.SKIP); TreeInfo doc = config.buildDocumentTree(source); SerializationParamsHandler ph = new SerializationParamsHandler(); ph.setSerializationParams(doc.getRootNode()); Properties paramDocProps = ph.getSerializationProperties().getProperties(); for (String name : paramDocProps.stringPropertyNames()){ String value = paramDocProps.getProperty(name); props2.setProperty(name, value); } CharacterMap charMap = ph.getCharacterMap(); if (charMap != null) { props2.setProperty(SaxonOutputKeys.USE_CHARACTER_MAPS, charMap.getName().getClarkName()); charMapIndex.putCharacterMap(charMap.getName(), charMap); } props = props2; params = new SerializationProperties(props2, charMapIndex); } UnicodeWriter uWriter = null; ExpandedStreamResult expandedResult = null; if (result instanceof UnicodeWriterResult) { uWriter = ((UnicodeWriterResult) result).getUnicodeWriter(); } else if (result instanceof StreamResult) { expandedResult = new ExpandedStreamResult(getConfiguration(), (StreamResult)result, props); } if (result instanceof StreamResult || result instanceof UnicodeWriterResult) { // The "target" is the start of the output pipeline, the Receiver that // instructions will actually write to (except that other things like a // NamespaceReducer may get added in front of it). The "emitter" is the // last thing in the output pipeline, the Receiver that actually generates // characters or bytes that are written to the StreamResult. SequenceReceiver target; String method = props.getProperty(OutputKeys.METHOD); if (method == null) { return newUncommittedSerializer(result, new Sink(pipe), params); } Emitter emitter = null; switch (method) { case "html": { emitter = newHTMLEmitter(props); emitter.setPipelineConfiguration(pipe); if (uWriter == null) { uWriter = expandedResult.obtainUnicodeWriter(); } emitter.setUnicodeWriter(uWriter); target = createHTMLSerializer(emitter, params, pipe); break; } case "xml": { emitter = newXMLEmitter(props); emitter.setPipelineConfiguration(pipe); if (uWriter == null) { assert expandedResult != null; uWriter = expandedResult.obtainUnicodeWriter(); } emitter.setUnicodeWriter(uWriter); target = createXMLSerializer((XMLEmitter) emitter, params); break; } case "xhtml": { emitter = newXHTMLEmitter(props); emitter.setPipelineConfiguration(pipe); if (uWriter == null) { assert expandedResult != null; uWriter = expandedResult.obtainUnicodeWriter(); } emitter.setUnicodeWriter(uWriter); target = createXHTMLSerializer(emitter, params, pipe); break; } case "text": { emitter = newTEXTEmitter(); emitter.setPipelineConfiguration(pipe); if (uWriter == null) { assert expandedResult != null; uWriter = expandedResult.obtainUnicodeWriter(); } emitter.setUnicodeWriter(uWriter); target = createTextSerializer(emitter, params); break; } case "json": { props.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); if (uWriter == null) { uWriter = expandedResult.obtainUnicodeWriter(); } JSONEmitter je = new JSONEmitter(pipe, uWriter, props); JSONSerializer js = new JSONSerializer(pipe, je, props); String sortOrder = props.getProperty(SaxonOutputKeys.PROPERTY_ORDER); if (sortOrder != null) { js.setPropertySorter(getPropertySorter(sortOrder)); } CharacterMapExpander characterMapExpander = makeCharacterMapExpander(pipe, props, charMapIndex); ProxyReceiver normalizer = makeUnicodeNormalizer(pipe, props); return customizeJSONSerializer(js, props, characterMapExpander, normalizer); } case "adaptive": { if (uWriter == null) { assert expandedResult != null; uWriter = expandedResult.obtainUnicodeWriter(); } AdaptiveEmitter je = new AdaptiveEmitter(pipe, uWriter); je.setOutputProperties(props); CharacterMapExpander characterMapExpander = makeCharacterMapExpander(pipe, props, charMapIndex); ProxyReceiver normalizer = makeUnicodeNormalizer(pipe, props); return customizeAdaptiveSerializer(je, props, characterMapExpander, normalizer); } default: { if (method.startsWith("{")) { // We should have an EQName name here rather than a Clark name, but handle both for robustness method = "Q" + method; } if (method.startsWith("Q{" + NamespaceConstant.SAXON + "}")) { CharacterMapExpander characterMapExpander = makeCharacterMapExpander(pipe, props, charMapIndex); ProxyReceiver normalizer = makeUnicodeNormalizer(pipe, props); target = createSaxonSerializationMethod( method, params, pipe, characterMapExpander, normalizer, expandedResult, result); if (target instanceof Emitter) { emitter = (Emitter) target; } } else { Receiver userReceiver; userReceiver = createUserDefinedOutputMethod(method, props, pipe); if (userReceiver instanceof Emitter) { emitter = (Emitter) userReceiver; if (uWriter == null) { assert expandedResult != null; uWriter = expandedResult.obtainUnicodeWriter(); } emitter.setUnicodeWriter(uWriter); target = params.makeSequenceNormalizer(emitter); } else { return params.makeSequenceNormalizer(userReceiver); } } } break; } if (emitter != null) { emitter.setOutputProperties(props); } //target = new RegularSequenceChecker(target); // add this back in for diagnostics only target.setSystemId(result.getSystemId()); return target; } else { // Handle results other than StreamResult: these generally do not involve serialization return getReceiverForNonSerializedResult(result, props, pipe); } } private ProxyReceiver makeUnicodeNormalizer(PipelineConfiguration pipe, Properties props) throws XPathException { String normForm = props.getProperty(SaxonOutputKeys.NORMALIZATION_FORM); if (normForm != null && !normForm.equals("none")) { return newUnicodeNormalizer(new Sink(pipe), props); } return null; } private CharacterMapExpander makeCharacterMapExpander(PipelineConfiguration pipe, Properties props, CharacterMapIndex charMapIndex) throws XPathException { String useMaps = props.getProperty(SaxonOutputKeys.USE_CHARACTER_MAPS); if (useMaps != null) { return charMapIndex.makeCharacterMapExpander(useMaps, new Sink(pipe), this); } return null; } /** * Get a Receiver to handle a result other than a StreamResult. This will generally not involve * serialization. * @param result the destination * @param props the serialization parameters (which in most cases will be ignored) * @param pipe the pipeline configuration * @return a suitable receiver to accept the raw query or transformation results * @throws XPathException if any failure occurs */ private Receiver getReceiverForNonSerializedResult(Result result, Properties props, PipelineConfiguration pipe) throws XPathException { if (result instanceof Emitter) { if (((Emitter) result).getOutputProperties() == null) { ((Emitter) result).setOutputProperties(props); } return (Emitter) result; } else if (result instanceof JSONSerializer) { if (((JSONSerializer) result).getOutputProperties() == null) { ((JSONSerializer) result).setOutputProperties(props); } return (JSONSerializer) result; } else if (result instanceof AdaptiveEmitter) { if (((AdaptiveEmitter) result).getOutputProperties() == null) { ((AdaptiveEmitter) result).setOutputProperties(props); } return (AdaptiveEmitter) result; } else if (result instanceof Receiver) { Receiver receiver = (Receiver) result; receiver.setSystemId(result.getSystemId()); receiver.setPipelineConfiguration(pipe); if (((Receiver) result).handlesAppend() && "no".equals(props.getProperty(SaxonOutputKeys.BUILD_TREE))) { return receiver; // TODO: handle item-separator } else { return new TreeReceiver(receiver); } } else if (result instanceof SAXResult) { ContentHandlerProxy proxy = newContentHandlerProxy(); proxy.setUnderlyingContentHandler(((SAXResult) result).getHandler()); proxy.setPipelineConfiguration(pipe); proxy.setOutputProperties(props); if ("yes".equals(props.getProperty(SaxonOutputKeys.SUPPLY_SOURCE_LOCATOR))) { if (config.isCompileWithTracing() && pipe.getController() != null) { pipe.getController().addTraceListener(proxy.getTraceListener()); } else { throw new XPathException( "Cannot use saxon:supply-source-locator unless tracing was enabled at compile time", SaxonErrorCode.SXSE0002); } } //proxy.open(); return makeSequenceNormalizer(proxy, props); } else if (result instanceof StAXResult) { StAXResultHandler handler = new StAXResultHandlerImpl(); Receiver r = handler.getReceiver(result, props); r.setPipelineConfiguration(pipe); return makeSequenceNormalizer(r, props); } else { if (pipe != null) { // try to find an external object model that knows this kind of Result List externalObjectModels = pipe.getConfiguration().getExternalObjectModels(); for (ExternalObjectModel model : externalObjectModels) { Receiver builder = model.getDocumentBuilder(result); if (builder != null) { builder.setSystemId(result.getSystemId()); builder.setPipelineConfiguration(pipe); return new TreeReceiver(builder); } } } } throw new IllegalArgumentException("Unknown type of result: " + result.getClass()); } public SequenceReceiver makeSequenceNormalizer(Receiver receiver, Properties properties) { String method = properties.getProperty(OutputKeys.METHOD); if ("json".equals(method) || "adaptive".equals(method)) { return receiver instanceof SequenceReceiver ? (SequenceReceiver)receiver : new TreeReceiver(receiver); } else { PipelineConfiguration pipe = receiver.getPipelineConfiguration(); SequenceReceiver result; String separator = properties.getProperty(SaxonOutputKeys.ITEM_SEPARATOR); if (separator == null || "#absent".equals(separator)) { result = new SequenceNormalizerWithSpaceSeparator(receiver); } else { result = new SequenceNormalizerWithItemSeparator(receiver, StringView.of(separator)); } result.setPipelineConfiguration(pipe); return result; } } /** * Create a serialization pipeline to implement the HTML output method. This method is protected * so that it can be customized in a user-written SerializerFactory * * @param emitter the emitter at the end of the pipeline (created using the method {@link #newHTMLEmitter} * @param params the serialization properties * @param pipe the pipeline configuration information * @return a Receiver acting as the entry point to the serialization pipeline * @throws XPathException if a failure occurs */ protected SequenceReceiver createHTMLSerializer( Emitter emitter, SerializationProperties params, PipelineConfiguration pipe) throws XPathException { Receiver target; target = emitter; Properties props = params.getProperties(); if (!"no".equals(props.getProperty(OutputKeys.INDENT))) { target = newHTMLIndenter(target, props); } target = new NamespaceDifferencer(target, props); target = injectUnicodeNormalizer(params, target); target = injectCharacterMapExpander(params, target, true); String cdataElements = props.getProperty(OutputKeys.CDATA_SECTION_ELEMENTS); if (cdataElements != null && !cdataElements.isEmpty()) { target = newCDATAFilter(target, props); } if (SaxonOutputKeys.isHtmlVersion5(props)) { target = addHtml5Component(target, props); } if (!"no".equals(props.getProperty(SaxonOutputKeys.ESCAPE_URI_ATTRIBUTES))) { target = newHTMLURIEscaper(target, props); } if (!"no".equals(props.getProperty(SaxonOutputKeys.INCLUDE_CONTENT_TYPE))) { target = newHTMLMetaTagAdjuster(target, props); } String attributeOrder = props.getProperty(SaxonOutputKeys.ATTRIBUTE_ORDER); if (attributeOrder != null && !attributeOrder.isEmpty()) { target = newAttributeSorter(target, props); } FilterFactory validationFactory = params.getValidationFactory(); if (validationFactory != null) { target = validationFactory.makeFilter(target); } return makeSequenceNormalizer(target, props); } /** * Create a serialization pipeline to implement the text output method. This method is protected * so that it can be customized in a user-written SerializerFactory * * @param emitter the emitter at the end of the pipeline (created using the method {@link #newTEXTEmitter} * @param params the serialization properties * @return a Receiver acting as the entry point to the serialization pipeline * @throws XPathException if a failure occurs */ protected SequenceReceiver createTextSerializer( Emitter emitter, SerializationProperties params) throws XPathException { Properties props = params.getProperties(); Receiver target; target = injectUnicodeNormalizer(params, emitter); target = injectCharacterMapExpander(params, target, false); target = addTextOutputFilter(target, props); FilterFactory validationFactory = params.getValidationFactory(); if (validationFactory != null) { target = validationFactory.makeFilter(target); } return makeSequenceNormalizer(target, props); } /** * Create a serialization pipeline to implement the JSON output method. This method is protected * so that it can be customized in a user-written SerializerFactory * * @param emitter the emitter at the end of the pipeline (created using the method {@link #newTEXTEmitter} * @param props the serialization properties * @param characterMapExpander the filter to be used for expanding character maps defined in the stylesheet * @param normalizer the filter used for Unicode normalization * @return a Receiver acting as the entry point to the serialization pipeline */ protected SequenceReceiver customizeJSONSerializer( JSONSerializer emitter, Properties props, CharacterMapExpander characterMapExpander, ProxyReceiver normalizer) throws XPathException { if (normalizer instanceof UnicodeNormalizer) { emitter.setNormalizationForm(((UnicodeNormalizer)normalizer).getNormalizationForm()); } if (characterMapExpander != null) { emitter.setCharacterMap(characterMapExpander.getCharacterMap()); } return emitter; } /** * Create a serialization pipeline to implement the Adaptive output method. This method is protected * so that it can be customized in a user-written SerializerFactory * * @param emitter the emitter at the end of the pipeline * @param props the serialization properties * @param characterMapExpander the filter to be used for expanding character maps defined in the stylesheet * @param normalizer the filter used for Unicode normalization * @return a Receiver acting as the entry point to the serialization pipeline */ protected SequenceReceiver customizeAdaptiveSerializer( AdaptiveEmitter emitter, Properties props, CharacterMapExpander characterMapExpander, ProxyReceiver normalizer) { if (normalizer instanceof UnicodeNormalizer) { emitter.setNormalizationForm(((UnicodeNormalizer) normalizer).getNormalizationForm()); } if (characterMapExpander != null) { emitter.setCharacterMap(characterMapExpander.getCharacterMap()); } return emitter; } /** * Create a serialization pipeline to implement the XHTML output method. This method is protected * so that it can be customized in a user-written SerializerFactory * * @param emitter the emitter at the end of the pipeline (created using the method {@link #newXHTMLEmitter} * @param params the serialization properties * @param pipe the pipeline configuration information * @return a Receiver acting as the entry point to the serialization pipeline * @throws XPathException if a failure occurs */ protected SequenceReceiver createXHTMLSerializer( Emitter emitter, SerializationProperties params, PipelineConfiguration pipe) throws XPathException { Receiver target = emitter; Properties props = params.getProperties(); if (!"no".equals(props.getProperty(OutputKeys.INDENT))) { target = newXHTMLIndenter(target, props); } target = new NamespaceDifferencer(target, props); target = injectUnicodeNormalizer(params, target); target = injectCharacterMapExpander(params, target, true); String cdataElements = props.getProperty(OutputKeys.CDATA_SECTION_ELEMENTS); if (cdataElements != null && !cdataElements.isEmpty()) { target = newCDATAFilter(target, props); } if (SaxonOutputKeys.isXhtmlHtmlVersion5(props)) { target = addHtml5Component(target, props); } if (!"no".equals(props.getProperty(SaxonOutputKeys.ESCAPE_URI_ATTRIBUTES))) { target = newXHTMLURIEscaper(target, props); } if (!"no".equals(props.getProperty(SaxonOutputKeys.INCLUDE_CONTENT_TYPE))) { target = newXHTMLMetaTagAdjuster(target, props); } String attributeOrder = props.getProperty(SaxonOutputKeys.ATTRIBUTE_ORDER); if (attributeOrder != null && !attributeOrder.isEmpty()) { target = newAttributeSorter(target, props); } if (params.getValidationFactory() != null) { target = params.getValidationFactory().makeFilter(target); } return makeSequenceNormalizer(target, props); } /** * This method constructs a step in the output pipeline to perform namespace-related * tasks for HTML5 serialization. The default implementation adds a NamespaceReducer * and an XHTMLPrefixRemover * * @param target the Receiver that receives the output of this step * @param outputProperties the serialization properties * @return a new Receiver to perform HTML5-related namespace manipulation */ public Receiver addHtml5Component(Receiver target, Properties outputProperties) { target = new NamespaceReducer(target); target = new XHTMLPrefixRemover(target); return target; } /** * Create a serialization pipeline to implement the XML output method. This method is protected * so that it can be customized in a user-written SerializerFactory * * @param emitter the emitter at the end of the pipeline (created using the method {@link #newXMLEmitter} * @param params the serialization properties * @return a Receiver acting as the entry point to the serialization pipeline * @throws XPathException if a failure occurs */ protected SequenceReceiver createXMLSerializer( XMLEmitter emitter, SerializationProperties params) throws XPathException { Receiver target; Properties props = params.getProperties(); boolean canonical = "yes".equals(props.getProperty(SaxonOutputKeys.CANONICAL)); if ("yes".equals(props.getProperty(OutputKeys.INDENT)) || canonical) { target = newXMLIndenter(emitter, props); } else { target = emitter; } target = new NamespaceDifferencer(target, props); if ("1.0".equals(props.getProperty(OutputKeys.VERSION)) && config.getXMLVersion() == Configuration.XML11) { // Check result meets XML 1.0 constraints if configuration allows XML 1.1 input but // this result document must conform to 1.0 target = newXML10ContentChecker(target, props); } target = injectUnicodeNormalizer(params, target); if (!canonical) { target = injectCharacterMapExpander(params, target, true); } String cdataElements = props.getProperty(OutputKeys.CDATA_SECTION_ELEMENTS); if (cdataElements != null && !cdataElements.isEmpty() && !canonical) { target = newCDATAFilter(target, props); } if (canonical) { target = newAttributeSorter(target, props); target = newNamespaceSorter(target, props); } else { String attributeOrder = props.getProperty(SaxonOutputKeys.ATTRIBUTE_ORDER); if (attributeOrder != null && !attributeOrder.isEmpty()) { target = newAttributeSorter(target, props); } } if (params.getValidationFactory() != null) { target = params.getValidationFactory().makeFilter(target); } return makeSequenceNormalizer(target, props); } protected SequenceReceiver createSaxonSerializationMethod( String method, SerializationProperties params, PipelineConfiguration pipe, CharacterMapExpander characterMapExpander, ProxyReceiver normalizer, ExpandedStreamResult expandedResult, Result result) throws XPathException { throw new XPathException("Saxon serialization methods require Saxon-PE to be enabled"); } /** * Create a serialization pipeline to implement a user-defined output method. This method is protected * so that it can be customized in a user-written SerializerFactory * * @param method the name of the user-defined output method, as a QName in EQName format * (that is "Q{uri}local"). * @param props the serialization properties * @param pipe the pipeline configuration information * @return a Receiver acting as the entry point to the serialization pipeline * @throws XPathException if a failure occurs */ protected SequenceReceiver createUserDefinedOutputMethod(String method, Properties props, PipelineConfiguration pipe) throws XPathException { Receiver userReceiver; // See if this output method is recognized by the Configuration userReceiver = pipe.getConfiguration().makeEmitter(method, props); userReceiver.setPipelineConfiguration(pipe); if (userReceiver instanceof ContentHandlerProxy && "yes".equals(props.getProperty(SaxonOutputKeys.SUPPLY_SOURCE_LOCATOR))) { if (pipe.getConfiguration().isCompileWithTracing() && pipe.getController() != null) { pipe.getController().addTraceListener( ((ContentHandlerProxy) userReceiver).getTraceListener()); } else { throw new XPathException( "Cannot use saxon:supply-source-locator unless tracing was enabled at compile time", SaxonErrorCode.SXSE0002); } } return userReceiver instanceof SequenceReceiver ? (SequenceReceiver)userReceiver : new TreeReceiver(userReceiver); } protected Receiver injectCharacterMapExpander(SerializationProperties params, Receiver out, boolean useNullMarkers) throws XPathException { CharacterMapIndex charMapIndex = params.getCharacterMapIndex(); if (charMapIndex != null) { String useMaps = params.getProperties().getProperty(SaxonOutputKeys.USE_CHARACTER_MAPS); if (useMaps != null) { CharacterMapExpander expander = charMapIndex.makeCharacterMapExpander(useMaps, out, this); expander.setUseNullMarkers(useNullMarkers); return expander; } } return out; } protected Receiver injectUnicodeNormalizer(SerializationProperties params, Receiver out) throws XPathException { Properties props = params.getProperties(); String normForm = props.getProperty(SaxonOutputKeys.NORMALIZATION_FORM); if (normForm != null && !normForm.equals("none")) { return newUnicodeNormalizer(out, props); } return out; } /** * Create a ContentHandlerProxy. This method exists so that it can be overridden in a subclass. * * @return the newly created ContentHandlerProxy. */ protected ContentHandlerProxy newContentHandlerProxy() { return new ContentHandlerProxy(); } /** * Create an UncommittedSerializer. This method exists so that it can be overridden in a subclass. * * @param result the result destination * @param next the next receiver in the pipeline * @param params the serialization parameters * @return the newly created UncommittedSerializer. */ protected UncommittedSerializer newUncommittedSerializer(Result result, Receiver next, SerializationProperties params) { return new UncommittedSerializer(result, next, params); } /** * Create a new XML Emitter. This method exists so that it can be overridden in a subclass. * * @param properties the output properties * @return the newly created XML emitter. */ protected Emitter newXMLEmitter(Properties properties) { return new XMLEmitter(); } /** * Create a new HTML Emitter. This method exists so that it can be overridden in a subclass. * * @param properties the output properties * @return the newly created HTML emitter. */ protected Emitter newHTMLEmitter(Properties properties) { HTMLEmitter emitter; // Note, we recognize html-version even when running XSLT 2.0. if (SaxonOutputKeys.isHtmlVersion5(properties)) { emitter = new HTML50Emitter(); } else { emitter = new HTML40Emitter(); } return emitter; } /** * Create a new XHTML Emitter. This method exists so that it can be overridden in a subclass. * * @param properties the output properties * @return the newly created XHTML emitter. */ protected Emitter newXHTMLEmitter(Properties properties) { boolean is5 = SaxonOutputKeys.isXhtmlHtmlVersion5(properties); if (is5) { return new XHTML5Emitter(); } else { return new XHTML1Emitter(); } } /** * Add a filter to the text output method pipeline. This does nothing unless overridden * in a subclass * * @param next the next receiver (typically the TextEmitter) * @param properties the output properties * @return the receiver to be used in place of the "next" receiver * @throws XPathException if the operation fails */ public Receiver addTextOutputFilter(Receiver next, Properties properties) throws XPathException { return next; } /** * Create a new Text Emitter. This method exists so that it can be overridden in a subclass. * * @return the newly created text emitter. */ protected Emitter newTEXTEmitter() { return new TEXTEmitter(); } /** * Create a new XML Indenter. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created XML indenter. */ protected ProxyReceiver newXMLIndenter(XMLEmitter next, Properties outputProperties) { XMLIndenter r = new XMLIndenter(next); r.setOutputProperties(outputProperties); return r; } /** * Create a new HTML Indenter. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created HTML indenter. */ protected ProxyReceiver newHTMLIndenter(Receiver next, Properties outputProperties) { HTMLIndenter r = new HTMLIndenter(next, "html"); r.setOutputProperties(outputProperties); return r; } /** * Create a new XHTML Indenter. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created XHTML indenter. */ protected ProxyReceiver newXHTMLIndenter(Receiver next, Properties outputProperties) { String method = "xhtml"; String htmlVersion = outputProperties.getProperty("html-version"); if (htmlVersion != null && htmlVersion.startsWith("5")) { method="xhtml5"; } HTMLIndenter r = new HTMLIndenter(next, method); r.setOutputProperties(outputProperties); return r; } /** * Create a new XHTML MetaTagAdjuster, responsible for insertion, removal, or replacement of meta * elements. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created XHTML MetaTagAdjuster. */ protected MetaTagAdjuster newXHTMLMetaTagAdjuster(Receiver next, Properties outputProperties) { MetaTagAdjuster r = new MetaTagAdjuster(next); r.setIsXHTML(true); r.setOutputProperties(outputProperties); return r; } /** * Create a new XHTML MetaTagAdjuster, responsible for insertion, removal, or replacement of meta * elements. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created HTML MetaTagAdjuster. */ protected MetaTagAdjuster newHTMLMetaTagAdjuster(Receiver next, Properties outputProperties) { MetaTagAdjuster r = new MetaTagAdjuster(next); r.setIsXHTML(false); r.setOutputProperties(outputProperties); return r; } /** * Create a new HTML URI Escaper, responsible for percent-encoding of URIs in * HTML output documents. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created HTML URI escaper. */ protected ProxyReceiver newHTMLURIEscaper(Receiver next, Properties outputProperties) { return new HTMLURIEscaper(next); } /** * Create a new XHTML URI Escaper, responsible for percent-encoding of URIs in * HTML output documents. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created HTML URI escaper. */ protected ProxyReceiver newXHTMLURIEscaper(Receiver next, Properties outputProperties) { return new XHTMLURIEscaper(next); } /** * Create a new CDATA Filter, responsible for insertion of CDATA sections where required. * This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created CDATA filter. * @throws net.sf.saxon.trans.XPathException * if an error occurs */ protected ProxyReceiver newCDATAFilter(Receiver next, Properties outputProperties) throws XPathException { CDATAFilter r = new CDATAFilter(next); r.setOutputProperties(outputProperties); return r; } /** * Create a new AttributeSorter, responsible for sorting of attributes into a specified order. * This method exists so that it can be overridden in a subclass. The Saxon-HE version of * this method returns the supplied receiver unchanged (attribute sorting is not supported * in Saxon-HE). The AttributeSorter handles both sorting of attributes into a user-specified * order (saxon:attribute-order) and sorting into C14N order (saxon:canonical). * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created filter. */ protected Receiver newAttributeSorter(Receiver next, Properties outputProperties) throws XPathException { return next; } /** * Create a new NamespaceSorter, responsible for sorting of namespaces into a specified order. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created filter. */ protected Receiver newNamespaceSorter(Receiver next, Properties outputProperties) throws XPathException { return next; } /** * Create a new XML 1.0 content checker, responsible for checking that the output conforms to * XML 1.0 rules (this is used only if the Configuration supports XML 1.1 but the specific output * file requires XML 1.0). This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created XML 1.0 content checker. */ protected ProxyReceiver newXML10ContentChecker(Receiver next, Properties outputProperties) { return new XML10ContentChecker(next); } /** * Create a Unicode Normalizer. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @param outputProperties the serialization parameters * @return the newly created Unicode normalizer. * @throws net.sf.saxon.trans.XPathException * if an error occurs */ protected ProxyReceiver newUnicodeNormalizer(Receiver next, Properties outputProperties) throws XPathException { String normForm = outputProperties.getProperty(SaxonOutputKeys.NORMALIZATION_FORM); return new UnicodeNormalizer(normForm, next); } /** * Create a new CharacterMapExpander. This method exists so that it can be overridden in a subclass. * * @param next the next receiver in the pipeline * @return the newly created CharacterMapExpander. */ public CharacterMapExpander newCharacterMapExpander(Receiver next) { return new CharacterMapExpander(next); } /** * Prepare another stylesheet to handle the output of this one. *

This method is intended for internal use, to support the * saxon:next-in-chain extension.

* * @param pipe the current transformation * @param href URI of the next stylesheet to be applied * @param baseURI base URI for resolving href if it's a relative * URI * @param result the output destination of the current stylesheet * @return a replacement destination for the current stylesheet * @throws XPathException if any dynamic error occurs */ public SequenceReceiver prepareNextStylesheet(PipelineConfiguration pipe, String href, String baseURI, Result result) throws XPathException { pipe.getConfiguration().checkLicensedFeature(Configuration.LicenseFeature.PROFESSIONAL_EDITION, "saxon:next-in-chain", -1); return null; } /** * Get a SequenceWrapper, a class that serializes an XDM sequence with full annotation of item types, node kinds, * etc. There are variants for Saxon-HE and Saxon-PE * @param destination the place where the wrapped sequence will be sent * @return the new SequenceWrapper */ public SequenceWrapper newSequenceWrapper(Receiver destination) { return new SequenceWrapper(destination); } /** * Check that a supplied output property is valid, and normalize the value (specifically in the case of boolean * values where yes|true|1 are normalized to "yes", and no|false|0 are normalized to "no"). Clark names in the * value ({uri}local) are normalized to EQNames (Q{uri}local) * * @param key the name of the property, in Clark format * @param value the value of the property. This may be set to null, in which case no validation takes place. * The value must be in JAXP format, that is, with lexical QNames expanded to either EQNames or * Clark names. * @return normalized value of the property, or null if the supplied value is null * @throws XPathException if the property name or value is invalid */ public String checkOutputProperty(String key, String value) throws XPathException { if (!key.startsWith("{")) { switch (key) { case SaxonOutputKeys.ALLOW_DUPLICATE_NAMES: case SaxonOutputKeys.ESCAPE_URI_ATTRIBUTES: case SaxonOutputKeys.INCLUDE_CONTENT_TYPE: case OutputKeys.INDENT: case OutputKeys.OMIT_XML_DECLARATION: case SaxonOutputKeys.UNDECLARE_PREFIXES: if (value != null) { value = checkYesOrNo(key, value); } break; case SaxonOutputKeys.BUILD_TREE: if (value != null) { value = checkYesOrNo(key, value); } break; case SaxonOutputKeys.BYTE_ORDER_MARK: if (value != null) { value = checkYesOrNo(key, value); } break; case OutputKeys.CDATA_SECTION_ELEMENTS: case SaxonOutputKeys.SUPPRESS_INDENTATION: case SaxonOutputKeys.USE_CHARACTER_MAPS: if (value != null) { value = checkListOfEQNames(key, value); } break; case OutputKeys.DOCTYPE_PUBLIC: if (value != null) { checkPublicIdentifier(value); } break; case OutputKeys.DOCTYPE_SYSTEM: if (value != null) { checkSystemIdentifier(value); } break; case OutputKeys.ENCODING: // no constraints break; case SaxonOutputKeys.HTML_VERSION: if (value != null) { checkDecimal(key, value); } break; case SaxonOutputKeys.ITEM_SEPARATOR: // no checking needed break; case OutputKeys.METHOD: case SaxonOutputKeys.JSON_NODE_OUTPUT_METHOD: if (value != null) { value = checkMethod(key, value); } break; case OutputKeys.MEDIA_TYPE: // no constraints break; case SaxonOutputKeys.NORMALIZATION_FORM: if (value != null) { checkNormalizationForm(value); } break; case SaxonOutputKeys.PARAMETER_DOCUMENT: // no checking break; case OutputKeys.STANDALONE: if (value != null && !value.equals("omit")) { value = checkYesOrNo(key, value); } break; case OutputKeys.VERSION: // no constraints break; default: throw new XPathException("Unknown serialization parameter " + Err.wrap(key), "XQST0109"); } } else if (key.startsWith("{http://saxon.sf.net/}")) { // Some Saxon serialization parameters are recognized in HE if they are used for internal purposes switch (key) { case SaxonOutputKeys.STYLESHEET_VERSION: // return break; case SaxonOutputKeys.PARAMETER_DOCUMENT_BASE_URI: // return break; case SaxonOutputKeys.SUPPLY_SOURCE_LOCATOR: case SaxonOutputKeys.UNFAILING: if (value != null) { value = checkYesOrNo(key, value); } break; default: throw new XPathException("Serialization parameter " + Err.wrap(key, Err.EQNAME) + " is not available in Saxon-HE", "XQST0109"); } } else { //return; } return value; } protected static String checkYesOrNo(String key, String value) throws XPathException { if ("yes".equals(value) || "true".equals(value) || "1".equals(value)) { return "yes"; } else if ("no".equals(value) || "false".equals(value) || "0".equals(value)) { return "no"; } else { throw new XPathException("Serialization parameter " + Err.wrap(key) + " must have the value yes|no, true|false, or 1|0", "SEPM0016"); } } private String checkMethod(String key, String value) throws XPathException { if (!"xml".equals(value) && !"html".equals(value) && !"xhtml".equals(value) && !"text".equals(value)) { if (!SaxonOutputKeys.JSON_NODE_OUTPUT_METHOD.equals(key) && ("json".equals(value) || "adaptive".equals(value))) { return value; } if (value.startsWith("{")) { value = "Q" + value; } if (isValidEQName(value)) { checkExtensions(value); } else { throw new XPathException("Invalid value (" + value + ") for serialization method: " + "must be xml|html|xhtml|text|json|adaptive, or a QName in 'Q{uri}local' form", "SEPM0016"); } } return value; } private static void checkNormalizationForm(String value) throws XPathException { if (!NameChecker.isValidNmtoken(StringView.of(value))) { throw new XPathException("Invalid value for normalization-form: " + "must be NFC, NFD, NFKC, NFKD, fully-normalized, or none", "SEPM0016"); } } private static boolean isValidEQName(String value) { Objects.requireNonNull(value); if (value.isEmpty() || !value.startsWith("Q{")) { return false; } int closer = value.indexOf('}', 2); return closer >= 2 && closer != value.length() - 1 && NameChecker.isValidNCName(value.substring(closer + 1)); } private static boolean isValidClarkName(/*@NotNull*/ String value) { if (value.startsWith("{")) { return isValidEQName("Q" + value); } else { return isValidEQName("Q{}" + value); } } protected static void checkNonNegativeInteger(String key, String value) throws XPathException { try { int n = Integer.parseInt(value); if (n < 0) { throw new XPathException("Value of " + Err.wrap(key) + " must be a non-negative integer", "SEPM0016"); } } catch (NumberFormatException err) { throw new XPathException("Value of " + Err.wrap(key) + " must be a non-negative integer", "SEPM0016"); } } private static void checkDecimal(String key, String value) throws XPathException { if (!BigDecimalValue.castableAsDecimal(value)) { throw new XPathException("Value of " + Err.wrap(key) + " must be a decimal number", "SEPM0016"); } } protected static String checkListOfEQNames(String key, String value) throws XPathException { Whitespace.Tokenizer tokenizer = new Whitespace.Tokenizer(StringView.of(value).tidy()); StringBuilder builder = new StringBuilder(); StringValue tok; while ((tok = tokenizer.next()) != null) { String s = tok.getStringValue(); if (isValidEQName(s) || NameChecker.isValidNCName(tok.codePoints())) { builder.append(s); } else if (isValidClarkName(s)) { if (s.startsWith("{")) { builder.append("Q").append(s); } else { builder.append("Q{}").append(s); } } else { throw new XPathException("Value of " + Err.wrap(key) + " must be a list of QNames in 'Q{uri}local' notation", "SEPM0016"); } builder.append(" "); } return builder.toString(); } protected static String checkListOfEQNamesAllowingStar(String key, String value) throws XPathException { Whitespace.Tokenizer tokenizer = new Whitespace.Tokenizer(StringView.of(value).tidy()); StringBuilder builder = new StringBuilder(); StringValue tok; while ((tok = tokenizer.next()) != null) { String s = tok.getStringValue(); if ("*".equals(s) || isValidEQName(s) || NameChecker.isValidNCName(s)) { builder.append(s); } else if (isValidClarkName(s)) { if (s.startsWith("{")) { builder.append("Q").append(s); } else { builder.append("Q{}").append(s); } } else { throw new XPathException("Value of " + Err.wrap(key) + " must be a list of QNames in 'Q{uri}local' notation", "SEPM0016"); } builder.append(" "); } return builder.toString().trim(); } private static final Pattern publicIdPattern = Pattern.compile("^[\\s\\r\\na-zA-Z0-9\\-'()+,./:=?;!*#@$_%]*$"); private static void checkPublicIdentifier(String value) throws XPathException { if (!publicIdPattern.matcher(value).matches()) { throw new XPathException("Invalid character in doctype-public parameter", "SEPM0016"); } } private static void checkSystemIdentifier(/*@NotNull*/ String value) throws XPathException { if (value.contains("'") && value.contains("\"")) { throw new XPathException("The doctype-system parameter must not contain both an apostrophe and a quotation mark", "SEPM0016"); } } protected void checkExtensions(String key /*@Nullable*/) throws XPathException { throw new XPathException("Serialization property " + Err.wrap(key, Err.EQNAME) + " is not available in Saxon-HE"); } protected Comparator getPropertySorter(String sortSpecification) throws XPathException { throw new XPathException("Serialization property saxon:property-order is not available in Saxon-HE"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy