XSLT If id has class - xslt

I simply can't find this anywhere. How can I write an if statement in xslt to show the words 'YES' if a div with an id of 'cat' also has a class assigned to it which is called 'true'?

If the div in question is the current node, and you don't currently know whether it has an ID of 'cat' or a class attribute, then you might write
<xsl:if test=".[#id='cat' and contains(#class,'true')]">
YES
</xsl:if>
If you're in a template that matches on div[#id='cat'], then you can use the simpler test test="contains(#class,'true')". (And conversely.)
Note that the test as just formulated will succeed for divs with class="untrue" -- if that's an issue for your situation, the solution becomes a bit messier.
In XSLT 1.0, the simplest way is to write something like this:
<xsl:if test=".[#id='cat'
and
contains(concat(' ', #class, ' '),' true ')]">
YES
</xsl:if>
In XSLT 2.0, I'd write something like:
<xsl:if test=".[#id='cat'
and
tokenize(#class, '\s') = 'true')]">
YES
</xsl:if>

Related

Manage flags in xslt?

All,
i am searching in a list of fields those who has the type clob and i am writing it separed by a comma like this [field1, field2, ... fieldn]
my problem is how to identify the first matched field to write it without comma ( i can't use position() because the first field matched can be the first of the list or the last of the list)
I want to make this algorithm in xslt,
variable is_first = TRUE;
if(is_first) {
do smthng;
isfirst = False;
}
Actually it is not possible to make something like this in xslt since variable are immutable. There probably could be workarounds but you have to specify your need in more details.
edit:
If your input is string with values separated by commas...
<xsl:variable name="inputString" select="'field1,field2,field3a,field4,field3b'" />
... you could use tokenize() functions...
<xsl:variable name="tokenized" select="tokenize($inputString, ',')" />
... and then select items corresponding to your condition
<!-- Select item corresponding to condition (e.g. it contains 3). Take first one if there are several such items -->
<xsl:value-of select="$tokenized[contains(., '3')][1]" />
Edit2:
You can use separator attribute of xsl:value-of (xslt 2.0) for output of delimited values.
Assuming following variable
<xsl:variable name="list">
<item>first</item>
<item>second</item>
<item>third</item>
</xsl:variable>
this <xsl:value-of select="$list/item" separator="," /> makes desired output first,second,third
You need to write this using functional code rather than procedural code. It's not possible to do the conversion without seeing the context (it's much easier to work from the problem rather than from the solution in a lower-level language).
But the most common equivalent in XSLT would take the form
<xsl:for-each select=".....">
<xsl:if test="position() = 1"><!-- first time code --></xsl:if>
....
</xsl:for-each>

In XSLT, why do I need to do comparisons with variables instead of with attribute values (in a test expression)

I am parsing a document, with different behavior depending on whether the id attribute is an element of a collection of values ($item-ids in the code below). My question is, why do I need to assign a variable and then compare with that value, like this:
<xsl:template match="word/item">
<xsl:variable name="id" select="#abg:id"/>
<xsl:if test="$item-ids[.=$id]">
<xsl:message>It matches!</xsl:message>
</xsl:if>
</xsl:template>
It seems to be that I should be able to do it like this, though it doesn't work:
<xsl:template match="word/item">
<xsl:if test="$item-ids[.=#abg:id]">
<xsl:message>It matches!</xsl:message>
</xsl:if>
</xsl:template>
This is something I keep forgetting and having to relearn. Can anybody explain why it works this way? Thanks.
To understand XPath, you need to understand the concept of the context node. An expression like #id is selecting an attribute of the context node. And the context node changes inside square brackets.
You don't have to use a variable in this case. Here you can use:
<xsl:template match="word/item">
<xsl:if test="$item-ids[. = current()/#abg:id]">
<xsl:message>It matches!</xsl:message>
</xsl:if>
</xsl:template>
The reason you can't just use $item-ids[. = #abg:id] is that inside the [], you are in the context of whatever is right before the [] (in this case $item-ids), so #abg:id would be treated as $item-ids/#abg:id, which isn't what you want.
current() refers to the current context outside of the <xsl:if> so current()/#abg:id should reflect you the value you want.
I think it's because the line
<xsl:if test="$item-ids[.=#abg:id]">
compares the value of $item-ids to the string '#abg:id' - you need to compare it to the value of #abg:id which is why you need to select that value into the $id variable for the test to work.
Does that help at all?
Edit: I've misunderstood the issue - the other answers are better than mine.

XSLT find whether sibling exist or not

Sample XML is given below.
<mapNode>
<mapNode>...</mapNode>
<mapNode>...</mapNode>-----I am here at 2
<mapNode>...</mapNode>
<mapNode>...</mapNode>
</mapNode>
<mapNode>
<mapNode>...</mapNode>
<mapNode>...</mapNode>
</mapNode>
I want to know whether position 3 exist or not.
Please help me.
Thanks in advance.
If you want to test if an element has a sibling following it, you can use the sensibly named "following-sibling" xpath expression:
<xsl:if test="following-sibling::*" />
Note that this will test if there is any following-sibling. If you only wanted to test for mapNode elements, you could do this
<xsl:if test="following-sibling::mapNode" />
However, this would also be true also in the following case, because following-sibling will look at all following siblings:
<mapNode>
<mapNode>...</mapNode>
<mapNode>...</mapNode>-----I am here at 2
<differentNode>...</differentNode>
<mapNode>...</mapNode>
</mapNode>
If you therefore want to check the most immediately following sibling was a mapNode element, you would do this:
<xsl:if test="following-sibling::*[1][self::mapNode]" />
In addition to #rene's answer you could also use the following-sibling axis from within any mapNode:
<xsl:template match="mapNode">
<xsl:if test="count(following-sibling::mapNode)>0">
<!-- has a successor -->
</xsl:if>
</xsl:template>
Not knowing what you already have but assuming you have a template to select a toplevel mapNode you can use count to findout how many mapNodes there are under thecurrent node:
<xsl:template match="/root/mapNode">
<xsl:if test="count(mapNode)>2">
more than two mapNodes
</xsl:if>
</xsl:template>

How to refer to the current value in XPath (XSLT)?

I am trying to run this XPath expression (that is, trying to count how many element content strings in my XML file end with letter-one-f ('a') or letter-two-f ('A'):
<xsl:value-of select="count( substring(.,string-length(.) -1,string-length(.) -1)=$letter-one-f or substring(., string-length(.) -1,string-length(.) -1)=$letter-two-f )"/>
but I don't know how to refer to the 'current value'. All I know is that it's usually represented by a dot '.' . I don't know where to put 'template match' or if that is even needed.
This code, on the other hand, works, because I have specified that it should look in '/n-grams-sorted/n-gram':
<xsl:value-of select="count(/n-grams-sorted/n-gram[starts-with(.,$letter-one-f) or starts-with(.,$letter-two-f) ])"/>
I just don't know how to apply this to the first expression. Where am I going to get 'the current value'? How am I going to tell it that I want to look in '/n-grams-sorted/n-gram'? It's all I need to get my expression working (I tried it in my editor's 'xpath view').
Source XML file sample:
<n-grams-sorted analysis="N_GRAM_TOKEN3" range="Total Set">
<n-gram position="1" frequency="3535" probability="0.0447735">. = =</n-gram>
<n-gram position="2" frequency="322" probability="0.0040784">= = De</n-gram>
<n-gram position="3" frequency="284" probability="0.0035971">= = Het</n-gram>
<n-gram position="4" frequency="207" probability="0.0026218">= = Hij</n-gram>
<n-gram position="5" frequency="168" probability="0.0021278">= = Dit</n-gram>
Thanks
By the linked stylesheets it's clear you are not using template match at all. You are just matching the root node of the document (/) and then calling templates from that.
In this way your current node (.) is the document root. This can be verified by changing your XPath to:
n-grams-sorted/n-gram[starts-with(.,$letter-one-f) or starts-with(.,$letter-two-f) ]
or, the equal
./n-grams-sorted/n-gram[starts-with(.,$letter-one-f) or starts-with(.,$letter-two-f) ]
or, the equal
current()/n-grams-sorted/n-gram[starts-with(.,$letter-one-f) or starts-with(.,$letter-two-f) ]
Depending on what your are doing, if you are working on n-gram nodes only I think you can replace your stylesheet inital rule with:
<xsl:template match="n-grams-sorted">
<xsl:call-template name="draw-grid">
<xsl:with-param name="rcount">28</xsl:with-param>
<xsl:with-param name="ccount">6</xsl:with-param>
<xsl:with-param name="r">0</xsl:with-param>
<xsl:with-param name="c">0</xsl:with-param>
</xsl:call-template>
</xsl:template>
and then simplify the XPath to:
n-gram[starts-with(.,$letter-one-f) or starts-with(.,$letter-two-f)]
being the current node changed to the topmost one (n-grams-sorted).
Here's the solution:
<xsl:value-of select="count(/n-grams-sorted/n-gram[substring(.,string-length(.), string-length(.))=$letter-one-f or substring(.,string-length(.), string-length(.))=$letter-two-f ] )"/>
The whole problem was not the current node.. the problem was that I was getting '0' as a result all the time, because I was doing string-length(.) -1, which is incorrect, since that would mean "sonya" would become "sony" and start at 'y'. 'y' would never result in 'a', that's why 0... so the solution was to use string-length(.) .. without the -1.

How-to break a for-each loop in XSLT?

How-to break a for-each loop in XSLT?
XSLT is written in a very functional style, and in this style there is no equivalent of a break statement. What you can do is something like this:
<xsl:for-each select="...nodes...">
<xsl:if test="...some condition...">
...body of loop...
</xsl:if>
</xsl:for-each>
That way the for-each will still iterate through all the nodes, but the body of the loop will only be executed if the condition is true.
Put the condition for stopping the "loop" in the select attribute of the for-each element. For instance, to "break" after four elements:
<xsl:for-each select="nodes[position()<=4]">
To iterate up to but not including a node that satisfied some particular condition:
<xsl:for-each select="preceding-sibling::node[condition]">
XSLT isn't a procedural language; don't think of for-each as being a "loop" in the way you have a loop in Java. For-each is a way to apply a template to each of a bunch of items. It doesn't necessarily happen in a particular order, so you can't think of it as "apply this template to each of a bunch of items until such-and-such happens, then stop".
That said, you can use the select attribute to filter the results, so it becomes more like "apply a template to each of a bunch of items, but only if such-and-such is true of them".
If what you really want is "apply a template to each of a bunch of items, where such-and-such is true of them, but only to the first one this is true of", you can combine the select attribute with the position() function.
A "break" from the body of an <xsl:for-each> XSLT instruction cannot be specified using a syntactic construct, however it can be simulated.
Essentially two techniques are discussed:
Performing something inside the body of <xsl:for-each> only if a specific condition is satisfied. This can be improved if the condition can be specified in the select attribute of <xsl:for-each> -- in this case only the necessary nodes will be processed. See for example: https://stackoverflow.com/a/7532602/36305
Specifying the processing not using <xsl:for-each> but with recursion. There are many examples of recursive processing with XSLT. See the code at: https://fxsl.sf.net/
The second method has the benefit of being able to perform the exit immediately, contrasted with the first method having to still perform many "empty cycles" even after the exit-condition has been satisfied.
I had a similar situation and here is the code I had written. For logical reasons, I couldn't fit in the other conditions with condition01.
<xsl:for-each select="msxsl:node-set($DATA6)[condition01]">
<xsl:choose>
<xsl:when test="not((condtion02 or condition03) and condition04)">
--body of for loop
</xsl:when>
</xsl:choose>
</xsl:for-each>
Hello I kwow this is an old post but maybe it can help other developers. I have found a way to break a for each in XSLT it is not litteraly a break but if you see the code you will get it. As you know or not know you can use inline C# code in xslt. In this example i want to loop al the nodes and take the first NTE node with Value RC But if I get a node that differs from the NTE node i want to stop looking at the condition. So I set a global variable in C# code and I ask the value each time I go through a node:
<xsl:value-of select="userCSharp:SetStopForeach('true')" />
<xsl:for-each select="following-sibling::node()">
<xsl:if test="local-name()='NTE_NotesAndComments_3' and userCSharp:GetStopForeach()" >
<xsl:for-each select="NTE_4_CommentType">
<xsl:if test="(CE_0364_0_IdentifierSt)[text()="RC"]">
<ns0:RESULTAAT_COMMENTAAR>
<xsl:for-each select="../NTE_3_Comment">
<xsl:value-of select="./text()" />
</xsl:for-each>
</ns0:RESULTAAT_COMMENTAAR>
</xsl:if>
</xsl:for-each>
</xsl:if>
<xsl:if test="local-name()='ORC_CommonOrder'" >
<xsl:value-of select="userCSharp:SetStopForeach('false')" />
</xsl:if>
</xsl:for-each>
.....
<msxsl:script language="C#" implements-prefix="userCSharp">
<![CDATA[
public bool StopForeach=false;
public bool GetStopForeach() {
return StopForeach;
}
public string SetStopForeach(bool aValue) {
StopForeach=aValue;
return "";
}
]]>
</msxsl:script>