I apologize for the elementary question. I have an XML file, as well as an XSL to translate it into another format (KML). Within the KML I wish to inject a dynamic attribute which is not present in the original XML document. I want to emit a node like the following:
<NetworkLinkControl>
<message>This is a pop-up message. You will only see this once</message>
<cookie>sessionID={#sessionID}</cookie>
<minRefreshPeriod>5</minRefreshPeriod>
</NetworkLinkControl>
In particular I want the {#sessionID} text to be replaced with a dynamic value that I insert into the template somehow (i.e. is NOT part of the source XML document that the XSLT is transforming).
Here's the code I'm using to marshal the KML:
DomainObject myObject = ...;
JAXBContext context = JAXBContext.newInstance(new Class[]{DomainObject.class});
Marshaller xmlMarshaller = context.createMarshaller();
xmlMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
TransformerFactory transFact = TransformerFactory.newInstance();
// converts from jaxb XML representation into KML
Templates displayTemplate = transFact.newTemplates(new StreamSource(new File("conf/jaxbkml.xsl")));
Result outputResult = new StreamResult(System.out);
TransformerHandler handler =
((SAXTransformerFactory) transFact).newTransformerHandler(displayTemplate);
handler.setResult(outputResult);
Transformer transformer = handler.getTransformer();
// TODO: what do I actually fill in here to ensure that the session ID comes through
// in the XSLT document? I can't make heads or tails of the javadocs
transformer.setOutputProperty("{http://xyz.foo.com/yada/baz.html}sessionID", "asdf");
xmlMarshaller.marshal(myObject, handler);
I have gathered that there is a way to substitute in values dynamically in the XSLT via Attribute Value Templates and I assume that there is a way to hookup the transformer's properties to be used with these Attribute Value Templates, but I don't quite see how it's done. Could someone shed some light? Thanks.
Thanks to #jtahlborn for setting me on the right track. It is possible to do this, but I wasn't putting all the pieces together. First, define xsl:param.
<!-- give it a default value if none is set -->
<xsl:param name="sessionID" select="''"/>
Second, insert a reference to this xsl:param. If you need to embed it within the content of a node, as I did, use an xsl:value-of node.
<cookie>sessionID=<xsl:value-of
select="$sessionID"/></cookie>
Otherwise, if you need to embed it within an attributes string:
<img src="{$sessionID}/sample.gif"/>
Next, pass in a value for that xsl:param from within Java.
Result outputResult = new StreamResult(outputStream);
TransformerHandler handler =
((SAXTransformerFactory) transFact).newTransformerHandler(displayTemplate);
Transformer transformer = handler.getTransformer();
// Here is where the parameter is bound.
transformer.setParameter("sessionID", sessionID);
handler.setResult(outputResult);
xmlMarshaller.marshal(listWrapper, handler);
The attribute value templates are part of your XSL, not part of your XML, so what you are attempting won't work. You could use xpath to select the element which matches the pattern "sessionID={#sessionID}" and replace that with the text of your choice.
i believe you can set parameters for the stylesheet using the Transformer.setParameter() method which can then be referenced in the stylesheet using the syntax "{$param}", see examples here.
Related
I use tagsoup as (SAX) XMLREader and set the namespace feature to false. This parser is used to feed the Transformer as SAX Source. Complete code:
final TransformerFactory factory = TransformerFactory.newInstance();
final Transformer t = factory.newTransformer(new StreamSource(
getClass().getResourceAsStream("/identity.xsl")));
final XMLReader p = new Parser(); // the tagsoup parser
p.setFeature("http://xml.org/sax/features/namespaces", false);
// getHtml() returns HTML as InputStream
final Source source = new SAXSource(p, new InputSource(getHtml()));
t.transform(source, new StreamResult(System.out));
This results in something like:
< xmlns:html="http://www.w3.org/1999/xhtml">
<>
<>
<>
<>
< height="17" valign="top">
Problem is that the tag names are blank. The XMLReader (tagsoup parser) does report an empty namespaceURI and empty local name in the SAX methods ContentHandler#startElement and ContentHandler#endElement. For a not namespace aware parser this is allowed (see Javadoc).
If i add a XMLFilter which copies the value of the qName to the localName, everything goes fine. However, this is not what i want, i expect this works "out of the box". What am i doing wrong? Any input would be appreciated!
I expect this works "out of the box". What am i doing wrong?
What you are doing wrong is taking a technology (XSLT) that is defined to operate over namespace-well-formed XML and attempting to apply it to data that it is not intended to work with. If you want to use XSLT then you must enable namespaces, declare a prefix for the http://www.w3.org/1999/xhtml namespace in your stylesheet, and use that prefix consistently in your XPath expressions.
If your transformer understands XSLT 2.0 (e.g. Saxon 9) then instead of declaring a prefix and prefixing your element names in XPath expressions, you can put xpath-default-namespace="http://www.w3.org/1999/xhtml" on the xsl:stylesheet element to make it treat unprefixed element names as references to that namespace. But in XSLT 1.0 (the default built-in Java Transformer implementation) your only option is to use a prefix.
I am constructing an XML DOM Document with a SAX parser. I have written methods to handle the startCDATA and endCDATA methods and in the endCDATA method I construct a new CDATA section like this:
public void onEndCData() {
xmlStructure.cData = false;
Document document = xmlStructure.xmlResult.document;
Element element = (Element) xmlStructure.xmlResult.stack.peek();
CDATASection section = document.createCDATASection(xmlStructure.stack.peek().characters);
element.appendChild(section);
}
When I serialize this to an XML file I use the following line to configure the transformer:
transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "con:setting");
Never the less no <![CDATA[ tags appear in my XML file and instead all backets are escaped to > and <, this is no problem for other tools but it is a problem for humans who need to read the file as well. I am positive that the "con:setting" tag is the right one. So is there maybe a problem with the namespace prefix?
Also this question indicates that it is not possible to omit the CDATA_SECTION_ELEMENTS property and generally serialize all CDATA nodes without escaping the data. Is that information correct, or are there maybe other methods that the author of the answer was not aware of?
Update: It seems I had a mistake in my code. When using the document.createCDATASection() function, and then serializing the code with the Transformer it DOES output CDATA tags, even without the use of the CDATA_SECTION_ELEMENTS property in the transformer.
It looks like you have a namespace-aware DOM. The docs say you need to provide the Qualified Name Representation of the element:
private static String qualifiedNameRepresentation(Element e) {
String ns = e.getNamespaceURI();
String local = e.getLocalName();
return (ns == null) ? local : '{' + ns + '}' + local;
}
So the value of the property will be of the form {http://your.conn.namespace}setting.
In this line
transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "con:setting");
try replacing "con:setting" with "{http://con.namespace/}setting"
using the appropriate namespace
Instead of using a no-op Transformer to serialize your DOM tree you could try using the DOM-native "load and save" mechanism, which should preserve the CDATASection nodes from the DOM tree and write them as CDATA sections in the resulting XML.
DOMImplementationLS ls = (DOMImplementationLS)document.getImplementation();
LSOutput output = ls.createLSOutput();
LSSerializer ser = ls.createLSSerializer();
try (FileOutputStream outStream = new FileOutputStream(...)) {
output.setByteStream(outStream);
output.setEncoding("UTF-8");
ser.write(document, output);
}
I'm doing a xstl transformation with saxon from an XML document.
The doc is not standard-valid XML, and I want to preserve all <![CDATA[< elements that are found in there.
However using the .xsl file for transformation with
Transformer trans = TransformerFactory.newInstance().newTransformer(new StreamSource(new File("foo.xsl"));
trans.transform(new StreamSource(new File("foo.xml"), new StreamResult(new File("output.xml")));
results in stripping out these CDATA entries. How can I prevent this?
You can't, as the distinction whether a text originated from a cdata section is not available in the datamodel used by xslt. You can however define in your stylesheet that certain result elements are to be wrapped inside cdata. This is done using the cdata-section-elements attribute of the xsl:output element in your stylesheet.
Consider using Andrew Welch's LexEv tool (bundled I believe with KernowForSaxon), which preprocesses CDATA start and end tags into something different (processing instructions perhaps?) that's visible in the XSLT data model and thus available to the application.
Mostly continued from this question: XSLT: CSV (or Flat File, or Plain Text) to XML
So, I have an XSLT from here: http://andrewjwelch.com/code/xslt/csv/csv-to-xml_v2.html
And it converts a CSV file to an XML document. It does this when used with the following command on the command line:
java -jar saxon9he.jar -xsl:csv-to-xml.csv -it:main -o:output.xml
So now the question becomes: How do I do I do this in my Java code?
Right now I have code that looks like this:
TransformerFactory transformerFactory = TransformerFactory.newInstance();
StreamSource xsltSource = new StreamSource(new File("location/of/csv-to-xml.xsl"));
Transformer transformer = transformerFactory.newTransformer(xsltSource);
StringWriter stringWriter = new StringWriter();
transformer.transform(documentSource, new StreamResult(stringWriter));
String transformedDocument = stringWriter.toString().trim();
(The Transformer is an instance of net.sf.saxon.Controller.)
The trick on the command line is to specify "-it:main" to point right at the named template in the XSLT. This means you don't have to provide the source file with the "-s" flag.
The problem starts again on the Java side. Where/how would I specify this "-it:main"? Wouldn't doing so break other XSLT's that don't need that specified? Would I have to name every template in every XSLT file "main?" Given the method signature of Transformer.transform(), I have to specify the source file, so doesn't that defeat all the progress I've made in figuring this thing out?
Edit: I found the s9api hidden inside the saxon9he.jar, if anyone is looking for it.
You are using the JAXP API, which was designed for XSLT 1.0. If you want to make use of XSLT 2.0 features, like the ability to start a transformation at a named template, I would recommend using the s9api interface instead, which is much better designed for this purpose.
However, if you've got a lot of existing JAXP code and you don't want to rewrite it, you can usually achieve what you want by downcasting the JAXP objects to the underlying Saxon implementation classes. For example, you can cast the JAXP Transformer as net.sf.saxon.Controller, and that gives you access to controller.setInitialTemplate(); when it comes to calling the transform() method, just supply null as the Source parameter.
Incidentally, if you're writing code that requires a 2.0 processor then I wouldn't use TransformerFactory.newInstance(), which will give you any old XSLT processor that it finds on the classpath. Use new net.sf.saxon.TransformerFactoryImpl() instead, which (a) is more robust, and (b) much much faster.
I'm currently working on a project which uses XSL-Transformations to generate HTML from XML.
On the input fields there are some attributes I have to set.
Sample:
<input name="/my/xpath/to/node"
class="{/my/xpath/to/node/#isValid}"
value="{/my/xpath/to/node}" />
This is pretty stupid because I have to write the same XPath 3 times... My idea was to have some kind of post-processor for the xsl file so i can write:
<input xpath="/my/xpath/to/node" />
I'm using using something like that to transform my xml
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import org.dom4j.Document;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.DocumentSource;
public class Foo {
public Document styleDocument(
Document document,
String stylesheet
) throws Exception {
// load the transformer using JAXP
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(
new StreamSource( stylesheet )
);
// now lets style the given document
DocumentSource source = new DocumentSource( document );
DocumentResult result = new DocumentResult();
transformer.transform( source, result );
// return the transformed document
Document transformedDoc = result.getDocument();
return transformedDoc;
}
}
My hope was that I can create a Transformer object out of a Document object. But it seems like it has to be a file path - at least I can't find a way to use a Document directly.
Anyone knows a way to achieve what I want?
Thanks
Why not skip the postprocessing, and use this in XSLT:
<xsl:variable name="myNode" select="/my/xpath/to/node" />
<input name="/my/xpath/to/node"
class="{$myNode/#isValid}"
value="{$myNode}" />
That gets you closer.
If you really want to DRY (as apparently you do), you could even use a variable myNodePath for which you generate the value from $myNode via a template or user-defined function. Does the name really have to be an XPath expression (as opposed to a generate-id()?)
Update:
Example code:
<xsl:variable name="myNode" select="/my/xpath/to/node" />
<xsl:variable name="myNodeName">
<xsl:apply-template mode="generate-xpath" select="$myNode" />
</xsl:variable>
<input name="{$myNodeName}"
class="{$myNode/#isValid}"
value="{$myNode}" />
The template for generate-xpath mode is available on the web... For example, you can use one of the templates for that purpose that comes with Schematron. Go to this page, download iso-schematron-xslt1.zip, and look at iso_schematron_skeleton_for_xslt1.xsl. (If you're able to use XSLT 2.0, then download that zip archive.)
In there you'll find a couple of implementations of schematron-select-full-path, which you can use for generate-xpath. One version is precise and is best for consumption by a program; another is more human-readable. Remember, for any given node in an XML document, there are multitudes of XPath expressions that could be used to select only that node. So you probably won't be getting the same XPath expression that you came in with at the beginning. If this is a deal-breaker, you may want to try another approach, such as ...
generating your XSLT stylesheet (the one you've already been developing, call it A) with another XSLT stylesheet (call it B). When B generates A, B has the chance to output the XPath expression both as a quoted string, and as an expression that will be evaluated. This is basically preprocessing in XSLT instead of postprocessing in Java. I'm not really sure if it would work in your case. If I knew what the input XML looks like, it would be easier to figure that out I think.
My hope was that I can create a Transformer object out of a Document object. But it seems like it has to be a file path - at least I can't find a way to use a Document directly.
You can create a Transformer object from a document object:
Document stylesheetDoc = loadStylesheetDoc(stylesheet);
// load the transformer using JAXP
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(
new DOMSource( stylesheetDoc )
);
Implementing loadStylesheetDoc is left as an excercise. You can build the stylesheet Document internally or load it using jaxp, and you could even write the changes to it you need as another XSLT transform transforming the stylesheet.