Node-Sets and Relational Expressions

By Elevate posted 08-09-2015 04:45

This applies to SLAX version 1.0 and higher.


An explanation of node-sets and relational expresions.


SLAX supports a general variety of relational expressions:

  • == (equals)
  • != (does not equal)
  • > (greater than)
  • < (less than)
  • >= (greater than or equal to)
  • <= (less than or equal to

In addition, the not() function is available to flip the current Boolean result to the opposite Boolean value: true to false or false to true.

When using traditional data types such as string, Boolean, or number, these operators work in an intuitive manner. It is obvious that "one" == "two" should evaluate to false and that 5 > 3 should evaluate to true, but node-set relational expressions do not always work so intuitively. This blog post will clarify the rules, highlight the caveats, and (hopefully) simplify your script writing and debugging.

First, keep in mind that unlike the simpler data types, a node-set potentially contains a variety of values. For example, a node-set could consist of the following nodes:


1	<name>ge-0/0/0</name>
2	<name>ge-1/0/0</name>
3	<name>ge-2/0/0</name>

Given that there are multiple values to choose from, which of the values is used when performing a relational comparison? The answer is: any of them. In other words, if any of the node values causes the relational expression to evaluate to true then the overall result is true. So, if the $example variable is a node-set with the three above values, then $example == "ge-0/0/0" would evaluate to true because one of the nodes in the $example node-set has a value equal to "ge-0/0/0".

However, because any of the possible node values can be used in order to cause the expression to evaluate to true, this means that opposite expressions could both evaluate to true. Returning to the above example, $example == "ge-0/0/0" is true, but so is $example != "ge-0/0/0" because there is one node that is equal to "ge-0/0/0" and there are other nodes that are not.

Or consider this number example:


1	<node>1</node>
2	<node>5</node>



1	<node>0</node>
2	<node>6</node>

When working with the above node-sets, both $node1 > $node2, and $node1 < $node2 evaluate to true. Again, the rule is that if any of the nodes in the node-set have a value that causes the relational expression to evaluate to true then the answer is true.

But what about empty node-sets? In this case they have no nodes, so how are they processed? To answer this, consider the rule again: if any of the nodes in the node-set have a value that causes the relational expression to evaluate to true then the answer is true. But if there are no nodes, then there are no nodes that can cause the operator to evaluate to true, so the answer is always false!

This makes sense when doing expressions such as $empty == "ge-0/0/0". But it makes less sense when dealing with expressions such as $empty == "" or $empty != "ge-0/0/0". Both of the two last expressions might seem like they should evaluate to true when $empty is an empty node-set, but because the node-set is empty they will always evaluate to false. Keep this in mind when crafting a relational expression, it might make sense to test for an empty node-set as part of the expression:


1	if( not( $empty ) || $empty == "" )

If you are interested in understanding this on a deeper level, then consider studying the XPATH specification.

Otherwise, here is a list of gotchas to watch out for:

Empty node-sets

Empty node-sets always evaluate to false. Consider including a test for a non-empty node-set as part of the overall Boolean expression.

Opposite relational expressions do not always give the opposite result

When multiple values are present in a node-set, then opposite expressions could both be true. $node-set == 5 and $node-set != 5 could both evaluate to true as long as $node-set has multiple nodes and only one of those nodes has a value of 5.

Using not() on an expression does not provide the same result as the opposite relational expression

This relates to the prior rule. What this means is that not( $example == "ge-0/0/0" ) and $example != "ge=0/0/0" do not yield the same result. The first example that uses the not() function will evaluate to true if there are no nodes with a value of "ge-0/0/0", but the second will evaluate to true as long as all of the nodes do not have a value of "ge-0/0/0" whether there are "ge-0/0/0" nodes present or not.

Node-set1 == Node-set2 does not check that every node value is the same

Remember that a relationship expression is true if any of the nodes in a node-set can make it evaluate to true. Comparing two node-sets for equality in this manner does not verify that all the nodes in the node-sets are equal, it just verifies that at least one node in each node-set has the same value.

Node-set1 != Node-set2 will always evaluate to true when multiple distinct node values are present

Similar to the above gotcha, using the "does not equal" operator will result in true as long as multiple distinct node values are present because that means that there is at least one node value that does not equal the other node value.

Code Example

Here is some example SLAX code and output that demonstrates many of these gotchas in practice:


01	match / {
02	    <op-script-results> {
03	        var $example-raw := {
04	            <name> "ge-0/0/0";
05	            <name> "ge-1/0/0";
06	            <name> "ge-2/0/0";
07	        }
08	        var $example = $example-raw/name;
09	        <output> "$example == 'ge-0/0/0' = " _ boolean( $example == "ge-0/0/0");
10	        <output> "$example != 'ge-0/0/0' = " _ boolean( $example != "ge-0/0/0");
12	        var $node1-raw := {
13	            <node> 1;
14	            <node> 5;
15	        }
16	        var $node1 = $node1-raw/node;
17	        var $node2-raw := {
18	            <node> 0;
19	            <node> 6;
20	        }
21	        var $node2 = $node2-raw/node;
22	        <output> "$node1 > $node2 = " _ boolean( $node1 > $node2 );
23	        <output> "$node2 > $node1 = " _ boolean( $node2 > $node1 );
24	        <output> "$node1 < $node2 = " _ boolean( $node1 < $node2 );
25	        <output> "$node2 < $node1 = " _ boolean( $node2 < $node1 );
27	        var $empty = /null;
28	        <output> "$empty == '' = " _ boolean( $empty == "" );
29	        <output> "$empty != 'example' = " _ boolean( $empty != "example" );
30	        <output> "not( $empty ) || $empty == '' = " _ boolean( not( $empty ) || $empty == "" );
31	        <output> "not( $empty ) || $empty != 'example' = " _ boolean( not( $empty ) || $empty != "example" );
32	    }
33	}



01	$example == 'ge-0/0/0' = true
02	$example != 'ge-0/0/0' = true
03	$node1 > $node2 = true
04	$node2 > $node1 = true
05	$node1 < $node2 = true
06	$node2 < $node1 = true
07	$empty == '' = false
08	$empty != 'example' = false
09	not( $empty ) || $empty == '' = true
10	not( $empty ) || $empty != 'example' = true

Note: The first two groups of tests make sense, since applying a condition to a node set is true if any of the nodes make it true.  So there is a node for which "== ge-0/0/0" and another node for which "!= ge-0/0/0" are true.  Odd but true.

But the last set is the stuff that drives me crazy.  Comparing empty nodes to empty strings should stringify both sides and then make the comparison, but instead booleanizes both sides and for some reason an empty string is true.  I don't really grok why they did it that way, but finally gave up and made jcs:empty() which tests for both empty strings and empty node sets.  Without this function, I was doing "string-length($X) == 0" way too often.

Addendum by Phil Shafer 5/10/2010


Original from Curtis Call blog post April. 5, 2010. Released to TechWiki with permission.