Can I set <xsl:output/> attributes conditionally in an XSLT stylesheet? - xslt

I'm trying to automate some xslt transformations, and I need a way to set xsl:output attributes based on the content in the XML files being transformed.
Specifically, I want to look into the XML files, grab the lang attribute from the root element, and set attributes based on the lang value.
I've tried the following:
<xsl:param name="language">
<xsl:value-of select="//*/#lang"/>
</xsl:param>
<xsl:output method="xml" xmlns:saxon="http://icl.com/saxon" encoding="utf-8">
<xsl:choose>
<xsl:when test="$language != 'ja'">
<xsl:attribute name="saxon:character-representation"><xsl:value-of select="'native'"/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="saxon:character-representation"><xsl:value-of select="'hex'"/></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:output>
...but my parser informs me that the xsl:output element must be empty.
Is there a way to do something like this in the context of a style sheet, or am I going to have to manipulate these attributes at a higher level?

You've tagged this docbook, which I suspect is why you're still using the ancient Saxon 6.5.5 processor and its http://icl.com/saxon namespace. However, the docbook stylesheets can be made to work with a modern version of Saxon, which allows you to choose serialization attributes dynamically in an xsl:result-document instruction.
An alternative is to override the xsl:output properties from the Java API or command line. However, that's awkward in your case where you want to make the properties dependent on something in the source document.

Related

How to select stylesheet to import based on content in XML

I'm trying to import a stylesheet based on the data source defined in the XML it is transforming.
XML1
<Root>
<Source>Facility A</Source>
</Root>
XML2
<Root>
<Source>Facility A</Source>
</Root>
I tried it like this:
<xsl:stylesheet>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="Root/Source/text()='Facility A'">
<xsl:import href="Stylesheet for Facility A.xml"/>
</xsl:when>
<xsl:when test="Root/Source/text()='Facility B'">
<xsl:import href="Stylesheet for Facility B.xml"/>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The transform just failed. Not sure how else to approach this.
This is a bit like saying that you want to import different modules into a Java program based on its input data: you're missing the fact that you can't read the input data until you have assembled/compiled the program.
In XSLT 3.0 you can invoke a stylesheet dynamically using the transform() function, but that may be more complicated than you actually need.
Another approach is to use a tool like XProc to control your workflow - or there are many other frameworks that enable you to orchestrate the execution of different stylesheets against different source documents. Camel seems to be popular at the moment, though I haven't used it, and I don't know how it compares with alternatives.

can I use an XSLT function to duplicate adding the xsl:attribute statements in similar templates

I have a series of templates which are processing various type of mapping elements differently. But, I want a similar set of attributes to be added to the generated element. Is there a way to do this with an XSLT function, or is there another recommended way?
Here is an example of a mapping template, this one is for ones that have no source values. Those three attributes that are added to the generated element, I don't want to duplicate in each mapping template. Is there a way to avoid doing that?
<xsl:template match="mapping[source/not(*)]">
<xsl:element name="{{destination/attribute/text()}}" namespace="{{destination/attribute/#namespace}}">
<xsl:attribute name="mapping-key"><xsl:value-of select="generate-id(.)"/></xsl:attribute>
<xsl:attribute name="override"><xsl:value-of select="(#override, 'false')[1]"/></xsl:attribute>
<xsl:attribute name="single-value"><xsl:value-of select="(#single-value, 'false')[1]"/></xsl:attribute>
<!-- add custom stuff to the element specific to this template -->
</xsl:element>
</xsl:template>
Thanks,
-tj
You can indeed use xsl:attribute-set here, to allow you to re-use groups of attributes in different places.
For example, see this XSLT (I removed the namespace creation for brevity)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:attribute-set name="attrSet">
<xsl:attribute name="mapping-key" select="generate-id(.)"/>
<xsl:attribute name="override" select="(#override, 'false')[1]"/>
<xsl:attribute name="single-value" select="(#single-value, 'false')[1]"/>
</xsl:attribute-set>
<xsl:template match="mapping[source/not(*)]">
<xsl:element name="{destination/attribute/text()}" use-attribute-sets="attrSet">
<!-- add custom stuff to the element specific to this template -->
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note, I have used the slightly simplified format for creating attributes (with select on the xsl:attribute statement) as available in XSLT 2.0.
Also, I note, you used double-curly braces in creating the element names {{destination/attribute/text()}}", where it should only be single, but I am guessing you may be doing some text pre-processing of the XSLT before using it, perhaps?

XSLT 1.0 - How to test an attribute contained in a parameter in an xsl:when statement

I have an XSLT template that passes a chunk of HTML into parameter. I need to pass that parameter to another template where it will be transformed one of two ways, depending upon the ID attribute of the top level HTML element contained in the parameter. Is it possible to test the content of a parameter with Xpath in XSLT 1.0? Here's my HTML:
<div id="someID">There could be many child nodes here</div>
Here's my XSLT test:
<xsl:when test="$content/#id = 'someID'">...</xsl:when>
Saxon 6.5.3 fails on this and tells me to switch to use exsl or specify version 1.1 (which has no impact), but I'd love to know if there's a clever way to achieve this using the tools I have in place.
The parameter is declared in the template as follows: <xsl:param
name="content"><xsl:apply-imports/></xsl:param>.
Strictly speaking, that's a variable, not a parameter. And its content is a result tree fragment. As the error message says, you must convert it to a node-set before you can process it further (in XSLT 1.0).
So do something like:
<xsl:variable name="content-rtf">
<xsl:apply-imports/>
</xsl:variable>
<xsl:variable name="content" select="exsl:node-set($content-rtf)" />
<xsl:choose>
<xsl:when test="$content/div/#id = 'someID'">
<!-- some result -->
</xsl:when>
<xsl:otherwise>
<!-- another result -->
</xsl:otherwise>
</xsl:choose>
after adding:
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
to your opening <xsl:stylesheet> tag.
Untested because a reproducible example was not provided.
<xsl:when test="$content/div[#id = 'someID']">...</xsl:when>

How to prevent self closing tags as well empty tags after transforming

I have in an input file:
<a></a>
<b/>
<c>text</c>
I need to converting this to string. Using transformer I am getting below output:
<a/> <!-- Empty tags should not collapse-->
<b/>
<c>text</c>
If I use xslt and output method is "HTML", I get the below output:
<a></a> <!-- This is as expected-->
<b></b> <!-- This is not expected-->
<c>text</c>
I want the structure same as in input file. It is required in my application since I need to calculate index and it will be very difficult to change the index calution logic.
What would be the correct XSLT to use?
What XSLT processor? XSLT is merely a language to transform xml so "html output" is dependent on the processor.
I'm going to guess this first solution is too simple for you but i've had to use this to avoid processing raw html
<xsl:copy-of select="child::node()" />
as this should clone the raw input.
In my case, I have used the following to extract all nodes that had the raw attribute:
<xsl:for-each select="xmlData//node()[#raw]">
<xsl:copy-of select="child::node()" />
</xsl:for-each>
Other options:
2) Add an attribute to each empty node depending on what you want it to do later ie role="long", role="short-hand".
3)
Loop through each node (xsl:for-each)
<xsl:choose>
<xsl:when test="string-length(.)=0"> <!-- There is no child-->
<xsl:copy-of select="node()" />
</xsl:when>
<xsl:otherwise>
...whatever normal processing you have
</xsl:otherwise>
4) Redefine your problem. Both are valid XHTML/XML, so perhaps your problem can be reframed or fixed elsewhere.
Either way, you may want to add more information in your question so that we can reproduce your problem and test it locally.
P.S. Too much text/code to put in a comment, but that's where this would belong.
A possible alternative is to use disable-output-escaping like this:
<xsl:text disable-output-escaping="yes"><a></a></xsl:text>
But I understand that this is a dirty solution...

Automating exslt:node-set?

Not sure if this is possible, but trying set up something that doesn't make me have to type exslt:node-set when pulling values from a dynamically created node block. I am storing the entire set of nodes in a variable, and wrapping it in exslt:node-set, but why does it not work when I then try to pull from it. Is this possible?
<xsl:variable name="LANG">
<xsl:variable name="tmp">
<xsl:element name="foo">
<xsl:element name="bar">Hello</xsl:element>
</xsl:element>
</xsl:variable>
<xsl:value-of select="exslt:node-set($tmp)"/>
</xsl:variable>
<!-- Love to be able to do this -->
<xsl:value-of select="$LANG/foo/bar"/>
<!-- This does work -->
<xsl:value-of select="exslt:node-set($LANG)/foo/bar"/>
In XSLT 1.0, the variable defined as in your example are called result tree fragments (RTF) and you can only use xsl:copy-of to copy the entire fragment to the result tree or xsl:value-of to copy the entire content. Example
<xsl:copy-of select="$LANG"/>
If you want treat the variable as a temporary tree you need the node-set() extension.
The common way to deal with static tree fragments (like lookup tables) in XSLT 1.0 is to define them as children of the stylesheet root elements (using a custom namespace). Then you can use the document() function to retrieve the wanted value.
Note If you are using Saxon (v>6.5), you could simply set the stylesheet version to 1.1 and you will be able to manage the RTF without any node-set extension.
[XSLT 1.0]
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:empo="http://stackoverflow.com/users/253811/empo">
<empo:LANG>
<empo:foo>
<empo:bar>Hello</empo:bar>
</empo:foo>
</empo:LANG>
<xsl:template match="/">
<xsl:variable name="LANG" select="document('')/*/empo:LANG"/>
<xsl:value-of select="$LANG/empo:foo/empo:bar"/>
</xsl:template>
</xsl:stylesheet>