XPath find if node exists - xslt

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
}

Related

xsl: trying to use a "not" in an xsl:when/following-sibling statement

I'm trying to find situations where a heading tag (head1) is not followed by a body tag.
Here's a snippet of what I'd be searching for, "head1" followed by "body":
<title role="head1">Third-Party Notices and/or Licenses</title>
<para role="body">Required notices for open source software products or components used by Cloud are identified in the following table along with the applicable licensing information.</para>
And here's the code I'm trying (the empty head1 code works fine):
<xsl:template match="w:p[w:pPr/w:pStyle/#w:val='ahead1']">
<xsl:variable name="elementValue">
<xsl:apply-templates mode="title.text.only" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$elementValue = ''">
<xsl:message>ERROR: Encountered an head 1 paragraph with no text content.</xsl:message>
<xsl:call-template name="revealDocPosition"/>
</xsl:when>
<!-- Code to check to see that a head1 is directly followed by a body tag -->
<xsl:when test="[$elementValue]and not[following-sibling::*[1][self::atgbody]]">
<!-- <xsl:when test="not[$elementValue][following-sibling::*[1][self::atgbody]]"> -->
<xsl:message>ERROR: Encountered an atg head 1 paragraph that was not follwed by an atgbody.</xsl:message>
<xsl:call-template name="revealDocPosition"/>
</xsl:when>
It fails in my build because it doesn't like the syntax:
[java] Error at xsl:when on line 30 column 84 of headings.xsl:
[java] XPST0003: XPath syntax error at char 0 on line 30 in {[$}:
[java] Unexpected token "[" in path expression
[java] Failed to compile stylesheet. 1 error detected.
I've tried different syntax and it either fails, or run and doesn't find the error.
Ideas?
The square braces are a predicate, and are used to filter items that do not evaluate to true when the predicate filter is applied.
The expression [$elementValue] has nothing to test and filter on.
Assuming that you are attempting to test whether $elementValue is "truthy" and has content, you need to move it outside of the predicate and just test the variable.
The expression not[following-sibling::*[1][self::atgbody]] is testing whether there is a child element named not and applying a predicate filter, which will never match since you don't have any not elements - so this test will never be true. You need to change [] to () in order to invoke the not() function.
<xsl:when test="$elementValue and not(following-sibling::*[1][self::atgbody])">
test="[$elementValue]and not[following-sibling::*[1][self::atgbody]]"
You seem to be making this up as you go along. Guessing the syntax isn't going to get you anywhere.
I don't know what's in the variable $elementValue because you haven't shown enough of your code. Your code refers to an atgBody element which doesn't appear in your source document snippet, and the match pattern also refers to nodes that aren't in your source snippet. This means it's difficult to see what your code is trying to achieve and therefore to tell you exactly where it's wrong.
The syntax error is that [$elementValue] isn't a legal expression. Without knowing what you thought it might mean, it's impossible to tell you how to correct it.

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.

How to filter node list based on the contents of another node list

I'd like to use XSLT to filter a node list based on the contents of another node list. Specifically, I'd like to filter a node list such that elements with identical id attributes are eliminated from the resulting node list. Priority should be given to one of the two node lists.
The way I originally imagined implementing this was to do something like this:
<xsl:variable name="filteredList1" select="$list1[not($list2[#id_from_list1 = #id_from_list2])]"/>
The problem is that the context node changes in the predicate for $list2, so I don't have access to attribute #id_from_list1. Due to these scoping constraints, it's not clear to me how I would be able to refer to an attribute from the outer node list using nested predicates in this fashion.
To get around the issue of the context node, I've tried to create a solution involving a for-each loop, like the following:
<xsl:variable name="filteredList1">
<xsl:for-each select="$list1">
<xsl:variable name="id_from_list1" select="#id_from_list1"/>
<xsl:if test="not($list2[#id_from_list2 = $id_from_list1])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
But this doesn't work correctly. It's also not clear to me how it fails... Using the above technique, filteredList1 has a length of 1, but appears to be empty. It's strange behaviour, and anyhow, I feel there must be a more elegant approach.
I'd appreciate any guidance anyone can offer. Thanks.
Use this XPath one-liner:
$vList1[not(#id = $vList2/#id)]
As far as I am aware using $var[] syntax doesn't work. What works is: expr1/[expr2 = $var], and func1($var).
What you can do is simply embed the expression that yields $list2 in the if test:
<xsl:for-each select="$list1">
<xsl:variable name="id" select="#id_from_list1"/>
<xsl:if test="not(expr2[#id_from_list2 = $id ])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
<xsl:copy-of select="$list2"/>
Substitute expr2 with actual expression.

how to use xsl:param value in the xsl:attribute name="width"

It sounds easy, but none of my "easy" syntax worked:
<xsl:param name="length"/>
<xsl:attribute name="width">$length</xsl:attribute>
not
<xsl:attribute name="width"><xsl:value-of-select="$length"></xsl:attribute>
any suggestions?
thanks
<xsl:attribute
name="width">$length</xsl:attribute>
This will create an attribute with value the string $length. But you want the value of the xsl:param named $length.
<xsl:attribute
name="width"><xsl:value-of-select="$length"></xsl:attribute>
Here the <xsl:value-of> element is not closed -- this makes the XSLT code not well-formed xml.
Solution:
Use one of the following:
<xsl:attribute name="width"><xsl:value-of select="$length"/></xsl:attribute>
or
<someElement width="{$length}"/>
For readability and compactness prefer to use 2. above, whenever possible.
You probably don't even need xsl:attribute here; the simplest way to do this is something like:
<someElement width="{$length}" ... >...</someElement>
Your first alternative fails because variables are not expanded in text nodes. Your second alternative fails because you attempt to call <xsl:value-of-select="...">, while the proper syntax is <xsl:value-of select="..."/>, as described in the section Generating Text with xsl:value-of in the standard. You can fix your code by using
<xsl:attribute name="width"><xsl:value-of select="$length"/></xsl:attribute>
or, as others have noted, you can use attribute value templates:
<someElement width="{$length}" ... >...</someElement>

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>