Conditional position-check in XSLT - xslt

I have an XSLT file which renders articles. I'm passing a variable into the XSLT file which is supposed to limit the number of records output. The trouble is, this 'limit' variable is optional - if it's not there the XSLT file needs to output all values; if the 'limit' variable is a number, then it needs to only output that number of items (presumably using a position() test).
Here's my code:
<xsl:variable name="limit" select="/macro/limit"/>
<xsl:template match="/">
<xsl:variable name="allNodes" select="$localSiteRoot/descendant-or-self::*[articles != '']"/>
<xsl:for-each select="$allNodes">
<div class="articleItem">
<h3><xsl:value-of select="./articleHeader"/></h3>
<xsl:if test="./articleSubheader != ''">
<h4><xsl:value-of select="./articleSubheader"/></h4>
</xsl:if>
</div>
</xsl:for-each>
</xsl:template>
Clearly I could just do an XSLT choose around this and say
<xsl:choose>
<xsl:when select="$limit!= ''">
<xsl:if test="position() < $limit">
But then I'd need to repeat the code twice. There must be a better way, perhaps using templates, but I just can't figure out how it would work and my XSLT isn't great.
Could anyone point me in the right direction with the best/neatest way to approach this?
Thanks!

Just use:
<xsl:for-each select="$allNodes[not(position() > $limit)]">
Explanation:
If the value of $limit is castable to a number, then the expression in the select attribute above selects not more than $limit nodes.
If the value of $limits isn't castable to a number (e.g. empty or 'abc') then it is converted to NaN. By definition comparisons involving NaN are always false(), therefore the predicate not(position() > $limit is true() and in this case all nodes in $allNodes are selected.

Related

Need Counter Value in Muenchian grouping logic code

I have requirement to print the incremental value within a for-each (Muenchian grouping) function
which I have already used.
Please find the snippet code
<xsl:template>
<xsl:for-each select="./DocumentPayable/DocumentPayableLine[generate-id() = generate-id(key('Locationabc', ./Tax/TaxRateCode))]">
<xsl:if test="(LineType/Code='AWT')">
<xsl:variable name="LocationCode" select="./Tax/TaxRateCode"/>
<Rcrd>
<AddtlInf>/D1/<xsl:value-of select="(Tax/TaxRateCode)"/></AddtlInf>
</Rcrd>
</xsl:if>
</xsl:for-each>
</xsl:template>
Expected Output:
/D1/INDIA
/D2/USA
/D3/AFRICA
Based on number of tax rate codes the incremental value should be shown.
Please help.
Use a predicate and position() e.g.
<xsl:for-each select="./DocumentPayable/DocumentPayableLine[generate-id() = generate-id(key('Locationabc', Tax/TaxRateCode))][LineType/Code='AWT']">
<Rcrd>
<AddtlInf>/D<xsl:value-of select="position()"/>/<xsl:value-of select="Tax/TaxRateCode"/></AddtlInf>
</Rcrd>
</xsl:for-each>

xslt in xml attribute

I have xml element tyle boleean.
<testelement>0</testelement>
I use xslt to transform value to no/yes depending on 0/1 value and it works great
<xsl:choose>
<xsl:when test="./text()='0'">
<xsl:text>No</xsl:text>
</xsl:when>
<xsl:when test="./text()='1'">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:otherwise>
</xsl:choose>
The same I try to do with attribute type boolean. Element has maxOcc unbounded.
<element attribute="0">
...
</element>
<element attribute="1">
...
</element>
In xlts:
<xsl:choose>
<xsl:when test="//#attribute='0'">
<xsl:text>No</xsl:text>
</xsl:when>
<xsl:when test="//#attribute='1'">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="no">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:otherwise>
</xsl:choose>
But after i use this code all values are Yes or all values are No depending what is value in first node element. EG if 0 is in first element all values are No and it doesnd matter that in second is 1.
How to transform it properly?
Thanks
all values are Yes or all values are No
depending what is value in first node element
Yes, of course they are. That's because your test:
<xsl:when test="//#attribute='0'">
selects all the attributes in the XML document, and in XSLT 1.0 (which I assume you're using) only the first one's value will be used.
You need first to be in the context of element, then test that specific element's attribute by:
<xsl:when test="#attribute='0'">
Here's a better way to do it:
<xsl:template match="node()[.='0'] | #*[.='0']" mode="toYesNo"/>No</xsl:template>
<xsl:template match="node()[.='1'] | #*[.='1']" mode="toYesNo"/>Yes</xsl:template>
<xsl:template match="node()|#*" mode="toYesNo"/>
<xsl:message terminate="no">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:template>
and then you can xsl:apply-templates (with mode="toYesNo") selecting any element, attribute, or text node to get the appropriate conversion.
In XSLT 3.0 you can replace the patterns withm for example, match=".[.='0']" to match any kind of node.
Try to avoid using ./text() because it goes wrong when there are comments in your XML. You can nearly always replace it with ..
And of course you error with the attributes was the leading //. You need to be very clear about the difference between absolute path expressions (which start with / and select from the root of the tree) and relative path expressions (which select from the node you are current processing).

How to increment a variable inside an XSLT template?

here is briefly the problem I'm facing:
I have built a custom Content Query webpart, I fill this CQWP with items from a list. What I would like to do is to have a special separator each 3 items.
How to tell the XSLT that the current item is the 3, 6 or 9th one, and therefore, that a separator has to be put ?
what I have thought of would be to do something like that in the itemstyle.xsl:
<xsl:variable name="increment" select="0"/>
<xsl:template>
<xsl:with-variable name="increment" select="$increment+1"/>
<xsl:if increment = multiple de 3>
-put a separator-
</xsl:if>
</xsl:template>
but it appears that global variable cannot be used this way. Therefore my second idea would to get sortof the "row number" of the corresponding item in order to get the same information.
Does anybody have any idea of how I can solve this problem ?
XSLT does not do destructive update. Therefore you cannot assign a value to a variable and then overwrite the variable with a new value later.
In your case, you can use the position() function with modulus arithmetic:
<xsl:for-each select="*">
<!-- normal processing here -->
<xsl:if test="position() mod 3 = 0">
<!-- separator here -->
</xsl:if>
</xsl:for-each>

XSLT:How to deal with testing the value of an element?

I have an xml file in which there is tag namely, <Gender/> It carries either 'M' or 'F' as data, now my work is to test the value and write <Gender_Tag>Male</Gender_Tag> or <Gender_Tag>Female</Gender_Tag> according to the values M or F respectively .. I tried this code .. It used to work in other circumstances..
All relative paths expressed in a template are evaluated against the current node. Your template match Gender elements, so Gender='M' returns true if there is any Gender's child named 'Gender' with the value 'M'. I guess this is not the case...
Use the dot to express the current node (here a Gender element):
<xsl:template match="root/details/Gender">
<Gender_Tag>
<xsl:choose>
<xsl:when test=".='M'">
<xsl:text>Male</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Female</xsl:text>
</xsl:otherwise>
</xsl:choose>
</Gender_Tag>
</xsl:template>
EDIT: You may use two templates too
<xsl:template match="root/details/Gender[.='M']">
<Gender_Tag>Male</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender[.='F']">
<Gender_Tag>Female</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender">
<xsl:choose>
<xsl:when test="normalize-space(text())='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:when>
<xsl:otherwise>
<Gender_Tag>Female</Gender_Tag>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My example differs in two points from Scoregraphic's:
It uses xsl:choose to ensure, that only one Gender_Tag element is created (that also means, that if the text is not 'M', it is always a Female)
Use of normalize-space() strips white space around the text content of the element.
Untested, but may work...
<xsl:template match="root/details/Gender">
<xsl:if test="text()='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:if>
<xsl:if test="text()='F'">
<Gender_Tag>Female</Gender_Tag>
</xsl:if>
</xsl:template>
Without seeing XML its hard to be certain, but I think your sample XSLT should be:
<xsl:template match="root/details/Gender">
<xsl:if test=".='M'">
<Gender_Tag><xsl:text>Male</xsl:text></Gender_Tag>
</xsl:if>
<xsl:if test=".='F'">
<Gender_Tag><xsl:text>Female</xsl:text></Gender_Tag>
</xsl:if>
</xsl:template>
Use of choose as per another answer would be better (though I think it should be two explicit when clauses rather than a when and an otherwise)

XSl:Variable - Condition to check whether value exist

Using XSLT 1.0, how do I check whether the value in the variable exists or not?
I am assigning the value to the variable initially from my XML data and then need to check whether it exits or not:
<xsl:variable name="DOC_TYPE">
<xsl:value-of select="name(./RootTag/*[1])"/>
</xsl:variable>
<xsl:if test="string($DOC_TYPE) = ''">
<xsl:variable name="DOC_TYPE">
<xsl:value-of select="name(./*[1])"/>
</xsl:variable>
</xsl:if>
The above is not working as expected. What I need is if <RootTag> exists in my data then the variable should contain the child node below the <RootTag>. If <RootTag> does not exist then the DOC_TYPE should be the first Tag in my XML data.
Thanks for your response.
You can't re-assign variables in XSLT. Variables a immutable, you can't change their value. Ever.
This means you must decide within the variable declaration what value it is going to have:
<xsl:variable name="DOC_TYPE">
<xsl:choose>
<xsl:when test="RootTag">
<xsl:value-of select="name(RootTag/*[1])" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="name(*[1])" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
A few other notes:
this: './RootTag' is redundant. Every XPath you don't start with a slash is relative by default, so saying 'RootTag' is enough
this: <xsl:value-of select="name(*[1])"/> already results in a string (names are strings by definition), so there is no need to do <xsl:if test="string($DOC_TYPE) = ''"> , a simple <xsl:if test="$DOC_TYPE = ''"> suffices
to check if a node exists simply select it via XPath in a test="..." expression - any non-empty node-set evaluates to true
XSLT has strict scoping rules. Variables are valid within their parent elements only. Your second variable (the one within the <xsl:if>) would go out of scope immediately(meaning right at the </xsl:if>).
Try this
<xsl:variable name="DOC_TYPE">
<xsl:choose>
<xsl:when test="/RootTag"><xsl:value-of select="name(/RootTag/*[1])"></xsl:value-of></xsl:when>
<xsl:otherwise><xsl:value-of select="name(/*[1])"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
It only exists if you have assigned it. There's no reason to test it for existence.
See also here