Project

Profile

Help

Bug #5886

closed

Serializer of s9api in Saxon 11 for json and adaptive output method seems to close underlying stream while that doesn't happen for output method xml

Added by Martin Honnen over 1 year ago. Updated 5 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Category:
s9api API
Sprint/Milestone:
Start date:
2023-02-22
Due date:
% Done:

100%

Estimated time:
Legacy ID:
Applies to branch:
11
Fix Committed on Branch:
11
Fixed in Maintenance Release:
Platforms:
.NET, Java

Description

Based on my findings in https://saxonica.plan.io/boards/3/topics/9295 and what I fill explain further it appears Saxon 11 has a bug where the use of an s9api Serializer over an OutputStream as the Destination of an Xslt30Transformer's callTemplate or applyTemplates closes the underlying stream for output methods json and adaptive while that doesn't happen (as I think it is supposed to do) for output method xml.

So for some sample code like

package org.example;

import net.sf.saxon.s9api.*;

import javax.xml.transform.stream.StreamSource;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;

public class Main {
    public static void main(String[] args) throws IOException, SaxonApiException {
        Processor processor = new Processor(false);

        String xsltXmlOutput = "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
                "  version=\"3.0\"\n" +
                "  xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n" +
                "  exclude-result-prefixes=\"#all\"\n" +
                "  expand-text=\"yes\">\n" +
                "\n" +
                "  <xsl:output method=\"xml\" indent=\"yes\"/>\n" +
                "\n" +
                "  <xsl:template match=\"/\" name=\"xsl:initial-template\">\n" +
                "    <test>Run at {current-dateTime()} with {system-property('xsl:product-name')} {system-property('xsl:product-version')}</test>\n" +
                "    <xsl:comment>Run at {current-dateTime()} with {system-property('xsl:product-name')} {system-property('xsl:product-version')}</xsl:comment>\n" +
                "  </xsl:template>\n" +
                "  \n" +
                "</xsl:stylesheet>";

        XsltCompiler xsltCompiler = processor.newXsltCompiler();

        XsltExecutable xsltExecutable = xsltCompiler.compile(new StreamSource(new StringReader(xsltXmlOutput)));

        Xslt30Transformer xslt30Transformer = xsltExecutable.load30();

        FileOutputStream stream = new FileOutputStream("xml-fragment-result.xml");

        xslt30Transformer.callTemplate(null, processor.newSerializer(stream));

        xslt30Transformer = xsltExecutable.load30();

        xslt30Transformer.callTemplate(null, processor.newSerializer(stream));

        stream.close();

        String xsltJsonOutput = "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
                "  version=\"3.0\"\n" +
                "  xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n" +
                "  exclude-result-prefixes=\"#all\"\n" +
                "  expand-text=\"yes\">\n" +
                "  \n" +
                "  <xsl:output method=\"json\" indent=\"yes\"/>\n" +
                "\n" +
                "  <xsl:template match=\"/\" name=\"xsl:initial-template\">\n" +
                "    <xsl:sequence\n" +
                "      select=\"map { 'tested-at' : current-dateTime(), 'with' : system-property('xsl:product-name') || ' ' || system-property('xsl:product-version') }\"/>\n" +
                "  </xsl:template>\n" +
                "  \n" +
                "</xsl:stylesheet>";

        xsltCompiler = processor.newXsltCompiler();

        xsltExecutable = xsltCompiler.compile(new StreamSource(new StringReader(xsltJsonOutput)));

        xslt30Transformer = xsltExecutable.load30();

        stream = new FileOutputStream("JsonResults.json");

        xslt30Transformer.callTemplate(null, processor.newSerializer(stream));

        xslt30Transformer = xsltExecutable.load30();

        xslt30Transformer.callTemplate(null, processor.newSerializer(stream));

        stream.close();

        String xsltAdaptiveOutput = "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
                "  version=\"3.0\"\n" +
                "  xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n" +
                "  exclude-result-prefixes=\"#all\"\n" +
                "  expand-text=\"yes\">\n" +
                "  \n" +
                "  <xsl:output method=\"adaptive\" indent=\"yes\"/>\n" +
                "\n" +
                "  <xsl:template match=\"/\" name=\"xsl:initial-template\">\n" +
                "    <xsl:sequence\n" +
                "      select=\"map { 'tested-at' : current-dateTime(), 'with' : system-property('xsl:product-name') || ' ' || system-property('xsl:product-version') }\"/>\n" +
                "  </xsl:template>\n" +
                "  \n" +
                "</xsl:stylesheet>";

        xsltExecutable = xsltCompiler.compile(new StreamSource(new StringReader(xsltAdaptiveOutput)));

        xslt30Transformer = xsltExecutable.load30();

        stream = new FileOutputStream("AdaptiveResults.txt");

        xslt30Transformer.callTemplate(null, processor.newSerializer(stream));

        xslt30Transformer = xsltExecutable.load30();

        xslt30Transformer.callTemplate(null, processor.newSerializer(stream));

        stream.close();
    }
}

the XML result file xml-fragment-result.xml has the output of the two transformations e.g.

<?xml version="1.0" encoding="UTF-8"?>
<test>Run at 2023-02-22T14:18:42.377+01:00 with SAXON HE 11.5</test>
<!--Run at 2023-02-22T14:18:42.377+01:00 with SAXON HE 11.5-->
<?xml version="1.0" encoding="UTF-8"?>
<test>Run at 2023-02-22T14:18:42.379+01:00 with SAXON HE 11.5</test>
<!--Run at 2023-02-22T14:18:42.379+01:00 with SAXON HE 11.5-->

while for the JsonResults.json and the AdaptiveResults.txt the output only contains the result of the first transformation e.g.

{ "with":"SAXON HE 11.5", "tested-at":"2023-02-22T14:18:42.394+01:00" }

and e.g.

map{"with":"SAXON HE 11.5","tested-at":xs:dateTime("2023-02-22T14:18:42.401+01:00")}

And for the adaptive sample the problem also manifests itself as an error

Error 
   java.io.IOException: Stream Closed: Stream Closed
Exception in thread "main" net.sf.saxon.s9api.SaxonApiException: java.io.IOException: Stream Closed
	at net.sf.saxon.s9api.Xslt30Transformer.callTemplate(Xslt30Transformer.java:493)
	at org.example.Main.main(Main.java:101)
Caused by: net.sf.saxon.trans.XPathException: java.io.IOException: Stream Closed
	at net.sf.saxon.serialize.AdaptiveEmitter.close(AdaptiveEmitter.java:332)
	at net.sf.saxon.event.ProxyReceiver.close(ProxyReceiver.java:104)
	at net.sf.saxon.event.CloseNotifier.close(CloseNotifier.java:36)
	at net.sf.saxon.event.ComplexContentOutputter.close(ComplexContentOutputter.java:739)
	at net.sf.saxon.trans.XsltController.callTemplate(XsltController.java:879)
	at net.sf.saxon.s9api.Xslt30Transformer.callTemplate(Xslt30Transformer.java:485)
	... 1 more
Caused by: java.io.IOException: Stream Closed
	at java.io.FileOutputStream.writeBytes(Native Method)
	at java.io.FileOutputStream.write(FileOutputStream.java:326)
	at net.sf.saxon.serialize.UTF8Writer._flushBuffer(UTF8Writer.java:577)
	at net.sf.saxon.serialize.UTF8Writer.close(UTF8Writer.java:96)
	at net.sf.saxon.serialize.AdaptiveEmitter.close(AdaptiveEmitter.java:330)
	... 6 more

The problem is particularly crippling if you use a Serializer over System.out for some transformation output as at that point it seems after the first transformation to System.out with json output method any other attempt to write/print to it is swallowed, as occurs in https://github.com/martin-honnen/SaxonJson2JsonExample1.

The problem doesn't occur with Saxon 12.

Please register to edit this issue

Also available in: Atom PDF