XSLT 3.0: Create element only if input element has data - xslt

We are using XSLT internally to map a single input schema to a large number of distinct output schemas. Most of the servers using these schemas return errors on empty elements, so empty elements cannot appear in the output. In many cases, a piece of data in the input will simply map to a piece of data in the output, possibly with a minor transformation, e.g.:
<!-- Input -->
<ourns:DateCreated>2021-12-09</ourns:DateCreated>
<!-- Output -->
<otherns:CreatedDt>2021-12-09<otherns:CreatedDt>
The XSLT for this is straightforward, even with the "no empty elements" requirement:
<xsl:if test="ourns:DateCreated != ''">
<otherns:CreatedDt>
<xsl:value-of select="ourns:DateCreated/text()"/>
</otherns:CreatedDt>
</xsl:if>
However, when you're mapping thousands of elements across hundreds of schemas, this business of wrapping everything in <xsl:if/> gets tiresome. You could add a function, say:
<xsl:function name="ourfn:createElementIfData">
<xsl:param name="tag" as="xs:string"/>
<xsl:param name="data" as="xs:string"/>
<xsl:if test="$data != ''">
<xsl:element name="{$tag}"><xsl:value-of select="$data"/></xsl:element>
</xsl:if>
</xsl:function>
...
<xsl:sequence select="ourfn:createElementIfData('otherns:CreatedDt', ourns:DateCreated)"/>
But this function will only work if it lives in a stylesheet where both namespaces are declared. If you wanted to share it (as you probably would such a general-purpose function), you would end up needing to either
Declare every possible otherns in the shared stylesheet, or
Pass in the fully qualified namespace on every invocation,
both of which feel wrong.
This seems like such a common use case that I feel like there must be a simple way to do it. What am I missing?

You could define your basic rules like this:
<xsl:template match="ourns:DateCreated"
mode="copySimpleElement">
<otherns:CreatedDt>{.}</otherns:CreatedDt>
</xsl:template>
and then override it for empty elements:
<xsl:template match="*[. = '']"
mode="copySimpleElement"
priority="20"/>
and then you just have to apply-templates to the relevant elements in the appropriate mode.

You haven't shown any context but perhaps <xsl:template match="ourns:*[not(has-children())]"/> suffices to prevent any processing of the elements without content and adding <xsl:template match="ourns:DateCreated[has-children()]" expand-text="yes"><otherns:CreatedDt>{.}</otherns:CreatedDt></xsl:template> suffices to map the non-empty element to the wanted output element.
Of course <xsl:template match="ourns:*[not(has-children())]"/> could be set up as <xsl:template match="*[not(has-children())]"/> if the rule can be applied to input elements from any namespace or can take a sequence of patterns with <xsl:template match="ourns:*[not(has-children())] | ourns2:*[not(has-children())]"/>.
All the above assumes you are processing those nodes through other templates e.g. the identity transformation <xsl:mode on-no-match="shallow-copy"/>.
If you want to take the function approach I would check if you can pass in an xs:QName:
<xsl:function name="ourfn:createElementIfData">
<xsl:param name="node-name" as="xs:QName"/>
<xsl:param name="data" as="xs:string"/>
<xsl:if test="$data != ''">
<xsl:element name="{$node-name}" namespace="{namespace-uri-from-QName($node-name)}"><xsl:value-of select="$data"/></xsl:element>
</xsl:if>
</xsl:function>
and use e.g. <xsl:sequence select="ourfn:createElementIfData(QName('http://yourothernamespace/', 'otherns:CreatedDt'), ourns:DateCreated)"/>.

Related

check if repeating node is empty in xslt

I have xml like defined below . The node EducationDetails can repeat (unbounded).
<PersonalDetailsResponse>
<FirstName></FirstName>
<LastName></LastName>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
</PersonalDetailsResponse>
I want to create another xml from the above one using xslt.
My requirement is, if there is no data in any of the EducationDetails child nodes , then the resulting xml has to get data from another source.
My problem is , I am not able to check if all the EducationDetails child nodes are empty.
Since variable value cannot be changed in xslt , I tried using saxon with below code.
xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
<xsl:variable name="emptyNode" saxon:assignable="yes" select="0" />
<xsl:when test="count(ss:education) > 0">
<xsl:for-each select="ss:education">
<xsl:if test="not(*[.=''])">
<saxon:assign name="emptyNode">
<xsl:value-of select="1" />
</saxon:assign>
</xsl:if>
</xsl:for-each>
<xsl:if test="$emptyNode = 0">
<!-- Do logic if all educationdetails node is empty-->
</xsl:if>
</xsl:when>
But it throwing exception "net.sf.saxon.trans.XPathException: Unknown extension instruction " .
It looks like saxon 9 jar is required for it ,which I am not able to get from my repository.
Is there a simpler way to check if all the child nodes of are empty.
By empty I mean, child nodes might be present, but no value in them.
Well, if you use <xsl:template match="PersonalDetailsResponse[EducationDetails[*[normalize-space()]]">...</xsl:template> then you match only on PersonalDetailsResponse element having at least one EducationDetails element having at least one child element with non whitespace data. As you seem to use an XSLT 2.0 processor you can also use the perhaps clearer <xsl:template match="PersonalDetailsResponse[some $ed in EducationDetails/* satisfies normalize-space($ed)]">...</xsl:template>.
Or perhaps if you want a variable inside the template use
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="not(EducationDetails/*[normalize-space()])"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
With XSLT 2.0 the use of some or every satisfies might be easier to understand e.g.
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="every $dt in EducationDetails/* satisfies not(normalize-space($dt))"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
But usually writing templates with appropriate match conditions saves you from using xsl:if or xsl:choose inside of a template.

XSLT to HTML transformation. generate-id() error in Qt5

I do a XSLT to HTML transformation using the method recommended in Qt doc:
QXmlQuery query(QXmlQuery::XSLT20);
query.setFocus(QUrl("myInput.xml"));
query.setQuery(QUrl("myStylesheet.xsl"));
query.evaluateTo(out);
Inside XSLT I use generate-id() method to generate unique ids for different DIV blocks. It works perfectly in Qt4.8, but not in Qt5.4
¿Anyone knows a reason for that, and how to solve this?
Edit: I get no error. What I get in Qt5 is always the same ID, while in Qt4 I get a different, unique ID each time i call generate-id().
I generate the ID this way:
<xsl:variable name="tc_id" select="generate-id()"/>
And I use it this way:
<xsl:value-of select="$tc_id"/>
This is the cpp code doing the transformation:
// generate output string
QXmlQuery query(QXmlQuery::XSLT20);
QString output;
query.setFocus(QUrl(_final_output_filepath.c_str()));
query.setQuery(xslt_code.c_str());
query.evaluateTo(&output);
Edit 2:
When I use this code...
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:xdt="http://www.w3.org/2005/xpath-datatypes">
<xsl:template match="/">
<xsl:for-each select="trial/testsuite">
<xsl:for-each select="testcase">
<xsl:variable name="tc_index" select="position()"/>
<xsl:variable name="tc_id" select="generate-id(.)"/>
<xsl:value-of select="$tc_id"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...I get always the same ID.
<xsl:template match="/">
<xsl:value-of select="generate-id()"/> --
<xsl:value-of select="generate-id()"/> --
<xsl:value-of select="generate-id()"/>
Thanks for the snippet. This was indeed why I kept bugging you about giving a reproducible example.
What happens here is that you call the generate-id() function multiple times without changing the context node. The default argument for this function is the context node (here: /, or the root node).
Unless you change the context node, this function is deliberately designed to be stable. That means that, if called repeatedly with the same argument (also meaning: the same default argument, the same context), it must return the same string.
It is also designed such that it always returns a unique string per distinct node. Two nodes are distinct if they have different position in the document (i.e., they are distinct even if they look the same, but appear on multiple places).
Bottom line: you didn't hit a bug in the Qt implementation of XSLT 2.0, but you hit a resolved issue that was a bug and was incidentally used as a feature.
If you require a unique ID in XSLT 2.0, and you are bound to giving the same context, there is probably something else that changes: for instance, you can be in a loop going over a set of numbers or strings. You can use this info to create a unique string.
Another "hack" in XSLT 2.0 is to use a single point in the specification where determinism is not guaranteed: on creation of new nodes:
<xsl:function name="my:gen-id" as="xs:string">
<xsl:sequence select="generate-id(my:gen-id-getnode())" />
</xsl:function>
<xsl:function name="my:gen-id-getnode" as="element()">
<node />
</xsl:function>
This small function touches on some advanced concepts and recently, people discussing in the XSL Working Group, have agreed that optimizing away the creation of the new node is allowed if the node identity is not required. Whether or not a processor correctly detects this is unclear.
In XSLT 3.0 a new property has been introduced on xsl:function: #new-each-time, which informs the processor that the function should be evaluated each time and not get inlined.
Update: tests with Qt 5.5 or 5.4
I have tested a variant of your code with Qt because I couldn't believe that identity (which is a core concept of XSLT) doesn't work with it. So, I created a document with similar-looking nodes of all six types (I ignored namespace nodes, as support for it is optional).
Input test document
<root test="bla">
<?pi do-something ?>
<row></row>
<!-- comment here -->
<row>content</row>
<row>content</row>
<row id="bla" xml:id="bla">content</row>
</root>
XSLT 2.0 code
Code slightly adjusted due to Qt not supporting #separator correctly.
<xsl:stylesheet
xmlns:my="my-functions"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:value-of select="string-join(
('gen-id(',
my:decorate(.), '[', my:depth(.), ']', ') = ', generate-id(.),
'
'), '')" />
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<!-- remove prev. and use this if you think for-each is different -->
<!--xsl:template match="/">
<xsl:for-each select="//node() | //#*">
<xsl:value-of select="string-join(
('gen-id(',
my:decorate(.), '[', my:depth(.), ']', ') = ', generate-id(.),
'
'), '')" />
</xsl:for-each>
</xsl:template-->
<xsl:function name="my:depth" as="xs:string">
<xsl:param name="node" />
<xsl:sequence select="
string(count($node/(/)//node()[$node >> .]) + 1)" />
</xsl:function>
<xsl:function name="my:decorate">
<xsl:param name="node" />
<xsl:sequence select="
($node/self::text(), 'text')[2],
($node/self::element(), concat('Q{}', name($node)))[2],
($node/self::document-node(), 'document')[2],
($node/self::comment(), 'comment')[2],
($node/self::attribute(), concat('#', name($node)))[2],
($node/self::processing-instruction(), 'processing-instruction')[2]
" />
</xsl:function>
</xsl:stylesheet>
Output using Exselt
gen-id(Q{}root[1]) = x1e2
gen-id(#test[2]) = x1e2a0
gen-id(processing-instruction[2]) = x1p3
gen-id(Q{}row[3]) = x1e4
gen-id(comment[4]) = x1c5
gen-id(Q{}row[5]) = x1e6
gen-id(text[6]) = x1t7
gen-id(Q{}row[7]) = x1e8
gen-id(text[8]) = x1t9
gen-id(Q{}row[9]) = x1e10
gen-id(#id[10]) = x1e10a0
gen-id(#xml:id[10]) = x1e10a1
gen-id(text[10]) = x1t11
Output using Qt 5.5 or 5.4
I used the pre-build xmlpatterns.exe and called it as xmlpatterns test.xsl input.xml, but its code uses the same libraries you are using:
gen-id(Q{}root[1]) = T756525610
gen-id(#test[2]) = T756525620
gen-id(text[2]) = T756525630
gen-id(processing-instruction[3]) = T756525640
gen-id(text[4]) = T756525650
gen-id(Q{}row[5]) = T756525660
gen-id(text[6]) = T756525670
gen-id(comment[7]) = T756525680
gen-id(text[8]) = T756525690
gen-id(Q{}row[9]) = T7565256100
gen-id(text[10]) = T7565256110
gen-id(text[11]) = T7565256120
gen-id(Q{}row[12]) = T7565256130
gen-id(text[13]) = T7565256140
gen-id(text[14]) = T7565256150
gen-id(Q{}row[15]) = T7565256160
gen-id(#id[16]) = T7565256170
gen-id(#xml:id[16]) = T7565256180
gen-id(text[16]) = T7565256190
gen-id(text[17]) = T7565256200
As this shows, stripping space does not work with Qt, as it considers them text nodes. But as you can also see, the generate-id function works for each and every node, whether they are processing instructions, text nodes, look the same, are empty elements etc. It didn't matter whether:
Using generate-id() vs generate-id(.)
Putting it in xsl:for-each or normal template processing
Using a variable to store the result prior to using
Hiding generate-id() inside another function
All returned the same, valid result.
UPDATE: Workaround
There's a relative expensive, yet workable workaround that you may be able to use, assuming that the generated ID in itself must be unique per document and node, but is not used in another way then for uniqueness (for instance, if used for cross-references, this will work).
<xsl:variable name="doc" select=".//node()" />
<xsl:function name="my:gen-id" as="xs:integer">
<xsl:param name="elem" as="node()" />
<xsl:sequence select="
for $i in 1 to count($doc)
return if($doc[$i] is $elem then $i else ())" />
</xsl:function>
This obviously has a performance hit, but if your documents are not that big and/or you do not call this function too often, it should be ok. You may consider creating a key if the subset for which you need this is defined.
A call to generate-id() returns a generated id of the context node and of course, if the context does not change, you will always get the same value.
I found out a solution for this problem. It seems to be some differences between Qt4 and Qt5 XSLT transformation engine in Linux.
The following code worked fine in Qt4, but not in Qt5. tc_id has always the same value:
<xsl:for-each select="testcase">
<xsl:choose>
<xsl:when test="#result != 'pass'">
<xsl:variable name="tc_id" select="generate-id(.)"/>
<xsl:attribute name="onClick">
ExpandCollapse('<xsl:value-of select="$tc_id"/>');
</xsl:attribute>
<div style="display:none">
<xsl:attribute name="id"><xsl:value-of select="$tc_id"/></xsl:attribute>
</div>
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
And the following code works fine both in Qt4 and Qt5:
<xsl:for-each select="testcase">
<xsl:choose>
<xsl:when test="#result != 'pass'">
<xsl:attribute name="onClick">
ExpandCollapse('<xsl:value-of select="generate-id(.)"/>');
</xsl:attribute>
<div style="display:none">
<xsl:attribute name="id"><xsl:value-of select="generate-id(.)"/></xsl:attribute>
</div>
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
It seems there is some problem with declaring the variable.

xslt pass parameters throuh all apply-template calls

In a rather complex xslt file some elements are to be processed twice. This is done by a template with a paramater.
<xsl:template macht="x">
<xsl:param name="modus"/>
<!-- comon things to do for both cases -->
<xsl:choose>
<xsl:when test="$modus='case1'"> <!-- things to do in case 1 --> </xsl:when>
<xsl:when test="$modus='case2'"> <!-- things to do in case 2 --> </xsl:when>
</xsl:choose>
</xsl:template>
The problem is: I cannot simply apply or call this template directly. The element x (for which this template with these two cases is for) is often at a quite low level of the xml input. Almost all ancestor elements have to be processed (in both cases) before it actually comes to x.
The call for the two cases is at the allmost top level.
In html it would be like this
<body>
<h1>Case 1</h1>
<xsl:apply-templates><xsl:with-parameter name="modus" select="case1"/>
<h1>Case 2</h1>
<xsl:apply-templates><xsl:with-parameter name="modus" select="case2"/>
</body>
So. How can I make sure, that the parameter reaches the template for x?
Of course, I could replace all
<xsl:apply-templates/>
calls within the templates for every single ancestor element of x by
<xsl:param name="modus">
<!-- What ever content here -->
<xsl:apply-templates><xsl:with-parameter name="modus" select="$modus"/></apply-templates>
But that would mean a lot of effort. Is there a better way to do this?
XSLT 2.0 has tunnel parameters e.g. with
<xsl:apply-templates>
<xsl:with-param name="modus" tunnel="yes" select="'foo'"/>
</xsl:apply-templates>
and
<xsl:template match="bar">
<xsl:param name="modus" tunnel="yes"/>
...
</xsl:template>
you don't have to pass on the parameter explicitly in the templates for ancestors of bar. So using an XSLT 2.0 processor like Saxon 9 you can do that.

XSLT for-each counter - how to access data

for performance testing purposes I want to take a small XML file and create a bigger one from it - using XSLT. Here I plan to take each entity (Campaign node in the example below) in the original XML and copy it n times, just changing its ID.
The only way I can think of to realize this, is a xsl:for-each select "1 to n". But when I do this I do not seem to be able to access the entity node anymore (xsl:for-each select="campaigns/campaign" does not work in my case). I am getting a processor error: "cannot be used here: the context item is an atomic value".
It seems that by using the "1 to n" loop, I am loosing the access to my actual entity. Is there any XPath expression that gets me access back or does anyone have a completely different idea how to realize this?
Here is what I do:
Original XML
<campaigns>
<campaign id="1" name="test">
<campaign id="2" name="another name">
</cmpaigns>
XSLT I try to use
<xsl:template match="/">
<xsl:element name="campaigns">
<xsl:for-each select="1 to 10">
<xsl:for-each select="campaigns/campaign">
<xsl:element name="campaign">
<xsl:copy-of select="#*[local-name() != 'id']" />
<xsl:attribute name="id"><xsl:value-of select="#id" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
Define a variable as the first thing in the match, like so:
<xsl:variable name="foo" select="."/>
This defines a variable $foo of type nodeset. Then access it like this
<xsl:for-each select="$foo/campaigns/campaign">
...
</xsl:for-each>

XSL: How best to store a node in a variable and then use it in future xpath expressions?

I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>