Project

Profile

Help

Upgrade from 9.5 to 9.9 now creates output file regardless of whether or not stylesheet writes to file

Added by Edward Porter about 5 years ago

We recently upgraded from Saxon 9.5 to Saxon 9.9. Many of our XSL stylesheets have root templates in which several xsl:result-document are used, but nothing is ever output to the root template itself.

In regression testing, we've discovered that URIs provided as dummy paths via our extension of XsltTransformer Java API, which did not previously get created, now do so regardless of if anything is ever output to the root template. I suspect that between 9.5 and 9.9 the underlying transformation code was changed such that now the makeDestination method is called prior to the transformation rather than at some point after the transformation has started.

Could it be confirmed that this is the case, and if so, is there some way to configure the transformer to execute as before, only creating the provided destination file if there is actually something to be written to it?


Replies (3)

Please register to reply

RE: Upgrade from 9.5 to 9.9 now creates output file regardless of whether or not stylesheet writes to file - Added by Michael Kay about 5 years ago

The XSLT 3.0 spec has changed in this area, with many more options for returning and processing results, and the new model made if very difficult to retain the existing behaviour. The spec now talks of the "raw result" of a transformation, which is the sequence returned by execution of the entry template; this result always exists, even if it is an empty sequence. You then have a choice what to do with this raw result; typically you will put it through sequence normalization, wrap a document node around it, and serialize it to a file in filestore, which delivers an empty file if the raw result was an empty sequence. Avoiding the file write conditionally if (a) the document is empty and (b) one or more xsl:result-document instructions were executed, which is what XSLT 2.0 did, gets very messy.

We found (during 9.9 development and testing) that implementing the last few details of the XSLT 3.0 spec for invoking transformations required a fairly major clean-up of the interface between the Transformer and the Destination, and that made it hard to replicate all the quirkiness of the previous behaviour.

The Xslt30Transformer API gives you full control of all the options, and basically leaves it up to you to control what happens from the application level.

RE: Upgrade from 9.5 to 9.9 now creates output file regardless of whether or not stylesheet writes to file - Added by Michael Kay about 5 years ago

This is what the spec actually says (in §2.3.6.2):

In previous versions of this specification it was stated that when the raw result of the initial template or function is an empty sequence, a result tree should be produced if and only if the transformation generates no secondary results (that is, if it does not invoke xsl:result-document). This provision is most likely to have a noticeable effect if the transformation produces serialized results, and these results are written to persistent storage: the effect is then that a transformation producing an empty principal result will overwrite any existing content at the base output URI location if and only if the transformation produces no other output. Processor APIs offering backwards compatibility with earlier versions of XSLT must respect this behavior, but there is no requirement for new processor APIs to do so.

I think we need to see exactly what API calls you are using.

RE: Upgrade from 9.5 to 9.9 now creates output file regardless of whether or not stylesheet writes to file - Added by Edward Porter about 5 years ago

For our purposes, it might be easier to simply delete the dummy files after the transform, but in any case, here's what we're doing:

prepareForTransform creates a File object using the dummy file path, but it does not write to the file system. That file's passed to the doTransforms method.

    protected void startTransform(String xml, URL[] xslUrl, String outfile,
            IdResolutionContext context, String format, TransformProgressMonitor monitor)
            throws TransformerConfigurationException, TransformerException, TransformCanceledException {
        if (monitor != null) {
            monitor.checkCanceled();
        }
        File results = prepareForTransform(xml, xslUrl, outfile, context, format);
        doTransforms(xml, xslUrl, results, context, format);
    }

doTransforms calls doRenderingTransform:

    private void doRenderingTransform(String xml, InputSource source, URL xslUrl,
            File results, IdResolutionContext context, String format)
            throws TransformerException {
        log.debug("******* TransformEngine Rendering Pass," + xslUrl.toString()
                + " *******");
        // log.debug("XMLParserConfiguration "
        // +
        // System.getProperty("org.apache.xerces.xni.parser.XMLParserConfiguration"));
        XMLReader reader = null;
        StreamResult streamResult = null;
        try {
            SAXTransformerFactory stfactory = createTransformFactory();
            errorListener = new ErrorListenerImpl(null);
            stfactory.setErrorListener(errorListener);
            StreamSource xslsource = new StreamSource(xslUrl.getPath());
            Transformer transMain = stfactory.newTransformer(xslsource);
            setXslMessageWriter(transMain);
            setSaxonControllerHandle(transMain);
            setExternallySuppliedStylesheetParameters(transMain);
            setInternallyCreatedXisStylesheetParameters(transMain);
            streamResult = new StreamResult(results);
            reader = createXMLReader(context, format);
            reader.setErrorHandler(errorListener);
            source.setEncoding(TransformParameters.PREF_ENCODING);
            
            URL input;
            if (SasXisDocXmlId.isValidProtocol(xml)) {
                input = new URL(xml);
            }
            else {
                URL[] array = setupProtocol(new String[] {xml});
                input = array[0];
            }
            String s = input.toString();
            source.setSystemId(s);
            log.debug("************* SAXON TransformEngine::Begin Rendering Transform *************");
            // reader.parse(source);
            transMain.transform(new SAXSource(reader, source), streamResult);
            // log.debug("The content transform results are in " + results);
            log.debug("************* SAXON TransformEngine::End Rendering Transform *************");
        }
        catch (Throwable e) {
            log.warn("************* SAXON TransformEngine::End Rendering Transform FAIL ******** "
                    + e);
            handleTransformExceptionMsg(e, writer);
            transformLogger.warn(errorListener.getErrors());
            throw new TransformerException(e + " :: Details " + errorListener.getErrors());
        }
        finally {
            if (streamResult != null) {
                OutputStream outputStream = streamResult.getOutputStream();
                if (outputStream != null) {
                    try {
                        streamResult.getOutputStream().flush();
                        streamResult.getOutputStream().close();
                    }
                    catch (IOException e) {
                        throw new TransformerException(
                                "Unable to close the Transform Output Stream", e);
                    }
                }
            }
            reader = null;
        }
    }

We actually have a Saxon9Api version of the same method:

    private void doRenderingTransformUsingSaxon9Api(String xml, URL[] xslUrl, File results,
            IdResolutionContext context, String format) throws TransformerException {
        log.debug("******* TransformEngine Rendering Pass," + xslUrl.toString() + " *******");
        try {
            log.debug("************* SAXON TransformEngine::Begin Rendering Transform *************");

            errorListener = new ErrorListenerImpl(null);

            File[] styleSheets = new File[xslUrl.length];
            int counter = 0;
            for (URL u : xslUrl) {
                  URI uri = u.toURI();
                  if(uri.getAuthority() != null && uri.getAuthority().length() > 0) {
                      // Hack for UNC Paths
                      uri = (new URL("file://" + u.toString().substring("file:".length()))).toURI();
                  }
                styleSheets[counter++] = new File(uri);
            }

            HashMap<String, Object> stylesheetParameters = getInternallyCreatedXisStylesheetParameters();
            stylesheetParameters.putAll(getSylesheetParameters());
            stylesheetParameters.putAll(getCommonXisStylesheetParameters(xml, context, format));
            //for (String key : stylesheetParameters.keySet()) {
            //    System.out.println(key + "->" + stylesheetParameters.get(key));
            //}

            SaxonProcessor.runTransform(styleSheets, new File(xml), new SaxonSourceParseOptions(false,
                    false), results, stylesheetParameters, new SaxonMessageListener(writer), errorListener);

            log.debug("************* SAXON TransformEngine::End Rendering Transform *************");
        }
        catch (Throwable e) {
            log.warn("************* SAXON TransformEngine::End Rendering Transform FAIL ********");
            handleTransformExceptionMsg(e);
            throw new TransformerException(e + " :: Details " + errorListener.getErrors());
        }
    }

If you have suggestions for refactoring or sample code leveraging the Xslt30Transformer to suppress output when there's a result-document, that would be helpful.

    (1-3/3)

    Please register to reply