Bug #3631
closedsaxon:next-in-chain does not work with Saxon 9.8.0.7 if I try to get an output property before I run the transformation
100%
Description
If I run the transformation with Saxon 9.8.0.7 using the code below, I get an exception:
The problem seems to be generated because I want to obtain the encoding from the transformer before I run the transformation. If I remove the line that gets the encoding "transformer.getOutputProperty("encoding");", the transformation works
<pre><code class="java">
String sourceID = "test.xml";
String xslID = "main.xsl";
// Create a transform factory instance.
TransformerFactoryImpl tfactory = new ProfessionalTransformerFactory();
// Create a transformer for the stylesheet.
Transformer transformer = tfactory.newTransformer(new StreamSource(xslID));
// Obtain the encoding
*String transformerEncoding = transformer.getOutputProperty("encoding");*
// Transform the source XML to System.out.
transformer.transform(new StreamSource(sourceID), new StreamResult(System.out));
Files
Updated by Michael Kay almost 7 years ago
Sigh: The JAXP documentation for methods like getOutputProperty() keeps changing to describe the quirks of the Xalan implementation, and it's hard to remain bug-compatible.
The TransformerImpl (actually the IdentityTransformer from which it inherits) has a field localOutputProperties which is initially empty. It's initialized by a number of methods:
-
getOutputProperty() initializes it to contain the stylesheet-defined output properties, and then returns the value of the selected property.
-
getOutputProperties() initializes it to contain a new Properties object backed by the stylesheet-defined output properties, and then returns yet another new Properties object copied from this.
-
getLocalOutputProperties() initializes it to an empty Properties object.
-
setOutputProperties() initializes it to contain the result of getOutputProperties().
-
setOutputProperty() initializes it to contain the result of getOutputProperties().
The transform() method first calls createDestination() which is creating a (s9api) Serializer whose properties are set individually from the result of calling getLocalOutputProperties(), If getOutputProperty() has been called, this contains the stylesheet-defined output properties; if not, it is an empty Properties object. It then calls XsltTransformer.setDestination(), which, if this Destination is a Serializer, passes it the stylesheet-defined output properties (but handles next-in-chain specially).
So this is all pretty messy, and the problem is that making any changes has a serious risk of breaking something.
I think that the localOutputProperties field should be a Properties object in which the front layer contains properties explicitly set using setOutputProperty() or setOutputProperties(), backed by a Properties object containing stylesheet-defined output properties.
Updated by Michael Kay almost 7 years ago
- Status changed from New to In Progress
The immediate effect of changing all the paths where localOutputProperties is initialized to be consistent with each other, is that the error (saxon:next-in-chain is not a serialization property) now occurs on both paths, whether we call getProperty() or not. That's to be expected.
We now need to fix the way that TransformerImpl.transform() is passing serialization properties to the Serializer. This is currently done in two phases: first TransformerImpl.makeDestination() copies over all the output properties found in localOutputProperties, then XsltTransformer.setDestination() copies over those properties defined in the stylesheet. This second phase appears to be redundant, except in its handling of next-in-chain, which has the effect of interposing a second transformation between the first transformation and the serializer. (The second phase also handles character maps.)
So I think that (a) we can reduce the second phase to only handle next-in-chain and character maps, and (b) in the first phase, we simply skip the next-in-chain property (which can't be set on a Serializer) to avoid the error message.
Updated by Michael Kay almost 7 years ago
It is not clearly documented, but the current behaviour is that output properties defined on the "second" stylesheet invoked via next-in-chain take effect in the serialization, and the changed code stops this happening. This can be corrected by reinstating the "redundant" code in XsltTransformer.setDestination(); but it seems better to do this only when handling the next-in-chain case.
We're now seeing a test failure on a unit test that sets serialization parameters from a configuration file (testSerializationConfiguration). This test runs both an XQuery and an XSLT stylesheet using this configuration.
XQuery is producing the correct result, but stepping through the code reveals a problem. XQuery is using a different mechanism to pass the serialization parameters from the query to the Serializer. Specifically it calls Serializer.getReceiver(Executable), which is not used on any XSLT paths. This method appears to have a bug in that it modifies the properties object held in the XQuery executable, which causes a problem if the same query is run more than once with different serialization properties applied at the API level. I've fixed this by creating a new Properties object over the base properties held in the XQuery executable.
On the XSLT side we are getting the wrong result. It seems the serialization parameters from the configuration file aren't being used. They are picked up by ProncipalStylesheetModule.gatherOutputProperties(), whence they find their way into the StylesheetPackage object. Unlike the previous test which used JAXP interfaces, this one is pure s9api and simply does XsltTransformer.setDestination(serializer), from which we have removed the supposedly-redundant code that moved the serialization properties from the XsltExecutable to the Serializer. So this needs to be reinstated after all.
Updated by Michael Kay almost 7 years ago
I'm now getting a failure in test case testJaxpImport which uses xsl:result-document with no href to write to the primary output, but with different serialization parameters. The issue here is that JAXP (being defined for XSLT 1.0) will pick up the default output properties, but the stylesheet is used properties from a named output definition. Internally the mechanism to support this is a ReconfigurableSerializer.
The issue here is that in ReconfigurableSerializer, the apiProperties object holds the API-defined properties backed by the properties from the unnamed output definition in the stylesheet. We are splatting these properties on top of the properties computed for xsl:result-document. We should be considering the API-defined properties, but not the unnamed output definition. Disentangling them is possible, but involves considering the different layers of the Properties object in a way that's potentially a bit fragile.
ReconfigurableSerializer is initialized from Xslt[30]Transformer.getDestinationReceiver, using the value of getLocalOutputProperties(). Perhaps Xslt[30]Transformer should maintain the API-defined properties separately?
The problem is that in Serializer, the "localOutputProperties" are supposed to be those defined directly by the API, but TransformerImpl.makeDestination() is adding the stylesheet-defined properties as if they were API-defined.
Need to think again. In IdentityTransformer the original intent was clearly to keep the API-defined and stylesheet-defined properties separate.
Updated by Michael Kay almost 7 years ago
- Category set to JAXP Java API
- Status changed from In Progress to Resolved
- Assignee set to Michael Kay
- Applies to branch trunk added
- Fix Committed on Branch 9.8, trunk added
Fixed on 9.8 and trunk branches
Updated by O'Neil Delpratt almost 7 years ago
- Status changed from Resolved to Closed
- % Done changed from 0 to 100
- Fixed in Maintenance Release 9.8.0.8 added
Bug fix applied in the Saxon 9.8.0.8 maintenance release.
Please register to edit this issue