Exception handling in Saxonica URIResolver - xslt

I am using saxonica EE version for xslt transformation, and throw an exception from custom URI Resolver class (given below), it is working fine for #include but same not working for #document(),
is there anyway we can stop transformation by throwing the exception while resolving document().
is it possible to apply URI resolver to document() during the compilation itself(while generating SEF).
public class CustomURIResolver implements URIResolver {
#Override
public Source resolve(String href, String base) {
String formatterOrlookUpKey = getKey(href);
if (formatterMap.containsKey(formatterOrlookUpKey)) {
return new StreamSource(new StringReader(formatterMap.get(formatterOrlookUpKey)));
} else {
throw new RuntimeException("did not find the lookup/formatter xsl " + href+" key:"+formatterOrlookUpKey);
}
}}
XSLT compilation :
Processor processor = new Processor(true);
XsltCompiler compiler = processor.newXsltCompiler();
compiler.setJustInTimeCompilation(false);
compiler.setURIResolver(new CigURIResolver(formatterMap));
XsltExecutable stylesheet = compiler.compile(new StreamSource(new StringReader(xsl)));
stylesheet.export(destination);
Transformation
Processor processor = new Processor(true);
XsltCompiler compiler = processor.newXsltCompiler();
compiler.setJustInTimeCompilation(true);
XsltExecutable stylesheet = compiler.compile(new StreamSource(new StringReader(sef)));
final StringWriter writer = new StringWriter();
Serializer out = processor.newSerializer(writer);
out.setOutputProperty(Serializer.Property.METHOD, "xml");
out.setOutputProperty(Serializer.Property.INDENT, "yes");
Xslt30Transformer trans = stylesheet.load30();
trans.setURIResolver(new CigURIResolver(formatterMap));
trans.setErrorListener(errorHandler);
trans.transform(new StreamSource(new StringReader(xml)), out);
Object obj = out.getOutputDestination();

I'm a little surprised by the observed effect, and would need a repro to investigate it. But I'm also a bit surprised that you're choosing to throw a RuntimeException, rather than a TransformerException which is what the URIResolver interface declares. If you want to explore this further please raise a support request with runnable code.
The rules for document() are a bit complex because of the XSLT 1.0 legacy of "recoverable errors": you might find that doc() behaves more predictably.
As regards compile-time resolution of doc() calls, Saxon does have an option to enable that, but it doesn't play well with SEF files: generally having external documents in a SEF file gets very messy, especially if for example you have several global variables bound to different parts of the same document.

Related

Saxonica Generate SEF file from xslt and apply the same for transformation

I am trying to find/know the correct approach to save sef in memory and use the same for transformation
Found below two approaches to generate sef file:
1. using xsltpackage.save(File) : it works fine but here need to save content to a File which doesn't suit our requirement as we need store in memory/db.
2. XsltExecutable.export() : it generated file but if i use the same .sef file for transformation, i am getting empty content as output(result).
I use xsl:include and document in xslt and i resolved them using URI resolver.
I am using below logic to generate and transform.
Note: i am using Saxon ee (trial version).
1.XsltExecutable.export()
public static String getCompiledXslt(String xsl, Map<String, String> formatterMap) throws SaxonApiException, IOException {
try(ByteArrayOutputStream destination = new ByteArrayOutputStream()){
Processor processor = new Processor(true);
XsltCompiler compiler = processor.newXsltCompiler();
compiler.setURIResolver(new CigURIResolver(formatterMap));
XsltExecutable stylesheet = compiler.compile(new StreamSource(new StringReader(xsl)));
stylesheet.export(destination);
return destination.toString();
}catch(RuntimeException ex) {
throw ex;
}
}
use the same SEF for transformation:
Processor processor = new Processor(true);
XsltCompiler compiler = processor.newXsltCompiler();
if (formatterMap != null) {
compiler.setURIResolver(new CigURIResolver(formatterMap));
}
XsltExecutable stylesheet = compiler.compile(new StreamSource(new StringReader(standardXsl)));
Serializer out = processor.newSerializer(new File("out4.xml"));
out.setOutputProperty(Serializer.Property.METHOD, "xml");
out.setOutputProperty(Serializer.Property.INDENT, "yes");
Xslt30Transformer trans = stylesheet.load30();
if (formatterMap != null) {
trans.setURIResolver(new CigURIResolver(formatterMap));
}
trans.transform(new StreamSource(new StringReader(sourceXMl)), out);
System.out.println("Output written to out.xml");
}
when use the sef generated from above export method to transform , i am getting empty content..same code works fine with sef generated from XsltPackage.save().
UPDATE : solved the issue by setting false to property (by default it is true) compiler.setJustInTimeCompilation(false);
There's very little point (in fact, I would say there is no point) in saving a SEF file in memory. It's much better to keep and reuse the XsltExecutable or XsltPackage object rather than exporting it to a SEF structure and then reimporting it. The only reason for doing an export/import is if the exporter and importer don't share memory.
You can do it, however: I think the only thing you need to change is that you need to close the destination stream after writing to it. Saxon tries to stick to the policy "Anyone who creates a stream is responsible for closing it"

Camel XSLT transformation

I am trying to use the XSLT component to do dynamic transformation from XML. Is it possible to pass in a java variable in the URI as XSLT template?
For example:
from("direct:foo").
to("xslt:${fee}").
to("direct:output");
foo - is a XML payload,
fee - XSLT template stored as java.lang.String,
output - xml payload
You can assign your variable into message header by some conditions:
.setHeader("TemplateLocation").constant("OSGI-INF/xsl/pretty.xsl")
After, you can use Recipient List EIP:
.recipientList().simple("xslt:${header.TemplateLocation}")
or you can use toD:
.toD("xslt:${header.TemplateLocation}")
Working example:
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.routeId("xsltTest")
.log(LoggingLevel.INFO, "XML input : \n${body}")
.setHeader("TemplateLocation").constant("OSGI-INF/xsl/pretty.xsl")
//.recipientList().simple("xslt:${header.TemplateLocation}")
.toD("xslt:${header.TemplateLocation}")
.to("log:end?level=INFO&showAll=true&multiline=true");
}
};
}
And there is no way to use a string variable as an xslt template, as far as I know.
Based on my knowledge
Your XSLT poller has a dynamic expression as subdirectory (${fee}).
As far as I know you cannot have a dynamic from address in a Camel
route. Because this would mean that you could consume from multiple
endpoints.
You can have it as separate file and call it like this
to("xslt:file:///foo/bar.xsl").
For more details XSLT
You cannot use a dynamic stylesheet (dynamic content) with the XSL component of Camel.
The most dynamic thing you can do is a dynamic reference to a static file like this:
.toD("xslt:${expressionWithFileReference}")
However, you can simply call a Java bean to do what you want and call it from the route:
.bean(javaBeanReference or new YourJavaBean())
In the Bean you can use Camel annotations to inject header(s), properties and the body into a method. Whatever you need from the current Camel Exchange.
public void yourMethod(
#Header(headername) String parameterName,
#Body Type parameterName) {
...
}
As Camel does not have support for dynamic XSLT input stream, I had to create my own Transformer. This might help someone
Here is my code snippet. I used camel processor as below
#Override
public void process(Exchange exchange) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
Target target = xmlMapper.readValue(transform(getInputStreamFromDocument(xmlPayload), new ByteArrayInputStream(xsltTemplate.getBytes())), target.class);
}
public byte[] transform(InputStream dataXML, InputStream inputXSL)
throws TransformerException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(inputXSL));
StreamSource in = new StreamSource(dataXML);
StreamResult out = new StreamResult(bos);
transformer.transform(in, out);
return bos.toByteArray();
}

How do I define the default XPath namespace in Saxon 9.5 EE in Java code?

How do I define the equivalent of the xpath-default-namespace, for example:
<xsl:stylesheet version="2.0" xpath-default-namespace="http://schemas.xmlsoap.org/soap/envelope/"
in the following Java code snippet for Saxon EE 9.5?
public String transform(String request) {
try {
ProfessionalConfiguration config = new ProfessionalConfiguration();
config.setExtensionElementNamespace("http://yeah.com", "com.MyFactory");
config.registerExtensionFunction(new MyVariable());
EnterpriseTransformerFactory factory = new EnterpriseTransformerFactory();
factory.setConfiguration(config);
Source xslt = new StreamSource(new File("text.xsl"));
Transformer transformer = factory.newTransformer(xslt);
Source input = new StreamSource(new File("test.xml"));
StringWriter result = new StringWriter();
transformer.transform(input, new StreamResult(result));
return result.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
You can't change the default xpath namespace from your Java code, only from your XSLT code.
You say you can't edit the XSLT stylesheets but you can. They are XML documents and can be transformed, and you have a transformation language and transformation engine available to you. If there's no other way of solving this, transform the stylesheets before executing them.

Saxon XSLT .Net Transformation: What to give in BaseURI when xml and xsl both are passed as strings

This is the code I have for Saxon Transformation of XSLT files which accepts xml and xslt and returns a transformed string. I can have either xsl 1.0 or 2.0 get processed through this function.
DocumentBuilder requires a BaseURI, even if I don't have any file format. I have provided "c:\\" as the BaseURI, inspite I have nothing to do with this directory.
Is there any better way to achieve this thing or write this function?
public static string SaxonTransform(string xmlContent, string xsltContent)
{
// Create a Processor instance.
Processor processor = new Processor();
// Load the source document into a DocumentBuilder
DocumentBuilder builder = processor.NewDocumentBuilder();
Uri sUri = new Uri("c:\\");
// Now set the baseUri for the builder we created.
builder.BaseUri = sUri;
// Instantiating the Build method of the DocumentBuilder class will then
// provide the proper XdmNode type for processing.
XdmNode input = builder.Build(new StringReader(xmlContent));
// Create a transformer for the stylesheet.
XsltTransformer transformer = processor.NewXsltCompiler().Compile(new StringReader(xsltContent)).Load();
// Set the root node of the source document to be the initial context node.
transformer.InitialContextNode = input;
StringWriter results = new StringWriter();
// Create a serializer.
Serializer serializer = new Serializer();
serializer.SetOutputWriter(results);
transformer.Run(serializer);
return results.ToString();
}
If you think that the base URI will never be used (because you never do anything that depends on the base URI) then the best strategy is to set a base URI that will be instantly recognizable if your assumption turns out to be wrong, for example "file:///dummy/base/uri".
Choose something that is a legal URI (C:\ is not).

Internet Explorer 9 and XSLT

I have some javascript code that, based on the browser you're using, applies an XSL transformation to some XML received. This works in all browsers except IE9. Although there's a provision in the logic for IE (to use tranformNode instead of new XSLTProcessor()) it would seem that IE9 does not define transformNode anymore.
I've been searching for some time to see if this is a problem for others without any luck. Which is puzzling and makes me think I'm doing something terribly wrong.
Here's the code that works with IE7/8 (from jstree - although slightly modified for clarity):
xm = document.createElement('xml');
xs = document.createElement('xml');
xm.innerHTML = xml;
xs.innerHTML = xsl;
xm.transformNode(xs.XMLDocument)
All I could find regarding IE9 and XSLT is that "it has been changed to be more standards compliant". I think it was referring to the way that the transformations were done, not so much the API.
From the author of jsTree (which uses XSLT transformations to render XML source data to the tree):
if(window.ActiveXObject) {
var xslt = new ActiveXObject("Msxml2.XSLTemplate");
var xmlDoc = new ActiveXObject("Msxml2.DOMDocument");
var xslDoc = new ActiveXObject("Msxml2.FreeThreadedDOMDocument");
xmlDoc.loadXML(xml);
xslDoc.loadXML(xsl);
xslt.stylesheet = xslDoc;
var xslProc = xslt.createProcessor();
xslProc.input = xmlDoc;
xslProc.transform();
callback.call(null, xslProc.output);
return true;
}
http://code.google.com/p/jstree/issues/detail?id=907&q=IE9&colspec=ID%20Type%20Status%20Priority%20Owner%20Summary