Support #4267
Updated by Michael Kay over 5 years ago
I am the same person as Rick Vlaming, but I don't know which email I used previously when reporting an issue last year. Nevertheless... I have an issue with getting the serialized result from a xslt-transformation. I don't know if this is really an issue or just designed that way. Or perhaps there is an alternative. We are in the transition from using JAXP to Saxon S9Api and using the licensed saxon-ee 9.9.1.4 java edition. Also we are planning to use xslt-chaining where the result of one xslt is going to be used in another xslt. This chain can consist of multiple xslt-transformations (say 10 transformations). I have created a reusable method which takes the xml (as a XdmNode) and the xslt and which gives the result as a XdmNode. On certain points in the chain the result has to be serialized. At such a point I was planning to serialize the XdmNode to the database. At first that seems to be working, but then I came across a xslt which has the omit-xml-declaration to "yes". The unittest failed because the serialization did include the xml-declaration. The application is depending on that omit because the result is put as a payload in another xml. I discovered that when using a TeeDestination which includes a serializer destination the xml-declaration is omitted when getting the result from stringwriter in de serializer destination. However I would like to have the reusable method to return the result as a XdmNode because when we do the chaining only a few times we have to serialize. So most of the time the serialisation within the transform is not needed, so I would like to spare that effort for the transform method. As an example to show the problem I did a small rewrite on the reusable methode. In stead of return the result as an XdmNode the example is returning the destination which I added to the input. The first test "omitXmlDeclarationXdmNodeDestination" does the transform with a XdmDestination and the serialization (method used also included) on the XdmNode of de XdmDestionation. The asserting on output of the serialization fails, because the xml-declaration is in the output. The second test "omitXmlDeclarationTeeDestination" does the transform with a TeeDestionation. The asserting on de stringWriter in that TeeDestination is succesfull, the xml-declaration is not in the result. I did put in an exta assert on de XdmNode, also from the teedestionation. Then the serializing is in fact as in the previous test and that is also failing. ~~~ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import org.junit.Test; import net.sf.saxon.s9api.Destination; import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.Serializer; import net.sf.saxon.s9api.TeeDestination; import net.sf.saxon.s9api.XdmDestination; import net.sf.saxon.s9api.XdmNode; import net.sf.saxon.s9api.Xslt30Transformer; import net.sf.saxon.s9api.XsltCompiler; import nl.belastingdienst.vmg.fabriek.common.domain.SharedException; import nl.belastingdienst.vmg.fabriek.common.util.SaxonS9ApiUtil; import nl.belastingdienst.vmg.fabriek.common.util.XsltTest; public class xslt9_9JavaTest extends XsltTest { private static final String PATH_TO_TEST_FILES = "src/test/resources/transformer/xsltjava/"; private static final String XML_DECLARATION_TAG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; @Test public void omitXmlDeclarationXdmNodeDestination() throws IOException { String omitInputXml = getFileContent(PATH_TO_TEST_FILES + "omit-input.xml"); XdmNode omitInputXdmNode = SaxonS9ApiUtil.getXdmNode(omitInputXml); InputStream omitXslt = getInputStream(PATH_TO_TEST_FILES + "omit.xslt"); XdmDestination destination = new XdmDestination(); transform(omitInputXdmNode, omitXslt, destination); String result = getSerializedXdmNode(destination.getXdmNode()); System.out.println(result); assertThat("assertXdmNode", result.startsWith(XML_DECLARATION_TAG), is(false)); } @Test public void omitXmlDeclarationTeeDestination() throws IOException { String omitInputXml = getFileContent(PATH_TO_TEST_FILES + "omit-input.xml"); XdmNode omitInputXdmNode = SaxonS9ApiUtil.getXdmNode(omitInputXml); InputStream omitXslt = getInputStream(PATH_TO_TEST_FILES + "omit.xslt"); XdmDestination resultXdmDestination = new XdmDestination(); StringWriter resultStringWriter = new StringWriter(); Serializer resultSerializer = omitInputXdmNode.getProcessor().newSerializer(resultStringWriter); TeeDestination teeDestination = new TeeDestination(resultXdmDestination, resultSerializer); transform(omitInputXdmNode, omitXslt, teeDestination); String transformed = resultStringWriter.toString(); System.out.println(transformed); assertThat("assertStringWriter", transformed.startsWith(XML_DECLARATION_TAG), is(false)); transformed = getSerializedXdmNode(resultXdmDestination.getXdmNode()); System.out.println(transformed); assertThat("assertXdmNode", transformed.startsWith(XML_DECLARATION_TAG), is(false)); } private static String getSerializedXdmNode(XdmNode xdmNode) { Processor processor = new Processor(true); Serializer serializer = processor.newSerializer(); // no omit-property here because that's in the xslt. Sometimes we use omit yes, sometimes no in xslt. try { return serializer.serializeNodeToString(xdmNode); } catch (SaxonApiException e) { throw new SharedException("Fout bij serializeren XdmNode"); } } private void transform(XdmNode inputXml, InputStream xsltCode, Destination destination) { Source xsltSource = new StreamSource(xsltCode); Processor processor = inputXml.getProcessor(); XsltCompiler xsltCompiler = processor.newXsltCompiler(); try { Xslt30Transformer transformer = xsltCompiler.compile(xsltSource).load30(); transformer.setGlobalContextItem(inputXml); transformer.applyTemplates(inputXml, destination); } catch (SaxonApiException e) { throw new SharedException("Transformatie-fout"); } } private XdmNode getXdmNode(String xml) { InputStream xmlInputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); try { return new Processor(true).newDocumentBuilder().build(new StreamSource(xmlInputStream)); } catch (SaxonApiException e) { throw new SharedException("Fout bij aanmaken XdmNode"); } } private InputStream getInputStream(String filename) throws IOException { String fileContent = getFileContent(filename); return new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)); } @Override protected String getXsdFileName() { // TODO: implement throw new UnsupportedOperationException("TODO: implement method getXsdFileName() --> String"); } } ~~~ omit-input.xml ~~~ <?xml version="1.0" encoding="UTF-8"?> <test></test> ~~~ omit.xstl ~~~ <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:math="http://www.w3.org/2005/xpath-functions/math" xmlns:array="http://www.w3.org/2005/xpath-functions/array" xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:err="http://www.w3.org/2005/xqt-errors" exclude-result-prefixes="array fn map math xhtml xs err" > <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet> ~~~ Console-output "omitXmlDeclarationXdmNodeDestination": ~~~ output-1: <?xml version="1.0" encoding="UTF-8"?><test/> java.lang.AssertionError: assertXdmNode Expected: is <false> but: was <true> Expected :is <false> Actual :<true> ~~~ Console-output "omitXmlDeclarationTeeDestination": ~~~ output-1: <test/> output-2: <?xml version="1.0" encoding="UTF-8"?><test/> java.lang.AssertionError: assertXdmNode Expected: is <false> but: was <true> ~~~