How to refer to the current value in XPath (XSLT)? - 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.

Related

XPATH: How to use a variable in call-template

I am writing an XSLT to transform an XML: I am not experienced in Xpath, and my XML is too big to post.
But here is my problem:
This code: works fine:
<cac:testmathieu>
<xsl:variable name="lineAmount2" select="body:LineAmount * -1" />
<xsl:value-of select="../body:TaxTrans[body:TaxBaseAmount=$lineAmount2]/body:TaxAmount"/>
</cac:testmathieu>
Then, why is the code below not working, when it's used right below the above code:
<xsl:call-template name ="TaxTotalLine">
<xsl:with-param name="TaxAmount" select="../body:TaxTrans[body:TaxBaseAmount=body:LineAmount*-1]/body:TaxAmount"></xsl:with-param>
</xsl:call-template>
The second piece of code is just a merge of of the code in the first example, or am I mistaken?
I think you want
<xsl:call-template name ="TaxTotalLine">
<xsl:with-param name="TaxAmount" select="../body:TaxTrans[body:TaxBaseAmount = current()/body:LineAmount*-1]/body:TaxAmount"></xsl:with-param>
</xsl:call-template>
to compare the LineAmount of the currently processed node (e.g. any outer template or for-each) to the TaxBaseAmout of the TaxTrans element you have applied the predicate in square brackets to.
thanks for the response.
It was acutally a combination of the "current()" and the brackets.
Much appreciated !
Kind regards
Mathieu

Looping through an xpath getting the right iteration each time in xslt 2

I have a condition where I need to loop atleast once and so I have the following xsl code. However, this doesnt work as it always gets the last iterations value. How can I tweak this so it gets the right iteration on each loop?
<xsl:variable name='count0'>
<xsl:choose>
<xsl:when test='count($_BoolCheck/BoolCheck[1]/CheckBoolType) = 0'>
<xsl:value-of select="1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select='count($_BoolCheck/BoolCheck[1]/CheckBoolType)'/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:for-each select="1 to $count0">
<xsl:variable name='_LoopVar_2_0' select='$_BoolCheck/BoolCheck[1]/CheckBoolType[position()=$count0]'/>
<e>
<xsl:attribute name="n">ValueIsTrue</xsl:attribute>
<xsl:attribute name="m">f</xsl:attribute>
<xsl:attribute name="d">f</xsl:attribute>
<xsl:if test="(ctvf:isTrue($_LoopVar_2_0/CheckBoolType[1]))">
<xsl:value-of select=""Value True""/>
</xsl:if>
</e>
</xsl:for-each>
The xml file is as follows:
<BoolCheck>
<CheckBoolType>true</CheckBoolType>
<CheckBoolType>false</CheckBoolType>
<CheckBoolType>1</CheckBoolType>
<CheckBoolType>0</CheckBoolType>
<CheckBoolType>True</CheckBoolType>
<CheckBoolType>False</CheckBoolType>
<CheckBoolType>TRUE</CheckBoolType>
<CheckBoolType>FALSE</CheckBoolType>
</BoolCheck>
In this case I need to iterate through each iteration of CheckBoolType and produce a corresponding number of values. However, in the above example if there were no CheckBoolType iterations I would still like the iterations to enter the for-each loop atleast once. i hope that clarifies it a little more.
First observation: your declaration of $count0 can be replaced by
<xsl:variable name="temp" select="count($_BoolCheck/BoolCheck[1]/CheckBoolType)"/>
<xsl:variable name="count0" select="if ($temp=0) then 1 else $temp"/>
(Sorry if that seems irrelevant, but my first step in debugging code is always to simplify it. It makes the bugs much easier to find).
When you do this you can safely replace the predicate [position()=$count0] by [$count0], because $count0 is now an integer rather than a document node. (Even better, declare it as an integer using as='xs:integer' on the xsl:variable declaration.)
But hang on, $count0 is the number of elements being processed, so CheckBoolType[$count] will always select the last one. That's surely not what you want.
This brings us to another bug in your code. The value of the variable $_LoopVar_2_0 is an element node named CheckBoolType. The expression $_LoopVar_2_0/CheckBoolType[1] is looking for children of this element that are also named CheckBoolType. There are no such children, so the expression selects an empty sequence, so the boolean test is always false.
At this stage I would like to show you some correct code to achieve your desired output. Unfortunately you haven't shown us the desired output. I can't reverse engineer the requirement from (a) your incorrect code, and (b) your prose description of the algorithm you are trying to implement.

XSLT If id has class

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>

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.

XPath find if node exists

Using a XPath query how do you find if a node (tag) exists at all?
For example if I needed to make sure a website page has the correct basic structure like /html/body and /html/head/title.
<xsl:if test="xpath-expression">...</xsl:if>
so for example
<xsl:if test="/html/body">body node exists</xsl:if>
<xsl:if test="not(/html/body)">body node missing</xsl:if>
Try the following expression: boolean(path-to-node)
Patrick is correct, both in the use of the xsl:if, and in the syntax for checking for the existence of a node. However, as Patrick's response implies, there is no xsl equivalent to if-then-else, so if you are looking for something more like an if-then-else, you're normally better off using xsl:choose and xsl:otherwise. So, Patrick's example syntax will work, but this is an alternative:
<xsl:choose>
<xsl:when test="/html/body">body node exists</xsl:when>
<xsl:otherwise>body node missing</xsl:otherwise>
</xsl:choose>
Might be better to use a choice, don't have to type (or possibly mistype) your expressions more than once, and allows you to follow additional different behaviors.
I very often use count(/html/body) = 0, as the specific number of nodes is more interesting than the set. For example... when there is unexpectedly more than 1 node that matches your expression.
<xsl:choose>
<xsl:when test="/html/body">
<!-- Found the node(s) -->
</xsl:when>
<!-- more xsl:when here, if needed -->
<xsl:otherwise>
<!-- No node exists -->
</xsl:otherwise>
</xsl:choose>
I work in Ruby and using Nokogiri I fetch the element and look to see if the result is nil.
require 'nokogiri'
url = "http://somthing.com/resource"
resp = Nokogiri::XML(open(url))
first_name = resp.xpath("/movies/actors/actor[1]/first-name")
puts "first-name not found" if first_name.nil?
A variation when using xpath in Java using count():
int numberofbodies = Integer.parseInt((String) xPath.evaluate("count(/html/body)", doc));
if( numberofbodies==0) {
// body node missing
}