I have been reading through programming blogs about how to use Extended Functions with XSLT and Saxon, and can't seem to reference external Java functions in the XSLT successfully. I am getting the following error whenever I run the transform:
Cannot find a matching 0-argument function named {come.acme.javaxslt.business.CarBusiness}getModel()
at xsl:apply-templates (file:/C:/Users/Dave/workspace/acme-javaXSLT-demo/cars.xsl#18)
processing /cars/car[1]/model[1]
in built-in template rule
at xsl:apply-templates (file:/C:/Users/Dave/workspace/acme-javaXSLT-demo/cars.xsl#10)
processing /cars
EXCEPTION: net.sf.saxon.trans.XPathException: Cannot find a matching 0-argument function named {com.acme.javaxslt.business.CarBusiness}getModel()
; SystemID: file:/C:/Users/Dave/workspace/acme-javaXSLT-demo/cars.xsl; Line#: 30; Column#: -1
net.sf.saxon.trans.XPathException: Cannot find a matching 0-argument function named {com.acme.javaxslt.business.CarBusiness}getModel()
at net.sf.saxon.expr.ErrorExpression.evaluateItem(ErrorExpression.java:58)
at net.sf.saxon.expr.ErrorExpression.iterate(ErrorExpression.java:71)
at net.sf.saxon.expr.Atomizer.iterate(Atomizer.java:180)
...
Referenced Java Class:
package com.acme.javaxslt.business;
public class CarBusiness {
public static String getModel(){
return "/";
}
}
XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cars="com.acme.javaxslt.business.CarBusiness">
<xsl:template match="/">
<html>
<body>
<h2>My Car Collection</h2>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="car">
<p>
<xsl:apply-templates select="make"/>
<xsl:apply-templates select="model"/>
</p>
</xsl:template>
<xsl:template match="make">
Make: <span style="color:#ff0000">
<xsl:value-of select="."/></span>
<br />
</xsl:template>
<xsl:template match="model">
Artist: <span style="color:#00ff00">
<xsl:value-of select="cars:getModel()"/></span>
<br />
</xsl:template>
</xsl:stylesheet>
Use of Saxon in Java:
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer transformer = tfactory.newTransformer(new StreamSource(new File(xslID)));
transformer.transform(new StreamSource(new File(sourceID)), new StreamResult(new File("html/report.html")));
As soon as the above is run, the errors occur.
Even if I substitute my custom Java class for an inbuilt java method in the XSLT, I get the same error. So it's obvious that the XSLT is not hooking up with Java.
The recommended approach is to add -TJ to the JVM options, or do something with FeatureKeys.TRACE_EXTERNAL_FUNCTIONS, but I've searched high and low on the web to find out more information on how to do this so that I can see why my XSLT is not hooking up with external Java functions, to no avail.
Please, can someone help me figure out what my problem is?
This is happening for Saxon PE and HE, I haven't tried other versions yet.
Thanks!
I should have used an earlier version of Saxon, Saxon-B 9.1.0.8, as pointed out here:
http://sourceforge.net/p/saxon/discussion/94027/thread/33492ab5/
As soon as I put the older jar files in the path, then the above XSLT called the java function correctly.
Related
I am using the Java javax.xml.transform library in my Scala Play application to perform a simple XSLT transformation on some XML. I am trying to remove the namespace from one of the elements, but I am getting an exception when I POST XML to the endpoint which does the transformation.
The method I have written to do the transformation is below:
def transformXml(xml: String, xslName: String): Try[String] = {
Try {
// Create transformer factory
val factory: TransformerFactory = TransformerFactory.newInstance()
// Use the factory to create a template containing the xsl file
val template: Templates = factory.newTemplates(new StreamSource(new FileInputStream(s"app/xsl/$xslName.xsl")))
// Use the template to create a transformer
val xformer: Transformer = template.newTransformer()
// Prepare the input for transformation
val input: Source = new StreamSource(new StringReader(xml))
// Prepare the output for transformation result
val outputBuffer: Writer = new StringWriter
val output: javax.xml.transform.Result = new StreamResult(outputBuffer)
// Apply the xslt transformation to the input and store the result in the output
xformer.transform(input, output)
// Return the transformed XML
outputBuffer.toString
}
}
Through putting printlns in my code, I have deduced that it is in fact failing at the xformer.transform(input, output) line. The XML I am passing in and the XSL file I am using to transform are below:
<?xml version="1.0"?>
<Message xmlns="http://foo.bar" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
<EnvelopeVersion>2.0</EnvelopeVersion>
<Header>
<MessageDetails>
...
...
...
</MessageDetails>
<SenderDetails/>
</Header>
<OtherDetails>
<Keys/>
</OtherDetails>
<Body>
</Body>
</Message>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:param name="ancestralNamespace" select="namespace-uri(/*[1])"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="ancestralNamespace" select="$ancestralNamespace"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*[contains(namespace-uri(),'foo.bar')]">
<xsl:param name="ancestralNamespace" select="namespace-uri(..)"/>
<xsl:element name="{local-name()}" namespace="">
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="ancestralNamespace" select="$ancestralNamespace"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
My expected output is this:
<?xml version="1.0"?>
<Message>
<EnvelopeVersion>2.0</EnvelopeVersion>
<Header>
<MessageDetails>
...
...
...
</MessageDetails>
<SenderDetails/>
</Header>
<OtherDetails>
<Keys/>
</OtherDetails>
<Body>
</Body>
</Message>
The error I get back from sending a POST request to my endpoint is this:
{
"statusCode": 500,
"message": "javax.xml.transform.TransformerException: java.lang.ArrayIndexOutOfBoundsException: -1"
}
I do not have much experience with XSLT and have inherited this code from someone else to try to debug, so if anyone with XML/XSLT experience could give me some help I would greatly appreciate it. The perplexing thing is that the person I got this problem from had written Unit Tests using this method (send in my example XML and get out the expected XML) and they passed so I don't know where to look next.
Right so after a few hours of debugging and fretting over this, I found the solution!
The default transformer which my Play application was using handles XSLT differently, and was getting confused at the line <xsl:param name="ancestralNamespace" select="namespace-uri(/*[1])"/>. What solved my issue was to use a different transformer. The one I found to work was Xalan (version 2.7.2), and after importing that into my project build file I hit the endpoint and the transformation was successful.
To import the version I found to work, add the following to your build:
"xalan" % "xalan" % "2.7.2" % "runtime"
I believe that the "runtime" section is the most important part, as it seems to overwrite what the application would normally use. I would guess that the reason my tests passed but my endpoint failed is that Scala Test runs with different configuration to runtime. Nothing else about my code had to be changed.
I hope this helps to stop anyone else from encountering this (admittedly rather unique) error! I ended up trawling through countless forums from as far back as 2002 before resorting to trying a different runtime configuration.
This is the input xml -
<payload id="001">
<termsheet>
<format>PDF</format>
<city>New York</city>
</termsheet>
</payload>
We are using Xalan for most of our xml transformations and we are on XSLT 1.0
I want to write a XSLT template which would convert the input to the below output -
<payload id="001">
<termsheet>
<format>pdf</format>
<city>Mr. ABC</city>
</termsheet>
</payload>
I tried lot of answers on SO, but can't get around this problem.
Apologies for not being clear, toLower was an over simplification. I want to use the city name and invoke a java method which will return a business contact from that city. I have updated the original question
I think that the simplest way is to use java extension with Xalan, you can write a simple java class that implements the business logic you need, and then call it from your xslt. The stylesheet is quite simple
<xsl:stylesheet version="1.0"
xmlns:java="http://xml.apache.org/xalan/java"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="java">
<xsl:template match='node() | #*'>
<xsl:copy>
<xsl:apply-templates select ='node()|#*'></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="termsheet/city">
<xsl:copy>
<xsl:value-of select='java:org.example.Card.getName(.)'/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
you also neeed to write the java class invoked
package org.example
public class Card {
public static String getName(String id) {
// put here your code to get what you need
return "Mr. ABC"
}
}
there are other ways to do that and you should really give an eye to the documentation about xalan extensions
I have a question on how to dynamically set the xpath expression in apply-templates select=?
<xsl:template match="CDS">
<xsl:result-document href="{$fileName}">
<xsl:copy>
<xsl:apply-templates select="$xpathCondition"/>
</xsl:copy>
</xsl:result-document>
</xsl:template>
This $xpathCondition am trying to set from java from properties file and setting to param in xsl.
transformer.setParameter("fileName", "Test.xml");
transformer.setParameter("xpathCondition", "CD[contains(Title/text(),'TEST')]");
$fileName is working as expected. But $xpathCondition is not working as expected.
There's no standard way of parsing a string as a dynamic XPath expression and executing it until you get to the xsl:evaluate instruction in XSLT 3.0. You really need to tell us which version you are using - the fact that you use xsl:result-document tells us that it's 2.0 or later, but beyond that we are guessing.
Many XSLT processors have an extension function called xx:eval() or similar.
The problem can be tackled in XSLT 3.0 using static parameters and shadow attributes. You can write:
<xsl:param name="xpathCondition" static="yes"/>
and then:
<xsl:apply-templates _select="{$xpathCondition}"/>
(Note the underscore in _select)
With 2.0 (or indeed 1.0) you can simulate this approach by doing a transformation on the stylesheet before executing it.
I have an XSL as shown
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" standalone="no" indent="yes"/>
<xsl:param name="V9_XML_PATH" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="measSchedule">
<xsl:variable name="match" select="/schedule/scheduleItem[measurements/measurement=document($V9_XML_PATH)/schedule/scheduleItem/measurements/measurement]"/>
<xsl:choose>
<xsl:when test="$match">
<xsl:copy-of select="$match"/>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I am using this to find the common records in 2 XML files whose records are in below format:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<schedule>
<scheduleItem scheduleId="1" startDate="2013-01-01" stopDate="2037-12-31">
<measurements>
<measurement>ADM010000</measurement>
</measurements>
<measPeriods>
<period day="0" duration="0" hour="0" interval="15" minutes="0"/>
</measPeriods>
</scheduleItem>
<!-- scheduleItem repeated n times -->
</schedule>
Here based on field /schedule/scheduleItem/measurements/measurement I am taking the intersection of 2 files.
But the problem is, when I am executing this in unix as:
xsltproc --stringparam V9_XML_PATH "/root/some/path/v9.xml" xsl.xslt v10.xml
its giving correct output, but when I started using this in my Java Program, I am getting exception java.lang.VerifyError
java.lang.VerifyError: (class: GregorSamsa$0, method: test signature: (IIIILcom/sun/org/apache/xalan/internal/xsltc/runtime/AbstractTranslet;Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;)Z) Incompatible type for getting or setting field
at GregorSamsa.template$dot$1()
at GregorSamsa.applyTemplates()
at GregorSamsa.applyTemplates()
at GregorSamsa.transform()
at com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.transform(Unknown Source)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)
at com.project.net.converter.XMLConverter.convert(XMLConverter.java:122)
Your stylesheet is obviously correct.
When you use Java to run the transformation, it uses XSLTC, the XSLT engine bundled with the JDK.
XSLTC parses your stylesheet and dynamically generates Java byte code which is then loaded and run to execute the transformation.
But somehow the generated XSLTC byte code is rejected by the Java VM, as indicated by the java.lang.VerifyError.
Therefore either XSLTC has a bug (option 1: likely), or the byte code verifier of your Java version rejects valid byte code (option 2: unlikely).
You could try to run your Java program with this JVM parameter :
java -Xverify:none ...
to turn of the byte code verifier. If it succeeds then option 2 is true.
Or you could try to use a different Java XSLT engine. For instance try Saxon; you probably only need to add the saxon jar to the classpath and your calling code should work without changes since you use the java.xml.transform interfaces. If this succeeds then option 1 was true.
This is most likely a classpath error. Check that you do not have several JAR files or directories with class files providing several times the same classes.
New to xslt, I wanted to set a value of string from java to this variable
<xsl:element name="input">
<xsl:attribute name="type">hidden</xsl:attribute>
<xsl:attribute name="name">trackId</xsl:attribute>
<xsl:attribute name="value"><xsl:value-of select="trackValue"/></xsl:attribute>
</xsl:element>
Is it in same manner as html or is it different apprach? Thanks for help and time.
Yes, you can pass values into your XSLT using parameters. What you would do is define a parameter near the top of your XSLT file:
<xsl:param name="trackValue" />
And then you would pass in a value for this when you run the transform:
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer xsltTransformer = transformerFactory.newTransformer(xsltSource);
xsltTransformer.setParameter("trackValue", parameterValue);
Then you can use it wherever you want to (note the use of the $ sign):
<xsl:attribute name="value"><xsl:value-of select="$trackValue"/></xsl:attribute>
XSL Transformation in Java with parameters