Getting a result both as Xdm and serialized
Added by Anonymous almost 16 years ago
Legacy ID: #6260125 Legacy Poster: Luc M. (luchm)
Hi All, Suppose you run an Xslt transformation using Saxon, accessed from Java through the s9api interface. Say you want the result to be used for another transformation later: then you probably want to store the result into an XdmDestination. So you have a piece of code that looks like that: XsltTransformer transformer = ...; XdmDestination xdm = new XdmDestination(); transformer.setSource(...); transformer.setDestination(xdm); transformer.transform(); Now say you also need a serialized representation of this result. A first attempt consists of doing: String serial = reconciliation.getXdmNode().toString(); This does not work: the String serial will not contain, for example the <?xml?> declaration. So how to "fully" serialize an XdmNode? The only way I found, so far, is to use the identity transformation: XsltTransformer identity = processor.newXsltCompiler().compile(new StreamSource(new StringReader( "<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>" +"<xsl:template match='@|node()'>" +"<xsl:copy>" +"<xsl:apply-templates select='@|node()'/>" +"</xsl:copy>" +"</xsl:template>" +"</xsl:stylesheet>"))).load(); identity.setInitialContextNode(xdm.getXdmNode()); Serializer serializer = new Serializer(); StringWriter sw = new StringWriter(); serializer.setOutputProperty(Serializer.Property.METHOD, "xml"); serializer.setOutputWriter(sw); identity.setDestination(serializer); identity.transform(); String serial = sw.toString(); This does the trick, but I believe that internally, it will create a deep-copy of the tree. Not great performance-wise. You might also want to send the Xdm to a content handler, for example an XMLFilter. So here are a few questions: * Is there a way to send an XdmDestination or an XdmNode to a Serializer? * Is there a way to send an XdmDestination or an XdmNode to a SAXDestination? * Is there a way to send an XdmDestination or an XdmNode to a SAX2 content handler, not using a SAXDestination? Regards,
Replies (6)
Please register to reply
RE: Getting a result both as Xdm and serialized - Added by Anonymous almost 16 years ago
Legacy ID: #6260235 Legacy Poster: Michael Kay (mhkay)
Looks as if you haven't discovered the TeeDestination which allows you to send transformation output to two places at once. Also, you don't have to build an XdmDestination just because you want to feed the result into another transformation. An XsltTransformer is itself a Destination, so you can send the results straight there. The other method you might find useful (it answers several of your questions) is Processor.writeXdmValue() which writes any kind of XDM value to any kind of Destination, including of course a SAXDestination or a Serializer.
RE: Getting a result both as Xdm and serialized - Added by Anonymous almost 16 years ago
Legacy ID: #6260769 Legacy Poster: Luc M. (luchm)
Dear Michael, TeeDestination is exactly what I was looking for. Thanks for this (very!) prompt and useful reply.
RE: Getting a result both as Xdm and serialized - Added by Anonymous almost 16 years ago
Legacy ID: #6273478 Legacy Poster: Luc M. (luchm)
Dear Michael, Here's a related question: suppose I have an XdmNode and an XMLFilter. I would like to execute an XSLT transformation on the document represented by reading the XdmNode as a sequence of SAX events, and sending these events through the XMLFilter. I have found no way to do that. I tried to create a SAXSource to give to the XSLTTransformer. The problem is that creating a SAXSource requires an InputSource. So what I'm wondering is whether there's a way to create a SAXSource with an XMLReader and a dummy InputSource. The XMLReader parse(InputSource) method would then just totally ignore the InputSource it is being given, and generate the calls to the content handler according to the XdmNode. If I can retrieve the proper ContentHandler, then I should be able to do something such as: private class XdmReader implements XMLReader { private final XdmNode xdmNode; private final Processor processor; XdmReader(XdmNode xdmNode, Processor processor) { this.xdmNode = xdmNode; this.processor = processor; } // ... a bunch of required methods ... public void parse(InputSource arg0) throws IOException, SAXException { // ignore the input source: we want to read the xdmnode SAXDestination dest = new SAXDestination( ... what content handler here? ...); try { processor.writeXdmValue(xdmNode, dest); } catch (SaxonApiException e) { throw new SAXException(e); } } } While that sounds that it might work (although it's certainly hacky), I have not figured out how to get the correct ContentHandler to make that work. Best,
RE: Getting a result both as Xdm and serialized - Added by Anonymous almost 16 years ago
Legacy ID: #6274052 Legacy Poster: Michael Kay (mhkay)
You're into lower-level Saxon interfaces here, but it can all be done. The XsltTransformer is a Destination, so it has a getReceiver() method. You can connect your XMLFilter filter to the XsltTransformer transformer by doing PipelineConfiguration pipe = processor.getConfiguration().makePipelineConfiguration(); ReceivingContentHandler rch = new ReceivingContentHandler(); rch.setReceiver(transformer.getReceiver()); rch.setPipelineConfiguration(pipe); filter.setContentHandler(rch); and you can send data from the XdmNode node to your XMLFilter using SAXDestination destination = new SAXDestination(filter); processor.writeXdmValue(node, destination);
RE: Getting a result both as Xdm and serialized - Added by Anonymous almost 16 years ago
Legacy ID: #6280859 Legacy Poster: Luc M. (luchm)
Thanks Michael. I'm still missing a few pieces. Let's try to make a complete and minimal example. Using your piece of code, I came up with: ========================================================================= import java.io.; import javax.xml.transform.stream.StreamSource; import net.sf.saxon.event.; import net.sf.saxon.s9api.; import org.xml.sax.; import org.xml.sax.helpers.; /** Test class: try to send an XdmNode through an XMLFilter, and then through an XSLT Transformation./ public class XdmNodeToXMLFilterToTransformer { static final Processor processor = new Processor(false); /** An initial xml document to be processed. / static final String document = "<root>This is an xml document</root>"; /* Some xslt tranformation. For the example, just the identity. / static final String xslt = "<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>" +"<xsl:template match='@|node()'>" +"<xsl:copy>" +"<xsl:apply-templates select='@*|node()'/>" +"</xsl:copy>" +"</xsl:template>" +"</xsl:stylesheet>"; /** An XML FIlter that appends one more attribute to each element. */ static class MyXMLFilter extends XMLFilterImpl { @Override public void startElement(String arg0, String arg1, String arg2, Attributes arg3) throws SAXException { AttributesImpl attr = new AttributesImpl(arg3); attr.addAttribute("", "myAttribute", "", "", "blah"); super.startElement(arg0, arg1, arg2, attr); } } public static void main(String[] args) throws Exception { // Get the XdmNode XdmNode xdmNode = processor.newDocumentBuilder().build(new StreamSource(new StringReader(document))); // Creates the filter XMLFilter filter = new MyXMLFilter(); // Creates the transformer XsltTransformer transformer = processor.newXsltCompiler().compile(new StreamSource(new StringReader(xslt))).load(); // Creates a destination for the transformer Serializer serializer = new Serializer(); StringWriter sw = new StringWriter(); serializer.setOutputProperty(Serializer.Property.METHOD, "xml"); serializer.setOutputWriter(sw); transformer.setDestination(serializer); // Ok, now we should have everything we need. Let's go // The following piece of code of a copy-paste from Michael's post, with a few changes to have // the arguments correct PipelineConfiguration pipe = processor.getUnderlyingConfiguration().makePipelineConfiguration(); ReceivingContentHandler rch = new ReceivingContentHandler(); rch.setReceiver(transformer.getReceiver(processor.getUnderlyingConfiguration())); rch.setPipelineConfiguration(pipe); filter.setContentHandler(rch); // The SAXDestination constructor wants a ContentHandler, but what we have is an XMLFilter! // Won't using filter.getContentHandler() bypass the filter?? SAXDestination destination = new SAXDestination(filter.getContentHandler()); processor.writeXdmValue(xdmNode, destination); // Did it work? transformer.transform(); System.out.println(sw); } } ========================================================================= You will notice I had to change a few things to get it to compile. I'm not worried about "getUnderlyingConfiguration()" instead of "getConfiguration()" (I suppose that was a typo). I am worried, though, about having SAXDestination destination = new SAXDestination(filter.getContentHandler()); instead of SAXDestination destination = new SAXDestination(filter); // does not compile Indeed, my understanding is that the getContentHandler method simply returns the content handler that has been provided, and thus this should bypass the filter. Regardless of whether the filter will be bypassed or not, there's still a missing piece: when I run that program, I get: ============================================================== Error Either a source document or an initial template must be specified Exception in thread "main" net.sf.saxon.s9api.SaxonApiException: Either a source document or an initial template must be specified at net.sf.saxon.s9api.XsltTransformer.transform(XsltTransformer.java:233) at com.combinenet.reconciliator.test.XdmNodeToXMLFilterToTransformer.main(XdmNodeToXMLFilterToTransformer.java:71) Caused by: net.sf.saxon.trans.XPathException: Either a source document or an initial template must be specified at net.sf.saxon.Controller.transform(Controller.java:1594) at net.sf.saxon.s9api.XsltTransformer.transform(XsltTransformer.java:231) ... 1 more ============================================================== Finally, concerning my initial approach (create an XMLReader whose parse(InputSource) method ignores the input source being provided): I got it to work without the filter. That is, I was able to create a SAXSource from this XMLReader and an empty InputSource, and got the document processed by the XsltTransformer. However I have not been successful in plugin in a filter in between. Best,
RE: Getting a result both as Xdm and serialized - Added by Anonymous almost 16 years ago
Legacy ID: #6345545 Legacy Poster: Luc M. (luchm)
Hi All, Ok, so I finally got it to work, using my initial idea. It works that way: * Instead of doing the SAX processing with an XMLFilter or ContentHandler, create a 'ReceivingContentFilter' class that inherits ReceivingContentHandler. Overrides the desired SAX processing methods just as you would in an XMLFilter. * Creates a 'XdmReader' class that implements 'XMLReader'. This is a hacky implementation, that stores an XdmNode during construction, and that implements parse(InputSource) to read the XdmNode instead of the provided InputSource. The code of that class follows. * Get the XsltTransformer to builds the tree by doing: XMLReader reader = new XdmReader(xdmNode, processor, transformer); transformer.setSource(new SAXSource(reader, new InputSource())); And it works! It took me a real while to get that stuff working, so if someone needs it I'll be happy to give more details. =================================== class XdmReader implements XMLReader { /** The node to be read. / private final XdmNode xdmNode; /* The s9api processor. / private final Processor processor; /* The content handler. / private final ReceivingContentHandler ch; /* * Constructs an {@link XdmReader} * * @param xdmNode * the node be to read * @param processor * the s9api processor * @param transformer * the transformer where what is read will be sent / XdmReader(XdmNode xdmNode, Processor processor, XsltTransformer transformer) { this.xdmNode = xdmNode; this.processor = processor; ReceivingContentHandler rch = new ReceivingContentFilter(); try { rch.setReceiver(transformer.getReceiver(processor .getUnderlyingConfiguration())); } catch (SaxonApiException e) { e.printStackTrace(); throw new Error(e); } this.ch = rch; } @Override public ContentHandler getContentHandler() { return ch; } @Override public DTDHandler getDTDHandler() { return new DefaultHandler(); } @Override public EntityResolver getEntityResolver() { return new DefaultHandler(); } @Override public ErrorHandler getErrorHandler() { return new DefaultHandler(); } @Override public boolean getFeature(String arg0) throws SAXNotRecognizedException, SAXNotSupportedException { return false; } @Override public Object getProperty(String arg0) throws SAXNotRecognizedException, SAXNotSupportedException { return null; } /* * Parses the {@link XdmNode} provided when the reader was constructed. * * WARNING: This implementation of {@link XMLReader#parse(InputSource)} * violates its contract: it ignores the argument it is given. Use with * extreme care! / @Override public void parse(InputSource arg0) throws IOException, SAXException { // ignore the input source: we want to read the xdmnode try { SAXDestination dest = new SAXDestination(ch); processor.writeXdmValue(xdmNode, dest); System.err.println("Done!"); } catch (SaxonApiException e) { throw new SAXException(e); } } /* * @throws NotImplementedException * always. */ @Override public void parse(String arg0) throws IOException, SAXException { throw new NotImplementedException(); } @Override public void setContentHandler(ContentHandler arg0) { // Do nothing } @Override public void setDTDHandler(DTDHandler arg0) { // Do nothing } @Override public void setEntityResolver(EntityResolver arg0) { // Do nothing } @Override public void setErrorHandler(ErrorHandler arg0) { // Do nothing } @Override public void setFeature(String arg0, boolean arg1) throws SAXNotRecognizedException, SAXNotSupportedException { // Do nothing } @Override public void setProperty(String arg0, Object arg1) throws SAXNotRecognizedException, SAXNotSupportedException { // Do nothing } } ===================================
Please register to reply