1

I am using Saxon EE to transform a very large document, using streaming transform. I now need to chain multiple XSLTs. How does one go about doing that ? When not streaming, I have used the XSLTTransformer class as destination, to do chained transforms. If I am not mistaken, I guess I cannot do that, as that would create a result tree as against result stream. Thanks, Ani

Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
  • I have tried using a TransformerHandler for each XSLT and then chaining them. This works fine if I do not use the StreamingTransformerFactory to create an instance of TransformerHandler. The transform works, but gives me Warning Requested initial mode is streamable, but the supplied input is not streamed" This is expected as I am not using a Streaming TransformHandler.
    – Aniruddha Joag May 12 '17 at 15:00
  • The moment I use a StreamingTransformerFactory to create a TransformerHandler, I get java.lang.ClassCastException: net.sf.saxon.jaxp.StreamingTransformerImpl cannot be cast to net.sf.saxon.jaxp.TransformerImpl at net.sf.saxon.jaxp.SaxonTransformerFactory.newTransformerHandler(SaxonTransformerFactory.java:396) – Aniruddha Joag May 12 '17 at 15:12
  • I think you should edit your question and show your Java code. In general I think to use Saxon and XSLT 3.0 Saxonica recommends using the s9api and not JAXP. On the other hand the docs http://saxonica.com/html/documentation/using-xsl/embedding/s9api-transformation.html clearly say about the `Xslt30Transformer` class clearly says "The Xslt30Transformer does not implement the Destination interface; if you want to pipe transformation results into a transformation, that transformation must be represented as an XsltTransformer" so I am not sure how your chaining is supported with streaming. – Martin Honnen May 12 '17 at 18:53
  • Well, it looks like chaining is supported. See Charles's example code below – Dino Fancellu May 13 '17 at 12:03
  • @DinoFancellu, yes, `StreamingTransformerFactory` seems to be a recent addition to 9.7, not yet documented at http://saxonica.com/html/documentation/javadoc/index.html, only added in 9.7.0.18 to resolve https://saxonica.plan.io/issues/3120. – Martin Honnen May 13 '17 at 12:57

1 Answers1

4

Pipe the SAX output events of the 1st transform into the SAX input events of the 2nd transform.

I've attached some example Scala code which shows this.

Basically it kicks off the 2nd XSLT first, which behind the scenes invokes the 1st XSLT with the initial input document capturing the intermediate output which is then fed as the input to the 2nd XSLT in real time. Nice.

  • It uses JAXP interfaces, so no S9 API.
  • I've successfully tested it with a 1.2GB input XML file.

Hope this helps.

XSLT 3.0 rocks by the way! Good choice.

import javax.xml.transform.sax.{SAXResult, SAXSource}
import javax.xml.transform.stream.{StreamResult, StreamSource}
import javax.xml.transform.{Source, Transformer}

import com.saxonica.config.StreamingTransformerFactory
import org.xml.sax._
import org.xml.sax.helpers.XMLFilterImpl

object Main extends App
{
  val transformer1 = transformer("transform-1.xsl")
  val transformer2 = transformer("transform-2.xsl")

  val inputXML = "big.xml"

  transformer2.transform(
    new SAXSource(
      new Transformer1OutputReader(transformer1, new StreamSource(inputXML)),
      null
    ),
    new StreamResult("out.xml")
  )

  def transformer(xslt : String) =
    new StreamingTransformerFactory().newTransformer(new StreamSource(xslt))

}


class Transformer1OutputReader(
  transformer1 : Transformer,
  source1 : Source) extends XMLFilterImpl
{
  def parseImpl() =
  {
    println("parseImpl()")

    val inputToSecondXslt : ContentHandler = getContentHandler

    transformer1.transform(
      source1,
      new SAXResult(inputToSecondXslt)
    )
  }

  override def parse(input : InputSource) = parseImpl
  override def parse(systemId : String) = parseImpl
  override def setFeature(name: String, value: Boolean) : Unit = {}
}
Charles Foster
  • 338
  • 3
  • 5