How do you examine two sets of XML and figure out what's different? This applies to SLAX version 1.0 and higher.
Feature
Sometimes you need to be able to compare two node-sets and figure out if they're exactly the same, or if they're different, which items are different? The exslt "set" extension is just the thing for this. However, its usage in SLAX is non-obvious (at least it was to me).
An obvious use case for this application of "set:difference()" is examining data sets from before and after something like a configuration change. And that is precisely the use case that ultimately led Jeremy to develop JSNAP, which obviates the need for any additional SLAX coding. Another use case might be calculating the lowest allocatable item from a sparsely-populated data range An example of this could be something like allocating an OID index, a VLAN ID or user ID from a specified data range which has pre-existing (and probably non-consecutive) entries.
To that end, below is an example of some SLAX code for detecting a difference between two XML data sets, which you may find useful. This example uses "set:difference()" to find the first available VLAN from a pool of VLAN IDs.
Script in a nutshell:
- The script initializes a data range that defines the allowable range of VLANS that can be assigned on the device ($vlan-range).
- The script then scans the configuration and builds up a list of the VLANs that are already in use in the configuration ($user-vlans).
- At that point, we can then derive the pool of available VLAN IDs ($available-vlans) by taking the difference of $vlan-range and $user-vlans.
The line that actually uses the set:difference() function is highlighted in red, below. Since this op script is just for demonstration, it simply displays the lowest available VLAN that you can use. A "real" script would do something with that data.
Have fun.
/doug
001 version 1.0;
002 /*
003 * File: vlan.slax
004 * Description: use set:difference to find 1st available item from a "pool"
005 */
006
007 ns junos = "http://xml.juniper.net/junos/*/junos";
008 ns xnm = "http://xml.juniper.net/xnm/1.1/xnm";
009 ns jcs = "http://xml.juniper.net/junos/commit-scripts/1.0";
010 ns func extension = "http://exslt.org/functions";
011 ns set = "http://exslt.org/sets";
012
013 import "../import/junos.xsl";
014
015 var $top-vlan = 110; /* upper end of vlan range */
016 var $bottom-vlan = 96; /* lower end of vlan range */
017
018 match / {
019 <op-script-results> {
020
021 /*
022 * First, build a node-set that describes the range of ALL VLANs that
023 * we are allowed to allocate from. (used later)
024 */
025 var $vlan-range := {
026 call create-vlan-range($item = $bottom-vlan);
027 }
028 expr jcs:output("Allowable VLAN ID range: ",$bottom-vlan,"-",$top-vlan);
029
030 /*
031 * Open mgd session and get configuration.
032 */
033 var $con = jcs:open();
034 if (not($con)) {
035 expr jcs:output("Error connecting to mgd.");
036 }
037 var $rpc-config-req = <get-configuration database="committed">;
038 var $configuration = jcs:execute($con,$rpc-config-req);
039
040 /*
041 * Collect ALL VLAN IDs assigned in the interfaces stanza and save into a node-set.
042 */
043 var $used-vlans := {
044 for-each ($configuration/interfaces//vlan-id) {
045 <vlan> {
046 <id> .;
047 }
048 }
049 }
050
051 /*
052 * Just for fun: show the count of VLANs IDs in use and display this list.
053 */
054 var $vlan-count = count($used-vlans/vlan);
055 expr jcs:output("There are ", $vlan-count, " VLANs currently in use in the config:");
056 for-each ($used-vlans/vlan) {
057 expr jcs:output("\t", id);
058 }
059
060 /*
061 * Now we can derive the set of "available VLANs" by getting the
062 * difference of $vlan-range and $used-vlans. For that we will use the
063 * exslt set:difference function. Usage is a little 'non-obvious',
064 * since we have to compare references to the nodes, not not the node
065 * values. It's a little weird but it works, is fast and only takes 3 steps:
066 */
067
068 var $ref-vlan-range = $vlan-range/vlan;
069 var $ref-used-vlans = $vlan-range/vlan[(id == $used-vlans/vlan/id)];var $available-vlans = set:difference($ref-vlan-range, $ref-used-vlans);
070 /*
071 * Just for fun: dump out the list of VLAN IDs still available
072 */
073 expr jcs:output("These are, then, the VLAN IDs still available:");
074 for-each ($available-vlans) {
075 expr jcs:output("\t", . );
076 }
077
078 /*
079 * Now all we have to do is select the 1st one in the list (should
080 * already be sorted). That way, we're always allocaing "from the
081 * bottom up"
082 * Just for fun, show the user which one you've picked:
083 */
084 var $next-vlan = $available-vlans[position() == 1];
085 expr jcs:output("We will use ID ", $next-vlan, " for our next VLAN.");
086 }
087 }
088 /*********************************************************************************************************
089 * End of main. Templates, functions below here.
090 */
091
092 /*********************************************************************************************************/
093 template create-vlan-range($item) {
094 /*
095 * Creates an xml node-set that describes the allowable VLAN range
096 */
097 if ($item <= $top-vlan) {
098 <vlan> {
099 <id> $item;
100 }
101 call create-vlan-range($item = $item + 1);
102 }
103 }
104 /*********************************************************************************************************/
Source
Original from Douglas McPherson blog post March 19, 2013. Released to TechWiki with permission.