running xslt from java
1. | Running a transform from java. | ||||||
Here is the source of a little command line Java program called Moxie [1]. It is an interface to the JAXP XSLT engine. I'd guess you could add servlet classes to this program and get it to work. You could pass in the source and stylesheet in other ways besides reading it in from args. /* * Moxie JAXP XSLT processor */ import java.io.File; import java.io.FileOutputStream; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; public class Moxie { public static void main(String[] args) throws Exception { /* Output file flag */ boolean file = false; /* Default system property for Xalan processor */ System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl"); /* Usage strings */ String info = "Moxie JAXP XSLT processor"; String usage = "\nUsage: java -jar moxie.jar"; String parms = " source stylesheet [result]"; /* Test arguments */ if (args.length == 0) { System.out.println(info + usage + parms); System.exit(1); } else if (args.length == 3) { file = true; } else if (args.length > 3) { System.out.println("Too many arguments; exit."); System.exit(1); } /* XML source document and stylesheet */ File source = new File(args[0]); File stylesheet = new File(args[1]); /* Set up source and result streams */ StreamSource src = new StreamSource(source); StreamSource style = new StreamSource(stylesheet); StreamResult out; if (file) { FileOutputStream outFile = new FileOutputStream(args[2]); out = new StreamResult(outFile); } else { out = new StreamResult(System.out); } /* Create transformer */ TransformerFactory factory = TransformerFactory.newInstance(); Transformer xf = factory.newTransformer(style); /* Set output encoding property */ xf.setOutputProperty(OutputKeys.ENCODING, "US-ASCII"); // encoding xf.setOutputProperty(OutputKeys.INDENT, "yes"); // indent /* Perform the transformation */ xf.transform(src, out); } } I can only guess what you mean by "replace a node's attribute in it." I suspect you want to process an XML document with your servlet, which, if you use this code, will require an XSLT stylesheet on the server that contains something like this (untested): <xsl:template match="mynode"> <xsl:copy> <xsl:attribute name="{name(@myatt)}">newvalue</xsl:attribute> </xsl:copy> </xsl:template> If you turn this code into a servlet, I'd like to see it. Best of luck. [1] from Chapter 17 of Learning XSLT (O'Reilly, 2003). | |||||||
2. | Calling to Java from xslt | ||||||
Maybe we should then make simpler. To better shown the pertinent points? What do you think aout the following? Calling Java from a stylesheet See the official documentation (http://www.saxonica.com/documentation/extensibility/functions.html). For an example, say you have the following class in Java: package org.example; class MyClass { public static String ourFunc(int i) { // ... } public String myFunc(int i) { // ... } } Assuming the class is in your classpath, you can then, in XSLT: 1/ call the static ourFunc() method, 2/ instanciate MyClass and 3/ call the myFunc() method on an instance of MyClass: <xsl:stylesheet xmlns:my="java:org.example.MyClass" ...> ... <!-- 1/ --> <xsl:value-of select="my:ourFunc(1)"/> <!-- 2/ --> <xsl:variable name="obj" select="my:new()"/> <!-- 3/ --> <xsl:value-of select="my:myFunc($obj, 1)"/> ... </xsl:stylesheet> Note the URI used to map to the class name, the pseudo-method with the name new to call the constructor (to instanciate a class), and the way we explicitely pass an instance (an object) as the first, additional parameter to instance-level methods (non-static methods). | |||||||
3. | Generating UUID from XSLT | ||||||
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:uuid="uuid" xmlns:math = "http://exslt.org/math" xmlns:xs = "http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:output omit-xml-declaration="yes" /> <xsl:template match="/"> <xsl:value-of select=" concat('First random ID:', uuid:get-id()), concat('Base timestamp: ', uuid:generate-timestamp()), concat('Clock id: ' ,uuid:generate-clock-id()), concat('Network node: ' ,uuid:get-network-node()), concat('UUID Version: ' ,uuid:get-uuid-version()), concat('Generated UUID: ' ,uuid:get-uuid()), concat('Generated UUID: ' ,uuid:get-uuid()), concat('Generated UUID: ' ,uuid:get-uuid()), concat('Generated UUID: ' ,uuid:get-uuid()) " separator=" " /> </xsl:template> <!-- Functions in the uuid: namespace are used to calculate a UUID The method used is a derived timestamp method, which is explained here: http://www.famkruithof.net/guid-uuid-timebased.html and here: http://www.ietf.org/rfc/rfc4122.txt --> <!-- Returns the UUID --> <xsl:function name="uuid:get-uuid" as="xs:string*"> <xsl:variable name="ts" select="uuid:ts-to-hex(uuid:generate-timestamp())" /> <xsl:value-of separator="-" select=" substring($ts, 8, 8), substring($ts, 4, 4), string-join((uuid:get-uuid-version(), substring($ts, 1, 3)), ''), uuid:generate-clock-id(), uuid:get-network-node()" /> </xsl:function> <!-- internal aux. fu with saxon, this creates a more-unique result with generate-id then when just using a variable containing a node --> <xsl:function name="uuid:_get-node"><xsl:comment /></xsl:function> <!-- generates some kind of unique id --> <xsl:function name="uuid:get-id" as="xs:string"> <xsl:sequence select="generate-id(uuid:_get-node())" /> </xsl:function> <!-- should return the next nr in sequence, but this can't be done in xslt. Instead, it returns a guaranteed unique number --> <xsl:function name="uuid:next-nr" as="xs:integer"> <xsl:variable name="node"><xsl:comment /></xsl:variable> <xsl:sequence select="xs:integer(replace(generate-id($node), '\D', ''))" /> </xsl:function> <!-- internal fu for returning hex digits only --> <xsl:function name="uuid:_hex-only" as="xs:string"> <xsl:param name="string" /> <xsl:param name="count" /> <xsl:sequence select="substring(replace($string, '[^0-9a-fA-F]', ''), 1, $count)" /> </xsl:function> <!-- may as well be defined as returning the same seq each time --> <xsl:variable name="_clock" select="uuid:get-id()" /> <xsl:function name="uuid:generate-clock-id" as="xs:string"> <xsl:sequence select="uuid:_hex-only($_clock, 4)" /> </xsl:function> <!-- returns the network node, this one is 'random', but must be the same within calls. The least-significant bit must be '1' when it is not a real MAC address (in this case it is set to '1') --> <xsl:function name="uuid:get-network-node" as="xs:string"> <xsl:sequence select="uuid:_hex-only('09-17-3F-13-E4-C5', 12)" /> </xsl:function> <!-- returns version, for timestamp uuids, this is "1" --> <xsl:function name="uuid:get-uuid-version" as="xs:string"> <xsl:sequence select="'1'" /> </xsl:function> <!-- Generates a timestamp of the amount of 100 nanosecond intervals from 15 October 1582, in UTC time. --> <xsl:function name="uuid:generate-timestamp"> <!-- date calculation automatically goes correct when you add the timezone information, in this case that is UTC. --> <xsl:variable name="duration-from-1582" as="xs:dayTimeDuration" > <xsl:sequence select="current-dateTime() - xs:dateTime('1582-10-15T00:00:00.000Z')" /> </xsl:variable> <xsl:variable name="random-offset" as="xs:integer"> <xsl:sequence select="uuid:next-nr() mod 10000"></xsl:sequence> </xsl:variable> <!-- do the math to get the 100 nano second intervals --> <xsl:sequence select=" (days-from-duration($duration-from-1582) * 24 * 60 * 60 + hours-from-duration($duration-from-1582) * 60 * 60 + minutes-from-duration($duration-from-1582) * 60 + seconds-from-duration($duration-from-1582)) * 1000 * 10000 + $random-offset" /> </xsl:function> <!-- simple non-generalized function to convert from timestamp to hex --> <xsl:function name="uuid:ts-to-hex"> <xsl:param name="dec-val" /> <xsl:value-of separator="" select=" for $i in 1 to 15 return (0 to 9, tokenize('A B C D E F', ' ')) [$dec-val idiv xs:integer(math:power(16, 15 - $i)) mod 16 + 1]" /> </xsl:function> </xsl:stylesheet> Ed. This is the equivalent using a Java extension. <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:util="java:java.util.UUID" version="1.0"> <xsl:output method="xml" indent="yes" encoding="utf-8"/> <xsl:template match="/"> <xsl:message> <xsl:variable name="uid" select="util:randomUUID()"/> <xsl:value-of select="util:toString($uid)"/> </xsl:message> <xsl:apply-templates/> </xsl:template> </xsl:stylesheet> | |||||||
4. | Access to Java from XSLT Stylesheet | ||||||
For some time now I've used system calls to get the data and time etc. What I hadn't done before was to call a class which I'd written and get back some answer. Until Michael Kay set me straight I really couldn't get my head round this. Now I've done it and cleaned up this section, here's a simple example which calculates how many days since you stopped smoking. All it does is call a single class, which is expected to be available in the classpath. I'm using Saxon, though I think this approach should be OK for most of the java implementations. Two parts to this. The java class file and the XSLT. Firstly then the java file import java.util.Date; import java.util.GregorianCalendar; import java.util.Calendar; public class xslextension { public static String returnString(double year, double month, double day) { int yStpd = (int)year; int mStpd = (int)month-1; //e.g. September should be 8 int dStpd = (int)day; long divr = 1000 * 60 * 60 *24; // seconds to days convertor. GregorianCalendar now = new GregorianCalendar (); long nowSecs = now.getTimeInMillis(); GregorianCalendar then = new GregorianCalendar(yStpd,mStpd,dStpd); long thenSecs = then.getTimeInMillis(); long diff =nowSecs - thenSecs; long days = diff / divr; long weeks = days / 7; long odds = days % 7; String dys = "Its been "+ days + " days, "; dys += "or, " + weeks + " Weeks and " + odds + " days since your last cigarette"; return dys; } } Points of note This is in a file xslextension.java - matches the class name The only method is public and static (hence we don't have to instantiate the class). The class is public. I know you won't make the mistake, but I forgot that :-) The input parameters are doubles, just to show that values can be passed. In this case its the year, month (January = 1) and day of month on which you stopped smoking. The return type is a java String. I've always found this simpler to deal with from the XSLT. If you want a variant you're on your own. That's it for the java. Compile that and make sure its in the path in use when you call the XSLT engine. The XSL is below, which calls the java. <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:java="java:xslextension" version="1.1"> <xsl:output method="html"/> <xsl:template match="/"> <html> <body bgcolor="#FFFFFF"> <h2>The smoking calendar</h2> <h3><xsl:value-of select="java:returnString(2002, 9,2)"/> </h3> </body> </html> </xsl:template> </xsl:stylesheet> Note the java namespace? It's the class name, not the method name. Also the 3 parameters of year, month and date. Run this in Saxon and note the output. Remember the command line classpath (or the environmental variable) must include the .class file! Final checks. Things that have been known to go wrong.
| |||||||
5. | Generating a random number using a java extension | ||||||
This stylesheet generates a new random number each time its run. XML file: brands.xml <?xml version="1.0" ?> <?xml-stylesheet type="text/xsl" href="brands.xsl"?> <?cocoon-process type="xslt"?> <brands> <car>BMW</car> <car>Mercedes</car> <car>Opel</car> <car>Porsche</car> <car>Ferrari</car> <car>Renault</car> <car>Citroen</car> <car>Seat</car> <car>VolksWagen</car> <car>Audi</car> <car>Saab</car> <car>Toyota</car> <car>Nissan</car> <car>Subaru</car> <car>Lamborghini</car> </brands> XSL file: brands.xsl <?xml version="1.0" ?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:j="http://icl.com/java.util.Random" exclude-result-prefixes="j" version="1.0"> <xsl:output method="html" indent="yes"/> <xsl:template match="/brands"> <html> <head> <title>Random</title> </head> <body> <xsl:variable name="cars" select="count(car)"/> There are <xsl:value-of select="$cars" /> car brands in the list.<br /> <br /> <xsl:variable name="R" select="j:new()" /> <xsl:variable name="r" select="ceiling(count(car) * j:nextDouble($R))" /> Selected car brand is: <xsl:value-of select="car[$r]" /> </body> </html> </xsl:template> </xsl:stylesheet> | |||||||
6. | Return value of a Java function | ||||||
Q expansion: I'm using xsl:variable to hold the return value of logon:seed(), a function that will change values every time it is called. The problem I'm having is that when I refer to my variable, $seed, the function is begin called. Do <xsl:variable name="seed" select="logon:seed()"/> If you don't use the select attribute, you're binding $seed to a result tree fragment, which you almost certainly don't want. | |||||||
7. | Mixing XSL stylesheet and Java handlers in Saxon | ||||||
I want to use XSLT template rules for some nodes and Java node handlers for others If you want to use XSLT template rules for some nodes and Java node handlers for others, the way to do it is to declare the Java node handlers in the stylesheet using the <saxon:handler> element, for example <saxon:handler match="html" handler="com.me.handlers.HTMLHandler"/> A slightly different approach that's closer to the way the standard has moved is to implement your custom code as an extension element, so instead of the above, write <xsl:template match="html"> <xyz:html-extension/> </xsl:template> and then implement <xyz:html-extension> using SAXON's element extensibility features. | |||||||
8. | java.lang.OutOfMemoryError | ||||||
Java Memory Errors - You may need to increase the swap space that Java uses on your system.This can be accomplished directly on the command line when running XT by adding the following parameters: - -msXX where XX is the Minimum Heap Size (use at least 50M) - -mxYYY where YYY is the Maximum Heap Size (200M, 400M, etc.) Your new UNIX command line call will now look like this: java -ms50M -mx400M com.jclark.xsl.sax.Driver input-file xsl-file output-file For Java 1.3, this is an extended option (-X...). Try "java -X" to get an overview of them. -Xmx is most likely what you are looking for. | |||||||
9. | How to get an executable from a .jar file | ||||||
The tool that James Clark uses to compile xt.exe It's called 'jexegen' and it's part of the Microsoft SDK for Java 4.0, at their website The by-product is not really "compiled", it just packs up all the .class files and "glues" them as a resource with a little loader-stub at the front. Any speed difference using a 'jexegen'-d executable should be identical to what you gen when you simply run with the Microsoft jview.exe java VM instead of another one. Makes it easier for non-Java-savvy folks to run the code on Windows. | |||||||
10. | Java extensions | ||||||
The answer is XSLT-processor dependent. You need to check the specs for your particular processor <plug>(or the relevant appendix of XSLT Programmer's Reference)</plug>
It doesn't matter what file the class is in, it matters what it's fully-qualified name is, e.g. com.me.package.Helloworld.class or simply Helloworld.class
For Saxon, use xmlns:java="java:com.me.package.Helloworld". And make sure the method is declared as: public static String hello() { return "hi!"; }
MSXML4 doesn't support Java. | |||||||
11. | URI Resolution. Controlling imports includes and the document() function | ||||||
I received a few requests for a better example of how to use the URIResolver. I hope the information below helps. When you need control over xsl:include/import or the document(), you need to use an URIResolver - you cannot do it in your stylesheet. This is very useful when you have a 'primary' XSL[1] and it includes, imports or uses document() to bring in other XML/XSL files/streams from two or more different points of control. The very simplest example (using Saxon), is to compile the file MyResolver.jar (or one based on it) and make sure it is in the classpath when Saxon is called up. Saxon takes a parameter -r className which specifies the resolver to use. Specify -r MyResolver and this java code will be called up as and when an include, import or document() call is made. The java source is included in the zip file - see below. As a more useful example lets say a user logs on to a server-side application. The user can choose different navigation styles to get around a site (dynamic JS or static HTML). You would need to somehow get or keep a user preference that tells the app which nav option they have choosen. A complete example is included here, zipped, which provides the complete item set up as a java commandline application. We might have a primary XSLT like: <?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:include href="head.xsl"/> <xsl:include href="banner.xsl"/> <xsl:include href="nav.xsl"/> <xsl:include href="footer.xsl"/> <xsl:template match="/"> <html> <xsl:call-template name="head"/> <body> <xsl:call-template name="banner"/> <div id="leftcol"> <xsl:call-template name="nav"/> </div> <div id="centercol"> call some templates </div> <div id="rightcol"> <div class="floater"> call some templates </div> </div> <br clear="all"/> <xsl:call-template name="footer"/> </body> </html> </xsl:template> </xsl:stylesheet> There are two different sets of the head and nav and to keep things modular. So you might use static_head.xsl & static_nav.xsl or dynamic_head.xsl & dynamic_nav.xsl. You need an URIResolver to handle which set to include. You may have a method that starts transformations in the app: void xform(ServletContext servlet_context, HttpServletRequest req, HttpServletResponse res, long _start_time) throws TransformerException, java.io.IOException { System.setProperty( "javax.xml.transform.TransformerFactory", "com.icl.saxon.TransformerFactoryImpl"); ServletOutputStream out = res.getOutputStream(); HttpSession http_session = req.getSession(); String nav_style = http_session.getAttribute("nav_style").toString(); String source = http_session.getAttribute("source").toString(); String style = http_session.getAttribute("style").toString(); try { Templates pss = tryCache(servlet_context, style, nav_style); Transformer transformer = pss.newTransformer(); Properties details = pss.getOutputProperties(); String mime = pss.getOutputProperties().getProperty(OutputKeys.MEDIA_TYPE); if (mime==null) { res.setContentType("text/html"); } else { res.setContentType(mime); } transformer.setParameter("nav_style", nav_style); transformer.transform( new StreamSource(source)), new StreamResult(out)); out.close(); out.flush(); } catch (Exception err) { System.out.println(err); } } Your tryCache method might look like: private synchronized Templates tryCache(ServletContext serv_context, String path, String nav_style) throws TransformerException, java.io.IOException { String full_path = serv_context.getRealPath(path); Templates x = (Templates)this.cache.get(path); if (x==null) { TransformerFactory factory = TransformerFactory.newInstance(); factory.setURIResolver(new MyResolver(serv_context, nav_style)); x = factory.newTemplates(new StreamSource(new File(full_path))); this.cache.put(path, x); } return x; } This primary XSLT needs to find files identified in the xsl:includes/imports and document(). The following URIResolver simply checks the href argument automatically sent in by the transformation process. The href argument is the value of the href in the xsl:include/import or the first (or only) argument from the document function. The base argument is either the location of the XSLT or the second argument in the document(). If it sees head.xsl or nav.xsl it prepends the filename with the nav style preference string: class MyResolver implements URIResolver { String base_path; String nav_style; public MyResolver(ServletContext context, String style) { this.base_path = context.getRealPath("/WEB-INF/styling/"); this.nav_style = style; } public Source resolve(String href,String base) { StringBuffer path = new StringBuffer(this.base_path); if (href.equals("head.xsl") | href.equals("nav.xsl")) { path.append(this.nav_style); path.append("_"); } path.append(href); File file = new File(path.toString()); if(file.exists()) return new StreamSource(file); return null; } } [1] the XSL used as the argument in the transformation against the main source XML | |||||||
12. | Transform via a java servlet under Tomcat | ||||||
The XSL stylesheet contains the line <xsl:variable name="CatalogDetails" select="document(concat('http://localhost/Path/To/Servlet/ServletName?Path=' , $pathtofile))"/> (In this case, the value of $pathtofile is passed as a parameter to the stylesheet, but it can equally well be derived from data within the XML file itself.) The servlet code is stripped and simplified. A lot of the checks that you would normally make have been excluded for simplicity's sake. I have used the same technique for extracting data from an JDBC:ODBC database, a SAP R/3 system via Remote Function Calls and, as an XSL newbie, I've used it as an "extension" feature for doing date arithmetic. Although it can be slow when calling a remote server, if the servlet engine is on the same box as the one you are performing the transform on, performance doesn't seem too bad. import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.*; import javax.servlet.http.*; public class GetProductXML extends HttpServlet { public GetProductXML() { } public void init(ServletConfig config) throws ServletException { super.init(config); } public void destroy() { } protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException { response.setContentType("text/xml"); PrintWriter out = null; try { out = new PrintWriter(new BufferedWriter(response.getWriter())); String pathToFile = request.getParameter("Path"); File file = new File(pathToFile); BufferedReader in = new BufferedReader(new FileReader(file)); String line = null; while ((line = in.readLine()) != null) { out.print(line); out.flush(); out.close(); } } catch (IOException e) { out.println("<?xml version=\"1.0\">"); out.println("<Data></Data>"); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } public String getServletInfo() { return "Short description"; } } | |||||||
13. | Trax Interface | ||||||
/** Method 1 **/ public String apply(String style, String source) throws TransformerException { TransformerFactory factory = TransformerFactory.newInstance(); Templates templates = factory.newTemplates(new StreamSource(new File(style))); Transformer transformer = templates.newTransformer(); StringReader sr = new StringReader(source); StringWriter sw = new StringWriter(); transformer.transform(new StreamSource(sr), new StreamResult(sw)); return sw.toString(); } However, if the source document has XML syntax errors, they get reported to System.err. I would really like to have control over error reporting, as my resulting program should be reporting problems into a log file, not stderr! So, back to the drawing board for Method 2. I did some digging around in the APIs and decided to use a "TransformerHandler" which is a TRAX "Transforming SAX handler". You can instantiate your own SAX XMLReader (and thereby have control over the error handling), and hand it your TransformerHandler instance. By and by you get a "Result" document containing the Styled/Transformed XML. /** Method 2 **/ public String apply(String style, String source) throws TransformerException { StringReader sr = new StringReader(source); StringWriter sw = new StringWriter(); try { SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); SAXParser parser = saxParserFactory.newSAXParser(); System.err.println(parser.getClass()); XMLReader xmlReader = parser.getXMLReader(); xmlReader.setFeature("http://xml.org/sax/features/namespaces", true); xmlReader.setFeature( "http://xml.org/sax/features/namespace-prefixes", false); TransformerFactory transformerFactory = TransformerFactory. newInstance(); if (!transformerFactory.getFeature(SAXTransformerFactory.FEATURE)) { System.err.println("uh oh ... this isn't going to work out!"); } SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) transformerFactory; String systemId = new File("pretty.xsl").toURL().toExternalForm(); StreamSource xslStreamSource = new StreamSource(systemId); TransformerHandler transformHandler = saxTransformerFactory.newTransformerHandler(xslStreamSource); xmlReader.setContentHandler(transformHandler); Result result = new StreamResult(sw); transformHandler.setResult(result); xmlReader.parse(new InputSource(sr)); } catch (ParserConfigurationException e) { System.out.println( "The underlying parser does not support the requested features."); } catch (FactoryConfigurationError e) { System.out.println("Error occurred obtaining SAX Parser Factory."); } catch (Exception e) { e.printStackTrace(); } return sw.toString(); } | |||||||
14. | Using a StreamResult as a StreamSource, or chaining stylesheets in java | ||||||
You can use SAXTransformerFactory to chain a series of Transformations by piping the SAX events from one transformer to next one example TransformerFactory tFactory = TransformerFactory.newInstance(); TransformerFactory tFactory = TransformerFactory.newInstance(); if (tFactory.getFeature(SAXSource.FEATURE) && tFactory.getFeature(SAXResult.FEATURE)) { // Cast the TransformerFactory to SAXTransformerFactory. SAXTransformerFactory saxTFactory = ((SAXTransformerFactory) tFactory); // Create a TransformerHandler for each stylesheet. TransformerHandler tHandler1 = saxTFactory.newTransformerHandler(new StreamSource("foo1.xsl")); TransformerHandler tHandler2 = saxTFactory.newTransformerHandler(new StreamSource("foo2.xsl")); TransformerHandler tHandler3 = saxTFactory.newTransformerHandler(new StreamSource("foo3.xsl")); // Create an XMLReader. XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setContentHandler(tHandler1); reader.setProperty("http://xml.org/sax/properties/lexical-handler";, tHandler1); tHandler1.setResult(new SAXResult(tHandler2)); tHandler2.setResult(new SAXResult(tHandler3)); // transformer3 outputs SAX events to the serializer. Serializer serializer = SerializerFactory.getSerializer(OutputProperties.getDefaultMethodProperties( "xml")); serializer.setOutputStream(System.out); tHandler3.setResult(new SAXResult(serializer.asContentHandler())); // Parse the XML input document. The input ContentHandler and output ContentHandler // work in separate threads to optimize performance. reader.parse("foo.xml"); } | |||||||
15. | Chained transformations in java | ||||||
Check out: sun.com It is sometimes useful to create a filter chain -- a concatenation of XSLT transformations in which the output of one transformation becomes the input of the next. This section of the tutorial shows you how to do that. | |||||||
16. | Java chain of transforms. | ||||||
I struggled with the same exact problem. It turns out that you *can't* pass parameters to TRAX when you use XMLFilter. Michael Kay pointed out that you don't really need to use XMLFilter at all. You can chain together stylesheets directly in a SAX chain. He points out that by not using XMLFilter, your code is simpler. Here is my example code below. The code is rather long, but that's because most of it deals with handling exceptions. The important part for your code starts with the comment:
Notice how I set up an instance of a TransformerHander for each stylesheet. Then notice how I set the result of the first instance to the next instance. The result of the last instance gets set to standard otuput. You can also set this to SAX transformation. (However, I can't for the life of me figure out how to set up a chain with a SAX transformation at the beginning of the chain using the type of chain I set up below. If anyone has any example code, please show me! But that's another matter.) I'm not a java programmer, so I can't tell you any more about the intricacies of java, but I think this code should work. I think it might be nice to have as a faq. It could serve as template for when you need to chain stylesheets together. I know how to chain together transformations using python and temporary files, but I think java is superior for such a task. ******************************************************* //standard java classes import java.io.IOException; import java.io.OutputStreamWriter; import java.io.FileNotFoundException; //javax classes import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.transform.TransformerConfigurationException; //sax classes import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; public class ChainStyleSheets { /** * Method main */ public static void main(String argv[]) throws TransformerException, TransformerConfigurationException, IOException, SAXException, ParserConfigurationException, FileNotFoundException { try { chainStylesheets("xml/foo.xml", "xsl/foo.xsl", "xsl/foo2.xsl", "xsl/foo3.xsl" ); } catch( Exception ex ) { handleException(ex); } } public static void chainStylesheets(String sourceID, String xsl_ID1, String xsl_ID2, String xsl_ID3 ) throws TransformerException, IOException, SAXException, ParserConfigurationException { TransformerFactory tfactory = TransformerFactory.newInstance(); // Does this factory support SAX features? if (tfactory.getFeature(SAXSource.FEATURE)) { // If so, we can safely cast. SAXTransformerFactory stfactory = ((SAXTransformerFactory) tfactory); // A TransformerHandler is a ContentHandler that will listen for // SAX events, and transform them to the result. // Set up a hanlder for each style sheet. //LOOK HERE TransformerHandler handler1 = stfactory.newTransformerHandler(new StreamSource(xsl_ID1)); TransformerHandler handler2 = stfactory.newTransformerHandler(new StreamSource(xsl_ID2)); TransformerHandler handler3 = stfactory.newTransformerHandler(new StreamSource(xsl_ID3)); // Set up the parmeters. This paremeter will be passed to // stylesheet 1 (foo.xsl) handler1.getTransformer().setParameter( "a-param", "a parameter set by java"); //parameters for stylesheet 2 handler2.getTransformer().setParameter( "b-param", "b parameter set by java"); // Set the output of the stylesheets. The first stylesheet will // output to handle2, thus piping the result so the second // stylesheet can parse it. The second stylesheet outputs to the // terminal Result result1 = new SAXResult(handler2); Result result2 = new SAXResult(handler3); Result result3 = new StreamResult(new OutputStreamWriter(System.out)); handler1.setResult(result1); handler2.setResult(result2); handler3.setResult(result3); //END LOOK HERE // Create a reader, and set it's content handler to be the // TransformerHandler. XMLReader reader=null; // Create the SAX members // Use JAXP1.1 ( if possible ) try { javax.xml.parsers.SAXParserFactory factory= javax.xml.parsers.SAXParserFactory.newInstance(); factory.setNamespaceAware( true ); javax.xml.parsers.SAXParser jaxpParser= factory.newSAXParser(); reader=jaxpParser.getXMLReader(); } catch( javax.xml.parsers.ParserConfigurationException ex ) { throw new org.xml.sax.SAXException( ex ); } catch( javax.xml.parsers.FactoryConfigurationError ex1 ) { throw new org.xml.sax.SAXException( ex1.toString() ); } catch( NoSuchMethodError ex2 ) { } if( reader==null ) reader = XMLReaderFactory.createXMLReader(); reader.setContentHandler(handler1); // Parse the source XML, and send the parse events to the // TransformerHandler. reader.parse(sourceID); } else { System.out.println( "Can't do exampleContentHandlerToContentHandler because tfactory is not a SAXTransformerFactory"); } } private static void handleException( Exception ex ) { System.out.println("EXCEPTION: " ); ex.printStackTrace(); if( ex instanceof TransformerConfigurationException ) { System.out.println(); System.out.println("Internal exception: " ); Throwable ex1=((TransformerConfigurationException)ex).getException(); ex1.printStackTrace(); if( ex1 instanceof SAXException ) { Exception ex2=((SAXException)ex1).getException(); System.out.println("Internal sub-exception: " ); ex2.printStackTrace(); } } } } | |||||||
17. | xsl:result-document in a servlet environment | ||||||
Although it's possible in principle to use a relative path, this isn't likely to be convenient in a servlet environment. Any relative URI is resolved relative to the base URI of the principal output document, which in the servlet case is going to the servlet output streat, which doesn't have a meaningful URI. You could, when you allocate the principal output destination, give it an arbitrary URI for this purpose: ServletOutputStream out = res.getOutputStream(); StreamResult tout = new StreamResult(out); tout.setSystemId("file:/c:/some/uri/"); transformer.transform(new StreamSource(sourceFile), tout); However, I'm wondering what you're actually trying to do here. Where do you want to put the extra output files? Is it a location associated with the individual end user, or a location associated with the source document, or what? Are you trying to split the source document into multiple result documents the first time it is accessed, and cache the results for later use? Do you actually want to serialize the multiple output documents to disk, or do you really want them in (application or session) memory?
I think the correct syntax is file:///usr/dir/file.xml to reference the UNIX file /user/dir/file.xml > I'm new to the Servlet environment.. I'm not sure how to obtain the If you're running on a hosted machine then you'll have to write the files to a directory where the servlet has write permission. A Java call such as getServletContext().getRealPath(source); will give you path names to files within the directory structure visible to users via a browser - it's not clear whether you want the XML files you generate to be directly accessible to users or not. |