Scripting How-To: Use the ternary condition operator

By Elevate posted 08-09-2015 04:57


Use the Ternary Condition Operator


For SLAX version 1.1 and higher, the ternary conditional operator is a useful addition to SLAX, but its current implementation does include non-trivial caveats that you must remember when working with boolean, node-set, or RTF data types.


NOTE: Because of these caveats, it might be best to limit its usage to string and number arguments. At a minimum, you should never use a boolean value for the second or third argument given that a boolean value of false will not be correctly returned by the operator.

SLAX 1.1 introduces multiple new operators, but the ternary conditional operator is probably the most significant. Typically represented as ?: (question mark followed by colon), the ternary conditional operator is so-named because it takes three arguments, returning either the second or third argument as its result depending on the Boolean value of the first argument. Specifically, if the first argument is true, then the operator's result will be the second argument; otherwise the operator's result will be the third argument.



1	argument-1 ? argument-2 : argument-3



1	var $result = ($interface-count < 5) ? "Interfaces are fine" : "Too many interfaces present";<output> $result;


In the above example, if the $interface-count variable is less than 5, then the $result variable will be assigned the string "Interfaces are fine"; otherwise it will be set to the string "Too many interfaces present". The parenthesis shown above are not required but helps make the code more readable.

Omitting the Second Argument

While this is a ternary operator—hence the namethe second argument can be omitted if it is identical to the first argument. This might be the case if you are assigning a default value from a jcs:input() prompt, as shown in the following example:


1	var $answer = jcs:get-input("Enter interface name [fxp0]: ");
2	var $result = string-length( $answer ) > 0 ? $answer : "fxp0";
The code above assigns the user's input to the $result variable if they provide input, or it assigns a default string of "fxp0" if no input is provided. However, that example requires two lines to achieve what the next example can accomplish in just one:


1	var $result = jcs:get-input("Enter interface name [fxp0]: " ) ?: "fxp0";
Now, when user input is provided, the first argument will evaluate to true and the user's input will be the result of the operator. If, however, the user elects to go with the default value, then jcs:get-input() will be given no input, causing the first argument to evaluate as false and the default "fxp0" string to be returned by the operator.

Argument Data Types

Although the complete implementation details of the ternary conditional operator are not required knowledge for script writers, it is important to understand that the value returned by the operator will sometimes have differences from the value provided as an argument. This is especially true for Booleans, result tree fragments (RTFs), and node-setswhich will all be discussed further in the following sectionsbut differences exist even for numbers, whose data type changes from number to string, a change that is caused by an intermediate conversion of the argument into a RTF before it undergoes a final conversion into its returned data type: either a string or a node-set.

This list indicates what data type a provided argument is returned as. In other words, if argument-2 or argument-3 is a number, then the returned data type will be a string.

  • Strings are returned as strings
  • Numbers are returned as strings
  • Booleans are returned as strings
  • Result tree fragments are returned as node-sets
  • Node-sets are returned as node-sets

The Problem with Booleans

Returning a number as a string instead of a number is unlikely to cause any problems given that SLAX automatically converts between numbers and strings as necessary. Booleans, however, are a different matter. When you convert a Boolean value of false into a string, you get the string "false"; but when you convert the string "false" into a Boolean, you get the Boolean value of true. (Only a blank string would convert to Boolean false.)



1	var $boolean-false = false();
2	var $result = (1 > 2) ?: $boolean-false;
3	<output> "Object type of $boolean-false = " _ exsl:object-type( $boolean-false );
4	<output> "String value of $boolean-false = " _ string( $boolean-false );
5	<output> "Boolean value of $boolean-false = " _ boolean( $boolean-false );   
6	<output> "Object type of $result = " _ exsl:object-type( $result );
7	<output> "String value of $result = " _ string( $result );
8	<output> "Boolean value of $result = " _ boolean( $result );



1	Object type of $boolean-false = boolean
2	String value of $boolean-false = false
3	Boolean value of $boolean-false = false
4	Object type of $result = string
5	String value of $result = false
6	Boolean value of $result = true


As you can see, the data type has changed from boolean to string, but more importantly the Boolean value has changed from false to true. Because of this discrepancy, the ternary conditional operator should never be used with Boolean arguments in the second or third position.


NOTE: This will likely change in future versions of Junos OS.

Result Tree Fragment Caveats

There are two caveats to consider when providing RTFs as arguments to the ternary conditional operator. First, the returned data type will be a node-set consisting of all the top-level elements from the RTF. This will likely not be a problem but is still important to keep in mind so that you form your script's location paths correctly.



01	var $rtf = {
02	    <interface> {
03	        <name> "fxp0";
04	        <unit> {
05	            <name> "100";
06	        }
07	    }
08	}
09	var $result = true() ? $rtf : $rtf;
10	<output> "logical interface =  " _ string( $result/unit/name );



1	logical interface =  100


Script writers accustomed to working with the := operator might expect the location path to be $result/interface/unit/name given that the := operator always returns a context position of the root node; however, because the ?: operator returns a context position of the top-level element(s), the location path must begin with the second-level element rather than the top-level element.

The second caveat for RTFs is that the string value of the original RTF might not be identical to the string value of the returned node-set. This discrepancy occurs because SLAX converts an RTF into a string by concatenating its entire string contents; but when SLAX converts a node-set into a string, it only considers the first node within the set; so if the original RTF has multiple top-level nodes with text content, then you will see a different string generated by the result node-set.



1	var $rtf = {
2	    <data> "one";
3	    <data> "two";
4	}
5	var $result = true() ? $rtf : $rtf;
6	<output> "Object type of $rtf = " _ exsl:object-type( $rtf );
7	<output> "String value of $rtf = " _ string( $rtf );
8	<output> "Object type of $result = " _ exsl:object-type( $result );
9	<output> "String value of $result = " _ string( $result );



1	Object type of $rtf = RTF
2	String value of $rtf = onetwo
3	Object type of $result = node-set
4	String value of $result = one


While this behavior is unlikely to cause issues with the typical usage of RTFs in Junos OS on-box scripts, it would be wise to keep the behavior in mind while coding, particularly when you will be converting RTFs or node-sets into strings.


NOTE: The described caveats might change in future versions of Junos OS.

Node-Sets Have Caveats Too


A node-set argument provided to the ternary operator returns its value as a node-set, but the ternary conditional operator does not perform an actual copy of the underlying XML structure, and the returned node-set is not guaranteed to have the same context position as the argument node-set did. In other words, here are the caveats when using a node-set as argument two or three:

  • The returned node-set will always have a context position of the top-level elements rather than the root node
  • The returned node-set points to a new XML data structure, one that does not contain the siblings or ancestors of the nodes within the argument's node-set

The first caveat presents a challenge when dealing with node-sets that were converted from RTFs because, while the node-set argument provided to the ternary operator will have a context position of the root node, the result node-set returned by the operator will have a context position of the top-level element node(s), a discrepancy that affects the location path syntax necessary to extract information from the node-set.



1	var $rtf := {
2	    <interface> "fxp0";
3	}
4	var $result = true() ? $rtf : $rtf;
5	<output> "String value of $rtf/interface = " _ string( $rtf/interface );
6	<output> "String value of $result/interface = " _ string( $result/interface );



1	String value of $rtf/interface = fxp0
2	String value of $result/interface =

It is trivial to work around this issue by simply taking a step back in the location path, but you must be cognizant of the operator's behavior so that you know when that code change is necessary.

The second caveat of node-set arguments is perhaps larger than the first because many script writers will expect the returned node-set to point to the same XML structure as the node-set argumentbut it only points at a copied subset of the original XML structure, a fact that will become apparent when you try to extract data from a parent hierarchy.



1	var $config = jcs:invoke( "get-configuration" );
2	var $interface = $config/interfaces/interface[name=="ge-0/0/0"];
3	var $input = jcs:get-input( "Select unit 100 or 200 [100]: " );
4	var $result = $input == "200" ? $interface/unit[name=="200"] : $interface/unit[name=="100"];
5	<output> "Physical interface: " _ $interface/name;
6	<output> "Logical interface: " _ $result/../name _ "." _ $result/name;



1	Select unit 100 or 200 [100]: 200
2	Physical interface: ge-0/0/0
3	Logical interface: .200

As you can see, the latter <output> does not complete successfully even though you might expect it to. The problem, as mentioned earlier, is that $result is not an actual copy of the XML data structure that the node-set argument is referring to; instead, it is simply a copy of the nodes of the node-set argument along with their descendants. In other words, ancestor nodes, sibling nodes, cousin nodes, and so on, will not be carried across from the argument node-set to the result node-set.

Once again, it is possible to workaround this issuein this case by using the $interface variable to access nodes in parent hierarchies—but you must always keep this caveat in mind whenever working with node-sets as arguments of the ternary conditional operator. 


NOTE: This behavior might change in a future version of Junos OS.