





I recently had the need on a project to be able to take a pre-built XML document template and substitute certain parameters into it. Interface code received XML from one system and needed to insert variables relating to the interface itself (such as a reason code for the message) into the document before passing it onwards to the third party system. The XML data source and the interface itself remained decoupled so the interface didn't know the XML format and the XML generator didn't know why it was generating XML. An example source document might look like this:
<Message reasonCode="{$reasonCode}">
<Update id="211" newValue="abc123"/>
</Message>The Apache Velocity project provides a great templating tool for text content but I didn’t really want to treat the XML document as text, with all the encoding and escaping problems that can bring, so I searched for a straightforward solution within the pure XML JAXP domain.
Ideally, I didn’t want to include any additional libraries for this simple task so my thoughts turned to the tools I had built in and I was reminded of the parameter support in XSLT that I had used previously to pass values into a stylesheet transformation. But my placeholders were in the source document, and no sensible stylesheet transformation would be able to substitute their values: so my source document needed to become a stylesheet.
I created an empty stylesheet template.xsl which looked like this and stored it with my interfacing class:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> </xsl:template> </xsl:stylesheet>
and parsed it into a DOM document ready for reuse:
Document cachedTemplateStylesheet = getDocumentBuilder().parse(getClass().getResource("template.xsl").toExternalForm());The idea was then to substitute my source document into the root xsl:template so that it became the output of the stylesheet, no matter what the input document.
// Insert the source document into the stylesheet
Document sourceDocument;
Document stylesheet = (Document) cachedTemplateStylesheet.cloneNode(true);
Element template = (Element) stylesheet.getElementsByTagNameNS("http://www.w3.org/1999/XSL/Transform", "template").item(0);
template.appendChild(stylesheet.importNode(sourceDocument.getDocumentElement(), true));The only preparation remaining was to make sure parameters were declared in the stylesheet so they could be inserted:
Map<String,String> parameters = Collections.singletonMap("reasonCode", "DO_STUFF");
final Element root = stylesheet.getDocumentElement();
for (final String key : parameters.keySet()) {
final Element param = stylesheet.createElementNS("http://www.w3.org/1999/XSL/Transform", "param");
param.setAttribute("name", key);
root.insertBefore(param, template);
}
With all that done, I would end up with a stylesheet document in memory which would look like this, if it were printed to a text format:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="reasonCode"/>
<xsl:template match="/">
<Message reasonCode="{$reasonCode}">
<Update id="211" newValue="abc123"/>
</Message>
</xsl:template>
</xsl:stylesheet>
This stylesheet ignores its source document, so it can be applied to an empty docment to produce my original interface document, with parameters substituted. This generic routine could be reused for many different interfaces as all it needs is a generic template stylesheet, the source message document, and a map of parameters. The full generic method (without exeception handling) is reproduced below:
Document substituteVariables(Document doc, Map<String, String> parameters) {
// First of all turn our XML from the database into a stylesheet by inserting it into a template stylesheet
final Document stylesheet = getTemplateStylesheet();
// Insert the original document as the root template of the stylesheet (i.e. the output)
final Element template = (Element) stylesheet.getElementsByTagNameNS("http://www.w3.org/1999/XSL/Transform", "template").item(0);
template.appendChild(stylesheet.importNode(doc.getDocumentElement(), true));
// Insert the variable names as parameter declarations
final Element root = stylesheet.getDocumentElement();
for (final String key : parameters.keySet()) {
final Element param = stylesheet.createElementNS("http://www.w3.org/1999/XSL/Transform", "param");
param.setAttribute("name", key);
root.insertBefore(param, template);
}
// Create a transformer with all the variables set
final Transformer transformer = transformerFactory.newTransformer(new DOMSource(stylesheet));
for (final Map.Entry<String,String> param : parameters.entrySet()) {
transformer.setParameter(param.getKey(), param.getValue());
}
// Transform an empty document to produce the output which is just XSLT parameter substitution into our original document
final DOMResult outputTarget = new DOMResult();
transformer.transform(new DOMSource(getDocumentBuilder().newDocument()), outputTarget);
return (Document) outputTarget.getNode();
}
This method allows any XML document to be used as a template, provided it contains placeholders for variables to be substituted into attributes. The placeholders must use the XSLT parameter format i.e. {$parameterName}. Substituting variables into element content rather than attributes would require the source document to use the xsl:valueOf element. This is a bit ugly, but luckily isn't required for this application.