Some background... I am transforming a xml (condensedModel) file using a xslt (deployments.xslt).
I have a deployments.xslt file that is using a functions.xslt file that is included using href.
This is where the problem looks to be. It seems to not be able to find the functions.xslt file I made and am referencing within that file.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:variable name="lowercase" select="'abcdefghijklmnopqrstuvwxyz-_'"/>
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ. '"/>
<!-- <xsl:variable name="kubename" select="'name'"/>-->
<xsl:variable name="kubename" select="'app.kubernetes.io/name'"/>
<xsl:variable name="quot">"</xsl:variable>
<xsl:variable name="apos">'</xsl:variable>
<xsl:include href="functions.xslt"/>
Java:
private TransformerFactory factory = TransformerFactory.newInstance();
private Transformer transformer;
private void init(String xslt) throws IOException, TransformerConfigurationException, AggregatorException {
if (xslt == null || xslt.isEmpty()) {
throw new AggregatorException("XSLT was null or empty. Unable to transform condensed model to yaml");
}
transformer = factory.newTransformer(new StreamSource(new StringReader(xslt)));
Security.addProvider(new BouncyCastleProvider());
}
public String transform(TransformingYamlEnum transformerToUse) throws TransformerException, IOException, AggregatorException {
String xslt = determineXsltToUse(transformerToUse);
init(xslt);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
InputStream inputStream = new ByteArrayInputStream(condensedModel.getBytes(StandardCharsets.UTF_8));
process(inputStream, bos);
return bos.toString("UTF-8");
}
}
private void process(InputStream inputStream, OutputStream outputStream) throws TransformerException {
transformer.transform(
new StreamSource(inputStream),
new StreamResult(outputStream));
}
This is obviously within a jar and the deployments.xslt is being loaded in within my init(). The actual transformation takes place when I call trasform().
It does work when I hard code the path to let's say the desktop and manually place the functions.xslt file on the desktop. But as you can imagine, this is not a viable option. Any ideas about what I am doing wrong?
The XSLT processor cannot resolve a relative URI in xsl:include/xsl:import unless it knows the base URI of the stylesheet. If you supply the input as a StreamSource wrapping an InputStream, without also supplying a SystemId, then the base URI will be unknown.
In your example the XSLT processor has no idea where the stylesheet code came from.
Related
I'm writing a Java application that does a XML transformation using XSLT3, using Saxon-HE 10.5 (as a Maven project).
My XSLT sheet imports other XSLT sheets, using <xsl:import> (e.g. <xsl:import href="sheet1.xsl"/>). All of the XSLT sheets are located inside ./src/main/resources. However, when I try to run the program, I get a FileNotFound Exception from Saxon, since it is looking for the files at the project base directory.
I assume there is some way to change where Saxon is looking for the files, but I was not able to find out how to achieve this when using the s9api API.
Here's my Java code performing the transformation:
public void transformXML(String xmlFile, String output) throws SaxonApiException, IOException, XPathExpressionException, ParserConfigurationException, SAXException {
Processor processor = new Processor(false);
XsltCompiler compiler = processor.newXsltCompiler();
XsltExecutable stylesheet = compiler.compile(new StreamSource(this.getClass().getClassLoader().getResourceAsStream("transform.xsl")));
Serializer out = processor.newSerializer(new File(output));
out.setOutputProperty(Serializer.Property.METHOD, "text");
Xslt30Transformer transformer = stylesheet.load30();
transformer.transform(new StreamSource(new File(xmlFile)), out);
}
Any help is appreciated.
Edit:
My solution based on #Michael Kay's recommendation:
public void transformXML(String xmlFile, String output) throws SaxonApiException, IOException, XPathExpressionException, ParserConfigurationException, SAXException {
Processor processor = new Processor(false);
XsltCompiler compiler = processor.newXsltCompiler();
compiler.setURIResolver(new ClasspathResourceURIResolver());
XsltExecutable stylesheet = compiler.compile(new StreamSource(this.getClass().getClassLoader().getResourceAsStream("transform.xsl")));
Serializer out = processor.newSerializer(new File(output));
out.setOutputProperty(Serializer.Property.METHOD, "text");
Xslt30Transformer transformer = stylesheet.load30();
transformer.transform(new StreamSource(new File(xmlFile)), out);
}
}
class ClasspathResourceURIResolver implements URIResolver
{
#Override
public Source resolve(String href, String base) throws TransformerException {
return new StreamSource(this.getClass().getClassLoader().getResourceAsStream(href));
}
}
Saxon doesn't know the base URI of the stylesheet (it has no way of knowing, because you haven't told it), so it can't resolve relative URIs appearing in xsl:import/#href.
Normally I would suggest supplying a base URI in the second argument of new StreamSource(). However, since the main stylesheet is loaded using getResourceAsStream(), I suspect you want to load secondary stylesheet modules using the same mechanism, and this can be done by setting a URIResolver on the XsltCompiler object.
I am new to XML, XSLT and javax.xml
Currently my objective is to merge two XML files using XSLT Version 1.0 and everything works fine.
But I feel that there is a limitation in my code and I would like to get rid of it, if possible.
These are my resources:
'file1.xml'
'file2.xml'
'merge.xslt'
This is my merge method:
public ByteArrayOutputStream merge(final InputStream file1) {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer;
try {
transformer = tFactory.newTransformer(new StreamSource("merge.xslt"));
transformer.transform(new StreamSource(file1), new StreamResult(outputStream));
} catch (final TransformerConfigurationException e) {
LOG.warn("Problem occurred transforming files configuration issue", e);
} catch (final TransformerException e) {
LOG.warn("Problem occurred transforming files", e);
}
return outputStream;
}
This is how I am passing file2.xml inside the XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="lookup" select="document('/file2.xml')"/>
<xsl:template match="/">
Do the processing how I want
</xsl:template>
</xsl:stylesheet>
What I want to achieve is that, I would like to modify my merge method to pass file1.xml and file2.xml.
public ByteArrayOutputStream merge(final InputStream file1,final InputStream file2)
And I want to somehow pass this InputStream file2 to the XSLT, so that the limitation of reading the file from file system is eliminated.
Can someone guide me if this is possible and how to achieve it, I would really appreciate all the help.
Thank you.
I tried a small example, referred here XSLT Processing with Java : passing xml content in parameter
But it didn't work for me.
final InputStream file1 = new FileInputStream("file1.xml");
final InputStream file2 = new FileInputStream("file2.xml");
final TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer;
transformer = tFactory.newTransformer(new StreamSource("merge.xslt"));
transformer.setParameter("lookup", new StreamSource(file2));
transformer.transform(new StreamSource(file1), new StreamResult(new FileOutputStream("test.xml")));
And updated the XSLT as follows:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="lookup"/>
<xsl:template match="/">
Do the processing how I want
</xsl:template>
</xsl:stylesheet>
Error that I am getting is as follows:
ERROR: 'Invalid conversion from 'javax.xml.transform.stream.StreamSource' to 'node-set'.'
Exception in thread "main" javax.xml.transform.TransformerException: java.lang.RuntimeException: Invalid conversion from 'javax.xml.transform.stream.StreamSource' to 'node-set'.
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:755)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:359)
Also Using :
<xsl:param name="lookup"/>
Will I get access to file2.xml inside the XSLT.
After doing a lot of research and reading different posts and blogs, I was finally able to resolve my issue.
I referred the questions asked here and got the idea for doing this.
Pass document as parameter to XSL Translation in Java
The other solutions suggested in this thread didn't workout for me.
Here is what I did,
Used a URIResolver instead of parameter.
public class DocumentURIResolver implements URIResolver {
final Map<String, Document> _documents;
public DocumentURIResolver(final Map<String, Document> documents) {
_documents = documents;
}
public Source resolve(final String href, final String base) {
final Document doc = _documents.get(href);
return (doc != null) ? new DOMSource(doc) : null;
}
}
This is how I modified my method:
public ByteArrayOutputStream merge(final InputStream file1,final InputStream file2) {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer;
try {
transformer = tFactory.newTransformer(new StreamSource("merge.xslt"));
final DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final Document documentFile = db.parse(file2);
Map<String, Document> docs = new HashMap<String, Document>();
docs.put("lookup", documentFile);
transformer.setURIResolver(new DocURIResolver(docs));
transformer.transform(new StreamSource(file1), new StreamResult(outputStream));
} catch (final TransformerConfigurationException e) {
LOG.warn("Problem occurred transforming files configuration issue", e);
} catch (final TransformerException e) {
LOG.warn("Problem occurred transforming files", e);
}
return outputStream;
}
And this is how I refereed it in my XSLT:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="lookup" select="document('documentFile')"/>
<xsl:template match="/">
Do the processing how you want to
</xsl:template>
</xsl:stylesheet>
Since you do not want to use the document() function in your XSLT, you could merge your input files using DOM functions in Java.
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document merge = db.newDocument();
Element root = merge.createElement("root");
merge.appendChild(root);
Document d1 = db.parse(new File("file1.xml"));
Document d2 = db.parse(new File("file2.xml"));
root.appendChild(merge.importNode(d1.getDocumentElement(), true));
root.appendChild(merge.importNode(d2.getDocumentElement(), true));
The merged document could then be passed to XSLT if needed.
I am new to Junit Test, I have an xml file and xslt file as follow:
File.xml
<people>
<person>
<role>Parent</role>
<lastname>Smith</lastname>
<locations>
<location>
<city>Springfield</city>
<state>MA</state>
<unimportant-field>123</unimportant-field>
</location>
<location>
<city>Chicago</city>
<state>IL</state>
<unimportant-field>456</unimportant-field>
</location>
</locations>
</person>
<person>
<role>Child</role>
<lastname>Smith</lastname>
<locations>
<location>
<city>Springfield</city>
<state>IL</state>
<unimportant-field>789</unimportant-field>
</location>
<location>
<city>Chicago</city>
<state>IL</state>
<unimportant-field>000</unimportant-field>
</location>
<location>
<city>Washington</city>
<state>DC</state>
<unimportant-field>555</unimportant-field>
</location>
</locations>
</person>
The XSLT file is as follows:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="parent-location" match="person[role='Parent']/locations/location" use="concat(../../lastname, '|', city, '|', state)" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="person[role='Child']/locations/location[not(key('parent-location', concat(../../lastname, '|', city, '|', state)))]"/>
</xsl:stylesheet>
This is the java code that I am using to apply the above XSLT on XML.
public class ApplyXSLT {
/**
* #param args
* #throws TransformerException
* #throws FileNotFoundException
*/
public void process()throws FileNotFoundException, TransformerException
{
InputStream file1 = new FileInputStream("file.xml");
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer(new StreamSource("file.xslt"));
transformer.transform(new StreamSource(file1), new StreamResult(new FileOutputStream("output.xml")));
}
}
I am not sure how to write a Junit test for the above, if anyone can guide me. I would appreciate it. Thank you.
First of all, it is most recommended that you write Junit tests to test API utilities. I mean, you should first code a full-semantic abstraction: A class with properly named methods, parameters, and return values. The standard method main does not have any semantics: You cannot know what it does (its name has no specific meaning), nor how many parameters it requires, nor where it produces its results.
So, when you have a class like this:
public class MyTransformer
{
public void transform(InputStream in, OutputStream out)
throws IOException, TransformException
{...}
}
// If needed, You could also add a main method which delegates over the transform method.
... you may code your Junit tester like this:
public class MyTransformerTest
{
// Transforms an existing file producing the result in memory,
// and compares it with an existing, expected output file.
private void test(String inputFilename, String expectedOutputFilename)
throws IOException
{
try
{
ByteArrayOutputStream out=new ByteArrayOutputStream();
new MyTransformer().transform(new FileInputStream(inputFilename), out);
String expectedResult=readFileAsString(expectedOutputFilename);
assertEquals("File '" + inputFilename + "' was not transformed OK", expectedResult, out.toString("ISO-8859-1"));
}
catch (TransformerException e)
{
e.printStackTrace();
fail(e.toString());
}
}
#Test
public void testEmpty()
throws IOException
{
test("empty-input.xml", "empty-output.xml");
}
#Test
public void testOnePersons()
throws IOException
{
test("one-persons-input.xml", "one-persons-output.xml");
}
#Test
public void testTwoPersons()
throws IOException
{
test("two-persons-input.xml", "two-persons-output.xml");
}
}
Not that this tester is based on a general purpose test method which tests any file. So, you just have to write an input file and an expected output file for each case you are interested on.
This technique will ease for you the future maintainment of the tester: Rembember that every time you'll find a bug, first of all you'll have to add a method in the tester to reproduce it (which initially will fail, of corse). Then fix the code, and run the tester again until it doesn't fail.
One more note: I usually leave the IOExceptions in the throws clause, without catching them. This is because this exception is not your code's fault. So, it is not interesting to your tests. If an IOException arises while the tester is executing, it means that no input or output file could be instantiated: It occurred before your code was executed.
I got a xslt transformation done with something like this:
public static String transform(Source xml, String xsltPath) {
try {
InputStream is = MyClass.class.getResourceAsStream(xsltPath);
final Source xslt = new StreamSource(is);
final TransformerFactory transFact = TransformerFactory.newInstance();
final Transformer trans = transFact.newTransformer(xslt);
final OutputStream os = new ByteArrayOutputStream();
final StreamResult result = new StreamResult(os);
trans.transform(xml, new StreamResult(os));
final String theResult = result.getOutputStream().toString();
return theResult;
}
catch (TransformerException e) {
return null;
}
}
As you can see xslt is loaded from resources. The function together with the transformation files i need are bundled in a library and this works as long as the library is stand alone from a main method or so.
However if this library is bundled with a webapplication and deployed in Jetty/Tomcat it gets a bit complicated. As long as the transformation files in it self do not reference any other files from resources there is no problem but with files like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet>
<xsl:import href="import_file1.xsl" />
<xsl:import href="import_file2.xsl" />
<xsl:template name="aTtemplate">
<xsl:for-each select="document('import_file3.xml')">
...
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The imports cannot be resolved and the document from the for each loop cannot be found. In Tomcat a workaround is to put the files inside the $TOMCAT/bin directory but that is not a suitable solution for us. Is there any method to get this resources recursively out of the lib?
I use a java application to call a xslt to do a xml transformation. The xslt file will generate a message and terminate the process if some condition happens. However, my java application couldn't catch the error message generated by xslt, it only catch an exception with general information - "Stylesheet directed termination".
Here is my java code:
SAXTransformerFactory saxTFactory = ((SAXTransformerFactory) tFactory);
// Create a TransformerHandler for stylesheet.
File f2 = new File(styleSheetPath);
TransformerHandler tHandler2 = saxTFactory.newTransformerHandler(new StreamSource(f2));
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.setContentHandler(tHandler2);
reader.setProperty("http://xml.org/sax/properties/lexical-handler", tHandler2);
CharArrayWriter outputWriter = new CharArrayWriter();
Result result = new StreamResult(outputWriter);
tHandler2.setResult(result);
try
{
reader.parse(new InputSource(new StringReader(XMLinput)));
}
catch(Exception ee)
{
dsiplay(ee.getMessage())
throw ee;
}
How can I catch the error message from xslt?
I tried to write a class:
private class MyErrorHandler extends DefaultHandler{
public void error(SAXParseException e)
{
System.out.println("error method "+e.getMessage());
}
public void fatalError(SAXParseException e)
{
System.out.println("fatal error method "+e.getMessage());
}
public void warning(SAXParseException e)
{
System.out.println("warning method "+e.getMessage());
}
and
MyErrorHandler myHandler = new MyErrorHandler();
reader.setErrorHandler(myHandler);
It didn't work.
Do you have any suggestion?
It looks as if you have placed an error handler on the source document XML parser, but are trying to catch errors from the transformer. So place the error handler on the Transformer rather than the XMLReader
tHandler2.getTransformer().setErrorListener(myHandler);
IIRC, I've always kept it simple and used Transformer directly rather than go for ContentHandler/TranformerHandler.
As alternatives you could go for an implementation specific extension or cause a special URI to be resolved and handle that (although as XSLT is a functional language, I guess it could technically resolve a URI that wont actually impact the result).
In what way is the XSLT generating an error and terminating? This might be crucial since the
<xsl:message>
tag does not always write to the standard output like you might expect. Especially with Saxon.
Thank you so much for the replying. Here is my xslt code:
<xsl:template match="*[not(*) and ((text() and not(normalize-space(text()) != '')) or .='')
and name(.) != 'para' and name(.) != 'recordDelimiter' and name(.) != 'physicalLineDelimiter' and name(.) != 'fieldDelimiter' ]">
<xsl:message terminate="yes">
<xsl:call-template name="output_message3_fail">
<xsl:with-param name="current_node" select="name(.)"/>
</xsl:call-template>
</xsl:message>
</xsl:template>
<xsl:template name="output_message3_fail">
<xsl:param name="current_node"/>
<xsl:text>
Conversion to EML2.1 failed:
The EML 2.0.1 document has empty string value in some elements which shouldn't be empty string in EML 2.1.0 sepcification - </xsl:text>
<xsl:value-of select="$current_node"/>
</xsl:template>
I use <xsl:message> to generate the error message.