how to do a dynamic boolean checking in xslt - xslt

From my root template for unique account value, call goes to trans template were in the input xml I will have multiple nodes, my requirement is that once the call goes from root template to trans template, if a match of accountId found in between multiple elements, account details template is called only once, irrespective of other match found. I need a solution for above requirement.
input sample :
<elements><accountId>1</accountId></elements>
<elements><accountId>1</accountId></elements>
<elements><accountId>2</accountId></elements>
<elements><accountId>2</accountId></elements>
<elements><accountId>3</accountId></elements>
The below line should be wrapped under some code so its called only once
<xsl:call-template name="Account_details" />
Below is my complete code for xsl
<xsl:template match="/">
<xsl:variable name="unique-accounts" select="//*/*/*/accountId/text()[generate-id()=generate-id(key('account-by-id', .)[1])]"/>
<xsl:for-each select="$unique-accounts">
<xsl:variable name="currentValue" select="current()"/>
<xsl:apply-templates select="//trans">
<xsl:with-param name="passCurrentValue" select="$currentValue"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="trans">
<xsl:param name="passCurrentValue" />
<xsl:variable name="booleanValue" select="true()"/>
<xsl:for-each select="elements">
<xsl:if test="$passCurrentValue=/*/*/accountId">
<xsl:if test="$booleanValue">
<xsl:call-template name="Account_details" />
<xsl:variable name="booleanValue" select="false()"></xsl:variable>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="Account_details">
.............
</xsl:template>

With the code
<xsl:template match="/">
<xsl:variable name="booleanCheck" select="true"></xsl:variable>
the variable booleanCheck is a node-set (XSLT 1.0) or sequence of elements named true below the document node /. So unless your XML input has a root element named true the value is an empty node-set respectively empty sequence. If you want to select a boolean value then use <xsl:variable name="booleanValue" select="true()"/>. See http://www.w3.org/TR/xpath/#function-true. Then you can test <xsl:if test="$booleanValue">. That should explain how to use boolean values, whether that fits into your context I am not sure.

Related

How to make template call work in xslt 1.0

I have this following template which i am not sure correct or not
<xsl:template match="text()" name="createName">
<xsl:param name="Type"/>
<xsl:if test="contains(Type,'NEW')">
<xsl:value-of select="concat('New','Goal')"/>
</xsl:if>
<xsl:if test="contains(Type,'AMD')">
<xsl:value-of select="concat('Amended','Goal')"/>
</xsl:if>
</xsl:template>
Calling this template here
<xsl:element name="PackageName">
<xsl:call-template name="createName">
<xsl:with-param name="Type" select="s0:PIXField/s0:TransactionID"/>
</xsl:call-template>
</xsl:element>
s0:PIXField/s0:TransactionID = BPA201605311506452806320060A1AMD
I want to create PackageName element with value 'Amended Goal'.
But for now i am getting empty PackageName. dont know where my code fails.
Please help.
If you want to access a parameter, you need to use the $ prefix. That is to say, do contains($Type,'NEW') and not contains(Type,'NEW'). The latter is looking for a child element called Type, and not the passed in parameter
Try this template. Note that you don't really need to use xsl:value-of with concat to output the text.
<xsl:template match="text()" name="createName">
<xsl:param name="Type"/>
<xsl:if test="contains($Type,'NEW')">
<xsl:text>New Goal</xsl:text>
</xsl:if>
<xsl:if test="contains($Type,'AMD')">
<xsl:text>Amended Goal</xsl:text>
</xsl:if>
</xsl:template>
Also note, you may not actually need the match attribute on the template either, if the template is only ever being called as a named template.
Also consider using xsl:choose / xsl:when instead of xsl:if.
You call this template in exactly the same way as shown in your question
<xsl:element name="PackageName">
<xsl:call-template name="createName">
<xsl:with-param name="Type" select="s0:PIXField/s0:TransactionID"/>
</xsl:call-template>
</xsl:element>
Nothing changes there at all.

Varying amount of iterations in XSL recursive loop within a for loop

I'm generating a CSV file from an XML using XSL. The XML contains Main elements with child elements Tags, which in turn contain varying amounts of child elements Tag. A part of the XML looks for example like this:
<Main>
<Tags>
<Tag>tag1</Tag>
<Tag>tag2</Tag>
<Tag>tag3</Tag>
</Tags>
</Main>
<Main>
<Tags>
<Tag>tag1</Tag>
<Tag>tag2</Tag>
<Tag>tag3</Tag>
<Tag>tag4</Tag>
<Tag>tag5</Tag>
<Tag>tag6</Tag>
</Tags>
</Main>
In the XSL I have a for each loop that goes through all my Main elements of my XML file. I want to print the values for all the Tag elements. I do this in another for-each loop which is inside the major loop. However, I always want to iterate 10 times, regardless of the amount of Tag elements. I want to print some text in each of the remaining iterations when I have exceeded the amount of printable Tag.
This is the output I'm after:
tag1,tag2,tag3,1,1,1,1,1,1,1,
tag1,tag2,tag3,tag4,tag5,tag6,1,1,1,1,
After the Tag for each loop, I'm calling a template, providing a variable with the amount of Tag in Tags. I then want the template to call itself recursively until it has done the varying amount of remaining iterations for the Tag elements of the current Main element. The amount of Tag elements changes with each Main iteration, which I suspect is a problem in my current solution (which causes my transformation software, Notepad++ with XML Tools, to crash):
<xsl:template match="/">
<xsl:for-each select="Main">
<xsl:for-each select="Tags/Tag">
<xsl:value-of select="Tag"/>
<xsl:text>,</xsl:text>
</xsl:for-each>
<xsl:call-template name="repeatable">
<xsl:with-param name="tagamount" select="count(Tags/*)"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="repeatable">
<xsl:param name="tagamount"/>
<xsl:param name="index" select="0" />
<xsl:text>1,</xsl:text>
<xsl:if test="not($index = 10-$tagamount)">
<xsl:call-template name="repeatable">
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Does anyone have any idea if it's possible to do this type of varying iteration, or am I out of luck?
Edit:
I managed to solve it. The problem was I had forgotten to pass on the variable tagamount with each recursive call. See my solution further below.
I couldn't wrap my head around your code. How about something simpler?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="sep" select="','"/>
<xsl:variable name="LF" select="'
'"/>
<xsl:variable name="filler" select="'1,2,3,4,5,6,7,8,9,10'"/>
<xsl:template match="/">
<xsl:for-each select="rt/Main/Tags">
<xsl:for-each select="Tag">
<xsl:value-of select="concat(., $sep)"/>
</xsl:for-each>
<xsl:value-of select="substring($filler, 2*count(Tag)+1)"/>
<xsl:value-of select="$LF"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note:
1. Your XML is missing a root element: I am using "rt" as a placeholder.
2. For testing purposes, I have changed "1,1,1,..." into "1,2.3...".
Here is one way to do it.
This XSLT stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!-- Sets the number of iterations per Tags element. -->
<xsl:variable name="maximum" select="10"/>
<!-- Matches all the Tags elements and calls a recursive template, intializing the count to 1. -->
<xsl:template match="//Tags">
<xsl:call-template name="output-tags">
<xsl:with-param name="count" select="1"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- A recursive template that will repeat itself until its count reaches the maximum value.
If the count is equal to or less then the number of Tag elements inside the current Tags
element, then find the Tag element in the count position and print its value. Otherwise,
print 1. -->
<xsl:template name="output-tags">
<xsl:param name="count"/>
<xsl:if test="$count <= $maximum">
<xsl:choose>
<xsl:when test="$count <= count(Tag)">
<xsl:value-of select="Tag[count(preceding-sibling::Tag) = $count - 1]"/>
<xsl:text>,</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>1,</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="output-tags">
<xsl:with-param name="count" select="$count + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
produces the following output when applied to your example input XML:
tag1,tag2,tag3,1,1,1,1,1,1,1,
tag1,tag2,tag3,tag4,tag5,tag6,1,1,1,1,
Thanks for the answers!
I managed to solve it right after I posted. The problem was I had forgotten to send the tagamount variable with the recursive call. After adding it, it works. The repeatable template then looks like this:
<xsl:param name="tagamount"/>
<xsl:param name="index" select="0" />
<xsl:text>1,</xsl:text>
<xsl:if test="not($index = 10-$tagamount)">
<xsl:call-template name="repeatable">
<xsl:with-param name="tagamount" select="$tagamount"/> <-----------
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>

Structural requirements when using "except" in XPATH/XSL

I am having trouble when using "except" in xpath. Here is the chunk of problem code. (I tried to simplify as much as possible without obscuring the whole problem).:
<!--First, create a variable containing some nodes that we want to filter out.
(I'm gathering elements that are missing child VALUE elements
and whose child DOMAIN and VARIABLE elements only occur once
in the parent list of elements.)
I've confirmed that this part does generate the nodes I want,
but maybe this is the incorrect result structure?-->
<xsl:variable name="badValues">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN and VARIABLE=current()/VARIABLE])=1">
<xsl:copy-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<!--Next Loop over the original nodes, minus those bad nodes.
For some reason, this loops over all nodes and does not filter out the bad nodes.-->
<xsl:for-each select="$root/A except $badValues/A"> ...
When you create an xsl:variable without using #select and do not specify the type with the #as, it will create the variable as a temporary tree.
You want to create a sequence of nodes, so that when they are compared in the except operator, they are "seen" as the same nodes. You can do this by specifying as="node()*" for the xsl:variable and by using xsl:sequence instead of xsl:copy-of:
<xsl:variable name="badValues" as="node()*">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN
and VARIABLE=current()/VARIABLE])=1">
<xsl:sequence select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
Alternatively, if you were to use a #select and eliminate the xsl:for-each it would also work. As Martin Honnen suggested, you could use an xsl:key and select the values like this:
<xsl:key name="by-dom-and-var" match="A" use="concat(DOMAIN, '|', VARIABLE)"/>
Then change your badValues to this:
<xsl:variable name="badValues"
select="$root/A[not(VALUE)]
[count(key('by-dom-and-var',
concat(DOMAIN, '|', VARIABLE))/VARIABLE) = 1]"/>>
You can see the difference in the identity of the nodes by using the generate-id() function as you iterate over the items by executing this stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:variable name="root" select="*" as="item()*"/>
<xsl:variable name="originalBadValues">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN
and VARIABLE=current()/VARIABLE])=1">
<xsl:copy-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="badValues" as="node()*">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN
and VARIABLE=current()/VARIABLE])=1">
<xsl:sequence select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<!--These are the generated ID values of all the A elements-->
<rootA>
<xsl:value-of select="$root/A/generate-id()"
separator=", "/>
</rootA>
<!--These are the generated ID values for
the original $badValues/A -->
<originalBadValues>
<xsl:value-of select="$originalBadValues/A/generate-id()"
separator=", " />
</originalBadValues>
<!--These are the generated ID values for
the correct selection of $badValues-->
<badValues>
<xsl:value-of select="$badValues/generate-id()"
separator=", " />
</badValues>
<!--The generated ID values for the result of
the except operator filter-->
<final>
<xsl:value-of select="($root/A except $badValues)/generate-id()"
separator=", "/>
</final>
</xsl:template>
</xsl:stylesheet>
Executed against this XML file:
<doc>
<A>
<VALUE>skip me</VALUE>
<DOMAIN>a</DOMAIN>
<VARIABLE>a</VARIABLE>
</A>
<A>
<DOMAIN>a</DOMAIN>
<VARIABLE>a</VARIABLE>
</A>
<A>
<DOMAIN>b</DOMAIN>
<VARIABLE>b</VARIABLE>
</A>
<A>
<DOMAIN>c</DOMAIN>
<VARIABLE>c</VARIABLE>
</A>
<A>
<DOMAIN>a</DOMAIN>
<VARIABLE>a</VARIABLE>
</A>
</doc>
It produces the following output:
<rootA>d1e3, d1e15, d1e24, d1e33, d1e42</rootA>
<originalBadValues>d2e1, d2e9</originalBadValues>
<badValues>d1e24, d1e33</badValues>
<final>d1e3, d1e15, d1e42</final>

Testing whether a node with particular content exists using xslt

I am trying to merge the elements from two separate web.xml files using XSLT. For example, if web-1.xml and web-2.xml are being merged, and I'm processing web-1.xml, I want all elements in web-2.xml to be added into the result, except any that already exist in web-1.xml.
In the XSLT sheet, I have loaded the document whose servlet's are to be merged into the other document using:
<xsl:variable name="jandy" select="document('web-2.xml')"/>
I then have the following rule:
<xsl:template match="webapp:web-app">
<xsl:copy>
<!-- Copy all of the existing content from the document being processed -->
<xsl:apply-templates/>
<!-- Merge any <servlet> elements that don't already exist into the result -->
<xsl:for-each select="$jandy/webapp:web-app/webapp:servlet">
<xsl:variable name="servlet-name"><xsl:value-of select="webapp:servlet-name"/></xsl:variable>
<xsl:if test="not(/webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
The problem I'm having is getting the test in the if correct. With the above code, the test always evaluates to false, whether a servlet-name element with the given node exists or not. I have tried all kinds of different tests but with no luck.
The relevant files are available at http://www.cs.hope.edu/~mcfall/stackoverflow/web-1.xml, and http://www.cs.hope.edu/~mcfall/stackoverflow/transform.xslt (the second web-2.xml is there as well, but StackOverflow won't let me post three links).
provide an anchor for the first document, just before the for-each loop:
<xsl:variable name="var" select="."/>
then, use it in your if:
<xsl:if test="not($var/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
Your template matches XPATH webapp:webapp from web-1.xml and
you are refencing absolute XPATH if your xsl:if condition: /webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name]. Try to do it using relative XPATH:
<xsl:if test="not(webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
I haven't checked it, so you have to give it a try.
Also, it would be easier if you could provide web-1.xml and web-2.xml files.
EDIT
The following XSLT merges two files - the only problem appears when there are sections of the same type (like listener) in two places of the input XML.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:webapp="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xpath-default-namespace="http://java.sun.com/xml/ns/javaee">
<xsl:output indent="yes"/>
<xsl:variable name="jandy" select="document('web-2.xml')"/>
<xsl:template match="/">
<xsl:element name="web-app">
<xsl:for-each select="webapp:web-app/*[(name() != preceding-sibling::node()[1]/name()) or (position() = 1)]">
<xsl:variable name="nodeName" select="./name()"/>
<xsl:variable name="web1" as="node()*">
<xsl:sequence select="/webapp:web-app/*[name()=$nodeName]"/>
</xsl:variable>
<xsl:variable name="web2" as="node()*">
<xsl:sequence select="$jandy/webapp:web-app/*[name() = $nodeName]"/>
</xsl:variable>
<xsl:copy-of select="$web1" copy-namespaces="no"/>
<xsl:for-each select="$web2">
<xsl:variable name="text" select="./*[1]/text()"/>
<xsl:if test="count($web1[*[1]/text() = $text]) = 0">
<xsl:copy-of select="." copy-namespaces="no"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

How can I pass HTML-element as parameter to XSLT function?

I have the following XSLT-function that I use in a XSLT file to generate XHTML output:
<xsl:function name="local:if-not-empty">
<xsl:param name="prefix"/>
<xsl:param name="str"/>
<xsl:param name="suffix"/>
<xsl:if test="$str != ''"><xsl:value-of select="concat($prefix, $str, $suffix)"/></xsl:if>
</xsl:function>
it simply checks whether a string str is not empty and, if so, returns the string, concatenated with a prefix and a suffix.
The function works fine as long as I only pass simple strings. But when I try to pass HTML elements as prefix or suffix, e.g.:
<xsl:value-of select="local:if-not-empty('', /some/xpath/expression, '<br/>')"/>
I get the following error message:
SXXP0003: Error reported by XML parser: The value of attribute "select"
associated with an element type "null" must not contain the '<' character.
The next thing I tried was to define a variable:
<xsl:variable name="br"><br/></xsl:variable>
and pass it to the function:
<xsl:value-of select="local:if-not-empty('', /some/xpath/expression, $br)"/>
but here, of course, I get an empty string, as the value of the element is extracted, and not the element itself copied.
My final hopeless attempt was to define a text element in the variable:
<xsl:variable name="br">
<xsl:text disable-output-escaping="yes"><br/></xsl:text>
</xsl:variable>
and pass this to the function, but this wasn't permitted, either.
XTSE0010: xsl:text must not contain child elements
I probably don't understand the intricate inner workings of XSLT, but in my opinion adding a <br/> element within a XSLT-transformation through a generic function seems legitimate...
Anyways... I'd appreciate if anyone could give me an alternative solution. I'd also like to understand why this doesn't work...
PS: I'm using Saxon-HE 9.4.0.1J, Java version 1.6.0_24
Try this:
<xsl:value-of select="local:if-not-empty('', /some/xpath/expression, '<br/>')" disable-output-escaping="yes"/>
Instead of concat, use: <xsl:copy-of> and pass as parameters items not strings:
<xsl:copy-of select="$pPrefix"/>
<xsl:copy-of select="$pStr"/>
<xsl:copy-of select="$pSuffix"/>
Here is a complete example:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:local="my:local" exclude-result-prefixes="local">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vBr"><br/></xsl:variable>
<xsl:template match="/">
<xsl:sequence select="local:if-not-empty('a', 'b', $vBr/*)"/>
</xsl:template>
<xsl:function name="local:if-not-empty">
<xsl:param name="pPrefix"/>
<xsl:param name="pStr"/>
<xsl:param name="pSuffix"/>
<xsl:if test="$pStr != ''">
<xsl:copy-of select="$pPrefix"/>
<xsl:copy-of select="$pStr"/>
<xsl:copy-of select="$pSuffix"/>
</xsl:if>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
a b<br/>
The problem is that <br/> is not a string - it is an XML element, so it cannot be manipulated using string functions. You need a separate function like this:
<xsl:function name="local:br-if-not-empty">
<xsl:param name="prefix"/>
<xsl:param name="str"/>
<xsl:if test="$str != ''">
<xsl:value-of select="concat($prefix, $str)"/>
<br/>
</xsl:if>
</xsl:function>
or a 'trick' like this where you handle <br/> as a separate case:
<xsl:function name="local:if-not-empty">
<xsl:param name="prefix"/>
<xsl:param name="str"/>
<xsl:param name="suffix"/>
<xsl:if test="$str != ''">
<xsl:value-of select="concat($prefix, $str)"/>
<xsl:choose>
<xsl:when test="$suffix = '<br/>'>
<br/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$suffix"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:function>