SLAX is an alternate syntax for XSLT, the W3C standard XML-to-XML transformation. XSLT is a powerful language, but uses an XML-based syntax that is painful to read and write. SLAX uses a syntax modeled after PERL and C which promotes the basic concepts of XSLT into first class language constructs. The result is scripts that are easier to develop and maintain.
This documentation covers the SLAX language, beginning with an overview and a reference section listing all SLAX statements. SLAX has a number of built-in functions which are also covered.
An implementation of the SLAX is available in an open source project called libslax. Built on top of libxslt and libxml2, libslax parses SLAX files and executes them, and can convert between SLAX and XSLT. A debugger and profiler are included. libslax was originally developed as part of the JUNOS Operating System by Juniper Networks and is released under a BSD license. See the "Copyright" file for details.
XSLT is a commonly used transformation language for XML. It is a declarative language that uses XPath expressions to inspect an XML input document and can generate XML output based on that input hierarchy. It's a simple but powerful tool for handling XML, but the syntax is problematic for developers.
XSLT uses an XML-based syntax, and while XML is great for machine-to-machine communication, it is inconvenient for humans to write, especially when writing programs. The occasional benefits of having an XSLT stylesheet be an XML document are outweighed by the readability issues of dealing with the syntax.
SLAX has a simple syntax which follows the style of C and Perl. Programming constructs and XPath expressions are moved from XML elements and attributes to first class language constructs. XML angle brackets and quotes is replaced by parentheses and curly braces which are familiar delimiters for programmers.
SLAX allows you to:
Use if/then/else instead of <xsl:choose> and <xsl:if> elements
Put test expressions in parentheses
Use "==" to test equality (to avoid building dangerous habits)
Use curly braces to show containment instead of closing tags
Perform concatenation using the "_" operator (lifted from perl6)
Perform simple logic using the "?:" operator
Write text strings using simple quotes instead of the <xsl:text> element
Simplify invoking named templates with a syntax resembling a function call
Simplify defining named template with a syntax resembling a function definition
Simplify namespace declarations
Reduce the clutter in scripts, allowing the important parts to be more obvious to the reader
Write more readable scripts
The benefits of SLAX are particularly strong for new developers, since it puts familiar constructs in familiar syntax, allowing them to concentrate in the new topics introduced by XSLT.
SLAX is purely syntactic sugar. The underlaying constructs are completely native to XSLT. All SLAX constructs can be represented in XSLT. The SLAX parser parses an input document and builds an XML tree identical to one produced when the XML parser reads an XSLT document.
SLAX can be viewed as a pre-processor for XSLT, turning SLAX constructs (like if/then/else) into the equivalent XSLT constructs (like <xsl:choose> and <xsl:if>) before the real XSLT transformation engine gets invoked.
The current distribution for libslax is built on libxslt. SLAX uses the xsltSetLoaderFunc() in libxslt to tell libxslt to use a different function when loading script documents. When xsltParseStylesheetDoc() loads a script, it then calls the SLAX loader code, which reads the script, constructs an XML document (xmlDoc) which mimics the document the normal XML parser would have created for a functionally equivalent script. After this document is returned to the existing libxslt code, normal XSLT processing continues, unaffected by the fact that the script was written in SLAX.
The "build‑as‑if" nature of SLAX makes is trivial to write a SLAX-to-XSLT convertor, which reads in SLAX and emits XSLT using the normal libxslt file writing mechanism. In addition, an XSLT-to-SLAX convertor is also available, which emits an XSLT document in SLAX format. These make a great learning aid, as well as allowing conversion of SLAX scripts to XSLT for environments where libxslt is not available.
SLAX builds on three existing technologies, XML, XPath, and XSLT.
XML is a set of encoding rules that allow simplified parsing of documents, which gives a simple way to represent hierarchical data sets in text. XML parsers can be used to encode any type of content, and their reuse has breed a set of high-quality implementations that can be easily incorporated into any software project. Having a single encoding helps removing parsing errors, increase code reuse, simplify developer interaction, reduce development costs, and reduce errors.
XPath is an expression language that allows the specification of sets of nodes within an XML document. The language is simultaneously powerfully expressive in terms of the nodes that can be selected, but also simple and readable. It follows the path of making simple things simple, while making hard things possible.
XSLT is a transformation language that turns XML input document into an XML output document. The basic model is that an XSLT engine (or "processor") reads a script (or stylesheet) and an XML document. The XSLT engine uses the instructions in the script to process the XML document by traversing the document's hierarchy. The script indicates what portion of the tree should be traversed, how it should be inspected, and what XML should be generated at each point.
We'll examine each of these technologies in more detail in the following sections.
XML is a set of encoding rules that allow simplified parsing of documents, which gives a simple way to represent hierarchical data sets in text. There are six basic constructs in XML:
Open tags: <foo>
Starts a level of hierarchy. An open tag and it's matching close tag constitute an XML element. An element can contain additional elements, data, or both.
Close tags: </foo>
Ends a level of hierarchy. There must be a close tag for every open tag.
Empty tags: <foo/>
Equivalent to <foo></foo>. Functions as a shorthand.
Text: <tag>data</tag>
Any additional text is data.
Attributes: <tag attribute="value"/>
Attributes are used to encode name=value pairs inside the open or empty tag. A specific attribute can appear only once per tag.
Namespaces: <prefix:tag xmlns:prefix="URI"/>
Defines the scope of a tag, as indicated by the prefix. This allows the same tag used in different namespaces to be unique. The URI is not dereferenced, but provides a unique name for the namespace. The "xmlns:" attribute maps the prefix to the given URI.
In addition, comments can be added in XML using the delimiters "<!‑‑" and "‑‑>". For example:
<!-- This is a comment -->
From these simple concepts, hierarchical documents are constructed, such as the following JUNOS configuration file:
The XPath expression language allows selection of arbitrary nodes from with an XML document. XSLT uses the XPath standard to specify and locate elements in the input document's XML hierarchy. XPath's powerful expression syntax allows complex criteria for selecting portions of the XML input document.
XPath views every piece of the document hierarchy as a node, including element nodes, text nodes, and attribute nodes.
An XPath expression can include a path of XML node names, which select child nodes based on their ancestors' names. Each member of the path must match an existing node, and only nodes that match all path members will be included in the results. The members of the path are separated by the slash character ('/').
For example, the following expression selects all <paragraph> elements that are parented by a <section> element, which in turn must be parented by a <chapter> element, which must be parented by a <doc> element.
doc/chapter/section/paragraph
XSLT views nodes as being arranged in certain "axes". The "ancestor" axis points from a node up through it's series of parent nodes. The "child" axis points through the list of an element node's direct child nodes. The "attribute" axis points through the list of an element node's set of attributes. The "following‑sibling" axis points through the nodes which follow a node but are under the same parent, while the "proceeding‑sibling" axis points through the nodes that appear before a node and are under the parent. The "descendant" axis contains all the descendents of a node. There are numerous other axes which are not detailed here.
When referring to an axis, use the axis name followed by two colons followed by the element name (which may include an optional prefix and it's trailing colon).
There are two axis aliases that make a convenient shorthand when writing expressions. The "@" alias refers to the attribute axis, allowing either "attribute::number" or "@number" to refer to the "number" attribute. The "//" alias refers to the "descendant‑or‑self" axis, so "doc//paragraph" will find all <paragraph> elements that have a <doc> element as an ancestor.
See also .....
Each XPath expression is evaluated from a particular node, which is referred to as the "context node" (or simply "context"). XSLT changes the context as the document's hierarchy is traversed, and XPath expressions are evaluated from that particular context node.
XPath expression contain two types of syntaxes, a path syntax and a predicate syntax. Path syntax allows traversal along one of the axis in the document's hierarchy from the current context node.
accounting-options
selects an element node named "accounting‑options" that is a child of the current context
server/name
selects an element node named "name" that is parented by an element named "server" that is a child of the current context
/configuration/system/domain-name
selects an element node named "domain‑name" that is parented by a "system" element that is parented by a "configuration" element that is the root element of the document.
parent::system/host-name
selects an element node name "host‑name" that is parented by an element named "system" that is the parent of the current context node. The "parent::" axis can be abbreviated as "..".
A predicate is a boolean test must be satisfied for a node to be selected. Each path member of the XPath expression can have zero or more predicates specified, and the expression will only select nodes for which all the predicates are true.
The predicate syntax allows tests to be performed at each member of the path syntax, and only nodes that pass the test are selected. A predicate appears inside square brackets ("[]") after a path member.
server[name == '10.1.1.1']
selects a "server" element that is a child of the current context and has a "name" element whose value is '10.1.1.1'
*[@inactive]
selects any node ('*' matches any node) that is a child of the current context that has an attribute ('@' selects node from the "attribute" axis) named "inactive"
route[starts-with(next-hop, '10.10.')]
selects a "route" element that is a child of the current context and has a "next‑hop" element whose value starts with the string "10.10."
The "starts‑with" function is one of many functions that are built into XPath. XPath also supports relational tests, equality tests, boolean operations, and many more features not listed here.
SLAX defines a concatenation operator "_" that concatenates its two arguments using the XPath "concat()" function. The following two lines are equivalent:
This section contains some overview material, intended as both overview and introduction. Careful reading of the specification or any of the numerous books on XSLT will certainly be helpful before using commit scripts, but the information here should provide a starting point.
XSLT is a language for transforming one XML document into another XML document. The basic model is that an XSLT engine (or processor) reads a script (or stylesheet) and an XML document. The XSLT engine uses the instructions in the script to process the XML document by traversing the document's hierarchy. The script indicates what portion of the tree should be traversed, how it should be inspected, and what XML should be generated at each point.
+-------------+
| XSLT |
| Script |
+-------------+
|
+-----------+ | +-----------+
|XML Input | v |XML Output |
| Document | +-------------+ | Document |
| i |-->| XSLT |--->| o |
| /|\ | | Engine | | / \ |
| i i i | +-------------+ | o o |
| /| | \ | | /|\ | |
| i i i i | | o o o o |
+-----------+ +-----------+
XSLT has seven basic concepts:
XPath -- expression syntax for selecting node from the input document
Templates -- maps input hierarchies to instructions that handle them
Parameters -- a mechanism for passing arguments to templates
Variables -- defines read-only references to nodes
Programming Constructs -- how to define logic in XSLT
Recursion -- templates that call themselves to facilitate looping
Context (Dot) -- the node currently be inspected in the input document
XPath has be discussed above. The other concepts are discussed in the following sections. These sections are meant to be introductory in nature, and later sections will provide additional details and complete specifications.
An XSLT script consists of a series of template definitions. There are two types of templates, named and unnamed. Named templates operate like functions in traditional programming languages, although with a verbose syntax. Parameters can be passed into named templates, and can be declared with default values.
template my-template ($a, $b = 'false', $c = name) {
/* ... body of the template goes here ... */
}
A template named "my‑template" is defined, with three parameters, one of which defaults to the string "false", and one of which defaults to the contents of the element node named "name" that is a child of the current context node. If the template is called without values for these parameters, the default values will be used. If no select attribute is given for a parameter, it defaults to an empty value.
call my-template($c = other-name);
In this example, the template "my‑template" is called with the parameter "c" containing the contents of the element node named "other‑name" that is a child of the current context node.
The parameter value can contain any XPath expression. If no nodes are selected, the parameter is empty.
Unnamed templates are something altogether different. Each unnamed template contains a select attribute, specifying the criteria for nodes upon which that template should be invoked.
match route[starts-with(next-hop, '10.10.')] {
/* ... body of the template goes here ... */
}
By default, when XSLT processes a document, it will recursively traverse the entire document hierarchy, inspecting each node, looking for a template that matches the current node. When a matching template is found, the contents of that template are evaluated.
The <xsl:apply‑templates> element can be used inside a template to continue XSLT's default, hierarchical traversal of nodes. If the <xsl:apply‑templates> element is used with a "select" attribute, only nodes matching the XPath expression are traversed. If no nodes are selected, nothing is traversed and nothing happens. Without a "select" attributes, all children of the context node are traversed. In the following example, a <route> element is processed. First all the nodes containing a "changed" attribute are processed under a <new> element. Then all children are processed under an <all> element. The particular processing depends on the other templates in the script and how they are applied.
Named templates can also use the "match" statement to perform dual roles, so the template can be used via "apply‑templates" or be calling it explicitly.
Parameters can be passed to either named or unnamed templates using either parameter lists or the "with" statement. Inside the template, parameters must be declared with a "param" statement and can then be referenced using their name prefixed by the dollar sign.
/*
* This template matches on "/", the root of the XML document.
* It then generates an element named "outer", and instructs
* the XSLT engine to recursively apply templates to only the
* subtree only "configuration/system". A parameter called
* "host" is passed to any templates that are processed.
*/
match / {
<outer> {
apply-templates configuration/system {
with $host = configuration/system/host-name;
}
}
}
/*
* This template matches the "system" element, which is the top
* of the subtree selected above. The "host" parameter is
* declared with no default value. An "inner" element is
* generated, which contains the value of the host parameter.
*/
match system {
param $host;
<inner> $host;
}
Parameters can be declares with default values by using the "select" attribute to specify the desired default. If the template is invoked without the parameter, the XPath expression is evaluated and the results are assigned to the parameter.
The second template declares two parameters, $dot which defaults to the current node, and $changed, which defaults to the "changed" attribute of the node $dot. This allows the caller to either use a different source for the "changed" attribute, use the "changed" attribute but relative to a different node that the current one, or use the default of the "changed" attribute on the current node.
XSLT allows the definition of both local and global variables, but the value of variables can only be set when the variable is defined. After that point, they are read only.
A variable is defined using the "var" statement.
template emit-syslog ($device, $date, $user) {
var $message = "Device " _ $device _ " was changed on "
_ $date _ " by user '" _ $user _ "'";
<syslog> {
<message> $message;
}
}
Although this example used an XSL variable, the above example could have used an XSL parameter for $message, allowing users to pass in their own message.
XSLT has a number of traditional programming constructs:
if (xpath-expression) {
/* Code here is evaluated if the expression is true */
}
if (xpath-expression) {
/*
* xsl:choose is similar to a switch statement, but
* the "test" expression can vary among "when" statements.
*/
} else if (another-xpath-expression) {
/*
* xsl:when is the case of the switch statement.
* Any number of "when" statements may appear.
*/
} else {
/* xsl:otherwise is the 'default' of the switch statement */
}
for-each (xpath-expression) {
/*
* Code here is evaluated for each node that matches
* the xpath expression. The context is moved to the
* node during each pass.
*/
}
for $item (items) {
/*
* Code here is evaluated with the variable $item set
* to each node that matches the xpath expression.
* The context is not moved.
*/
}
for $i (1 ... 20) {
/*
* Code here is evaluated with the variable $i moving
* thru a sequence of values between 1 and 20. The
* context is not changed.
*/
}
while ($count < 10) {
/*
* Code here is evaluated until the XPath expression is
* false. Note that this is normally only useful with
* mutable variables.
*/
}
XSLT is a declarative language, mixing language statements (in the "xsl" namespace) with output elements in other namespaces. For example, the following snippet makes a <source> element containing the value of the "changed" attribute.
XSLT depends on recursion as a looping mechanism. Recursion occurs when a section of code calls itself, either directly or indirectly. Both named and unnamed templates can recurse, and different templates can mutually recurse, with one calling another that in turn calls the first.
Care must be taken to prevent infinite recursion. The XSLT engine used by JUNOS limits the maximum recursion, to avoid excessive consumption of system resources. If this limit is reached, the commit script fails and the commit is stopped.
In the following example, an unnamed template matches on a <count> element. It then calls the "count‑to‑max" template, passing the value of that element as "max". The "count‑to‑max" template starts by declaring both the "max" and "cur" parameters, which default to one. Then the current value of "$cur" is emitted in an <out> element. Finally, if "$cur" is less than "$max", the "count‑to‑max" template recursively invokes itself, passing "$cur + 1" as "cur". This recursive pass then output the next number and recurses again until "$cur" equals "$max".
As mentioned earlier, the current context node changes as the apply-templates logic traverses the document hierarchy and as an <xsl:for‑each> iterates through a set of nodes that match an XPath expression. All relative node references are relative to the current context node. This node is abbreviated "." (read: dot) and can be referred to in XPath expressions, allowing explicit references to the current node.
match system {
var $system = .;
for-each (name-server/name[starts-with(., "10.")]) {
<tag> .;
if (. == "10.1.1.1") {
<match> $system/host-name;
}
}
}
This example contains four uses for ".". The "system" node is saved in the "system" variable for use inside the "for‑each", where the value of "." will have changed. The "for‑each"'s "select" expression uses "." to mean the value of the "name" element. "." is then used to pull the value of the "name" element into the <tag> element. The <xsl:if> test then uses ".", also to reference the value of the current context node.
Books and tutorials on XSLT abound, helping programmers learn the technology. XSLT processors (programs that run XSLT scripts) are available from both commercial and open-source developers, allowing users to play with XSLT offline. IDEs with extended debuggers also exist. XSLT is common enough that piecing together a simple script is easy, as is finding help and advice.
This document lists the SLAX statements, with brief examples followed by their XSLT equivalents. The utility of SLAX will hopefully be appearent, as will the simple transformation that SLAX parsing code is performing.
SLAX (and XSLT) is a "declarative" language, meaning that a SLAX script will describe the output that should be generated, not give imperative instructions about how to generate that output. This is quite different than traditional procedural programming, in both content and style.
As a SLAX script executes, it uses the description contained in rules and templates to generate a "result tree", which is a hierarchy of XML output nodes. Some logic statements and conditional processing statements are intermixed with the output description to allow flexibility in the output generated.
We'll start by examining how SLAX generated these output nodes.
SLAX makes extensive use of the XPath expression language. Expressions are used to select nodes, specify conditions, and to generate output content. Expressions contain five constructs:
Paths to elements
Select nodes based on node names
Example: chapter
Selects child nodes based on parent node names
Example: doc/chapter/section/paragraph
Selects child nodes under at any depth
Example: doc//paragraph
Selects parent nodes relative to the current node
Example: ../../chapter
Selects attributes using "@" followed by attribute name
Example: ../../chapter/@number
Predicate tests
Selects nodes for which the expression in the square brackets evaluates to "true" (with boolean() type conversion)
SLAX follows XPath syntax, with the following additions:
"&&" may be used in place of the "and" operator
"||" may be used in place of the "or" operator
"==" may be used in place of the "=" operator
"!" may be used in place of the "not()" operator
"_" is the concatenation operator ("x" _ "y" === concat("x", "y"))
"?:" is converted into choose/when/otherwise elements
The first four additions are meant to prevent programmers from learning habits in SLAX that will negatively affect their ability to program in other languages. The last two additions are for convenience.
When referring to XPath expressions in this document, we mean this extended syntax.
Strings are encoded using quotes (single or double) in a way that will feel natural to C programmers. The concatenation operator is underscore ("_"), which is the new concatenation operator for Perl 6. (The use of "+" or "." would have created ambiguities in the SLAX language.)
In this example, the contents of the <three> and <four> element are identical, and the <five> element's contents are nearly identical, differing only in the use of the XPath concat() function:
<top> {
<one> "test";
<two> "The answer is " _ results/answer _ ".";
<three> results/count _ " attempts made by " _ results/user;
<four> {
expr results/count _ " attempts made by " _ results/user;
}
<five> {
expr results/count;
expr " attempts made by ";
expr results/user;
}
<six> results/message;
}
The equivalent XSLT:
<top>
<one><xsl:text>test</xsl:text></one>
<two><xsl:value-of select='concat("The answer is ",
results/answer, ".")'/></two>
<three><xsl:value-of select='concat(results/count,
" attempts made by ", , results/user)'/></three>
<four><xsl:value-of select='concat(results/count,
" attempts made by ", , results/user)'/></four>
<five>
<xsl:value-of select="results/count"/>
<xsl:text> attempts made by </xsl:text>
<xsl:value-of select="results/user"/>
</five>
<six><xsl:value-of select='results/message'/></six>
</top>
XPath expression can be added to the result tree using the "expr" and "uexpr" statements.
Beginning with SLAX-1.2, elements may be used directly as function arguments. Arguments can be either a single element or a block of SLAX code, placed inside braces.
var $a = my:function(<elt>, <max> 15);
var $b = my:test({
<min> 5;
<max> 15;
if ($step) {
<step> $step;
}
});
var $c = my:write(<content> {
<document> "total.txt";
<size> $file/size;
if (node[@type == "full]) {
<full>;
}
});
The "uexpr" behaves identically to the "expr" statement, except that the contents are not escaped. Normally characters like "<", ">", and "&" are escaped into proper XML (as "<", ">", and "&", respectively), but uexpr avoids this escaping mechanism.
uexpr "<:-&>";
See also disable-output-escaping in the XSLT specification.
Elements are the primary encoding mechanism of XML, and can be combined and arranged to encoding complex hierarchical constructs.
The XML encoding uses three tags: the start tag, the end tag, and the empty tag. The start tag consists of the less than character ('<'), the element name, a set of optional attributes (discussed later), and the greater than character ('>'). This is followed by the contents of the XML element, which may include additional elements. The end tag consists of the less than character ('<'), the slash character ('/'), the element name, and the greater than character ('>'). If an element has no content, an empty tag can be used in place of an open and close tags. The empty tag consists of the less than character ('<'), the element name, a set of optional attributes (discussed later), the slash character ('/'), and the greater than character ('>').
In SLAX, XML elements are encoded using braces instead of the closing tag. The element is given, wrapped in chevrons as in XML, but it then followed by an open brace, the elements contents, and a close brace. If the element is empty, a semi-colon can be used to signify an empty element. If the element contains only a single XPath expression, that expression can be used in place of the braces and the element is ended with a single semi-colon.
Elements are written with in a C-like syntax, with only the open tag. The contents of the tag appear immediately following the open tag. These contents can either be a simple expression, or a more complex expression placed inside braces.
The following SLAX is equivalent to the above XML data:
Programmers are accustomed to using braces, indentations, and editor support to delineate blocks of data. Using these nesting techniques and removing the close tag reduces the clutter and increases the clarity of code.
The name of an element can be specified by an XPath expression, using the "element" statement. The contents of the element must be placed inside a set of braces.
In this example, the value of the "name" node (rather than the literal string "name") will be used to create an XML element, whose contents are an empty element with a name of "from‑" concatenated with the value of the address node and an emply element whose name is the value of the variable "$my‑var" (refer to Variables for more information about variables). Node values are selected from the current context.
element name {
element "from-" _ address;
element $my-var;
}
XML elements can also be specified using a JSON-like style, where quoted strings name the element, followed by a colon and the contents of the element. The contents can be any of the following:
Since there is a mismatch between the data encoding capabilities in XML and JSON, a set of attributes can be used to control the exact rendering of JSON data.
Attribute
Value
Description
name
string
A name to use in place of the element name
type
string
An indication of the desired encoding
The value of the "type" attribute must be one of the subset listed below. This allows SLAX to distinguish between '{ "value": "5" }' and '{ "value": 5 }' to control the encoding of the value as a string or a number. Similarly the caller can control whether "null" is a string or the special null value. If the type attribute is missing, the element is assumed to be a simple field.
Type
Description
array
Contains a JSON array
false
Contains the JSON "false" value
null
Contains the JSON "null" value
member
Is a member of an array
number
Contains a number value
true
Contains the JSON "true" value
In this example, the type attribute is used to indicate the encoding of JSON in XML.
JSON:
"name": "Skip Tracer",
"location": "The city that never sleeps",
"age": 5,
"real": false,
"cases": null,
XML:
<name>Skip Tracer</name>
<location>The city that never sleeps</location>
<age type="number">5</age>
<real type="false">false</real>
<cases type="null">null</cases>
The mismatch of capabilities between JSON and XML also requires the use of alternative encodings for arrays. The <member> element is used to contain member (type="member" of an array (of type="array").
In this example, alternative encodings are used for array values. Also the "type" attribute is used to hold the JSON type:
In addition, JSON allows values for the names which an invalid in XML. When this occurs, an element named "element" is used to hold the content with the "name" attribute containing the real name:
XML allows a set attribute value to be specified on an open or empty tag. These attributes are an unordered set of name/value pairs. The XML encoding uses the attribute name, an equals sign ('='), and the value of the attribute in quotes.
In addition, XSLT introduces a means of assigning attribute values, called "attribute value templates" (AVT). SLAX encodes these as XPath values for attributes:
Attributes on elements follow the style of XML, with the attribute name, an equals sign, and the value of the attribute.
<element attr1="one" attr2="two">;
In this example, the attributes are assigned values using XPath values. The "tag" node value is assigned to the "ref" attribute, while the "title" attribute is assigned the value of the "$title" variable.
Where XSLT allow attribute value templates using curly braces, SLAX uses the normal expression syntax. Attribute values can be any XPath expression, including quoted strings, parameters, variables, and numbers, as well as the SLAX concatenation operator ("_").
Note that curly braces placed inside quote strings are not interpreted as attribute value templates, but as real braces and are escaped when translated into XSLT.
The name of an attribute can be specified by an XPath expression, using the "attribute" statement. The contents of the attribute must be placed inside a set of braces.
In this example, the value of the "name" node (rather than the literal string "name") will be used to create an XML attribute, whose contents are an empty element with a name of "from‑" concatenated with the value of the address node. Node values are selected from the current context.
Attribute sets can be used to define sets of attributes that can be used together. An attribute set contains a set of "attribute" statements that define the names and values of attributes. These attributes can then be referred to as a collection rather than repeat the contents in the script.
faq: Do people really use this?
My guess (which could be wildly wrong) is that this is a rarely used feature that are created to cover the condition that a template cannot return a set of attributes. That's my guess anyway.
The "attribute‑set" statement defines a set of attributes that can be used repeatedly. The argument is the name of the attribute set, and the contents are a set of attribute statements.
Namespaces map URI strings to prefixes that are used to indicate the administrative domain of specific XML elements. The syntax and semantic constraints for the element <size> will be distinct depending on the namespace under which it is defined.
The URI is a string that uniquely identifies the namespace. Here are some examples:
Example namespaces
http://xml.juniper.net/junos
http://www.w3.org/1999/XSL/Format
The prefix is a string pre-pended to the local element name with a colon. Prefixes map to namespaces and are used as "shorthand" for the underlaying namespace.
The "ns" statement defines a mapping from a prefix to a URI namespace identifier. Namespaces must be defined prior to their use.
By default, elements are in the "null" namespace, but the "ns" statement can be used to change the namespace for unprefixed elements.
The syntax of the "ns" statement is:
ns [<prefix> <options> = ] <uri>;
If a prefix is the value prefixed to element names to indicate their namespace should be that of the given URI. If no prefix is given, the given URI will be applied to all elements that do not include a prefix. The values and meanings of <options> are detailed below.
Namespace definitions are supplied using the "ns" statement. This consists of either the "ns" keyword, a prefix string, an equal sign and a namespace URI or the "ns" keyword and a namespace URI. The second form defines the default namespace.
ns junos = "http://www.juniper.net/junos/";
The "ns" statement may appear either following the "version" statement at the beginning of the stylesheet or at the beginning of any block.
ns a = "http://example.com/1";
ns "http://example.com/global";
ns b = "http://example.com/2";
match / {
ns c = "http://example.com/3";
<top> {
ns a = "http://example.com/4";
apply-templates commit-script-input/configuration;
}
}
When appearing at the beginning of the stylesheet, the ns statement may include either the "exclude" or "extension" keywords. These keywords instruct the parser to add the namespace prefix to the "exclude‑result‑prefixes" or "extension‑element‑prefixes" attribute.
The "extension" statement instructs the processing engine that extenstion namespaces, which will cause elements in that namespace to be given special handling by the engine.
For libslax, the extension keyword instructs the library to search for an extension library associated with the namespace. If found, the extension library is loaded and initialized so the script can use functions and elements defined within that library.
The XML specification reserved all prefixes and attributes beginning with the characters "xml". In addition, SLAX reserves all prefixes and attributes that begin with the characters "slax". These reservations help to future proof against changes and enhancements to the language.
When a prefix is used without a corresponding "ns" statement in scope, SLAX will refer to a set of default namespaces. If the prefix has a default namespace, that namespace will be automatically mapped to the prefix.
The following table lists the default set of prefixes installed with the libslax software distribution:
An XML processing instruction is a mechanism to convey application-specific information inside an XML document. The application can detect processing instructions and change behaviour accordingly.
The "processing‑instruction" statement adds a processing instruction to the result tree. The argument to the statement is that name of the processing instruction and the contents of the statement (within braces) is the value of that instruction.
Comments are information for the user or author. They are not formal content and should not be inspected or parsed. They can be discarded without affecting the content of the XML.
<!-- This is an XML comment -->
SLAX comments are distinct from XML comments. SLAX comments appear as part of the SLAX script, and are not part of either the input or output XML documents. SLAX comments follow the C/Perl style of a "/*" followed by the comment, terminated by "*/".
/* This is a SLAX comment */
XML comments may not appear inside a SLAX script.
Comments in SLAX are entered in the traditional C style, beginning the "/*" and ending with "*/". These comments are preserved in the in-memory XML tree representation of the SLAX script. This allows comments to be preserved if the tree is emitted using the normal XML output renderer.
On many occasions, parts of the input XML document will be copied to the output XML document. Such copies can be deep or shallow, meaning that the entire node hierarchy is copied or just the node itself. SLAX contains two distinct statements for these two styles of copying.
The "copy‑of" statement performs a deep copy of a given set of nodes. The argument to the statement is an XPath expression specifying which nodes should be copied.
The "copy‑node" statement performs a shallow copy of the specific node to the result tree, along with any namespace nodes, but none other child nodes (including attribute nodes) are copied. The contents of the statement are a template specifying what should be inserted into the new node.
The "number" statement inserts a generated number into the result tree. This statement has two distinct forms. When used with an argument, the statement inserts the number given by that XPath expression, and a set of optional statements can be used to specify the formatting to be used for that number.
When used without an argument, the number is generated based on position of the current node in the source document, and a set of optional statements can be used to specify formatting.
The formatting statements are given in the following table:
Statement
Value
Description
format
see below
Style of numbering
letter-value
Not implemented in libxslt
grouping-separator
character
Used between groups of digits
grouping-size
number
Number of digits in a group
The value of the "format" statement gives the style of numbering, as
Value
Style
"1"
1 2 3 4
"01"
01 02 03 04
"a"
a b c d
"A"
A B C D
"i"
i ii iii iv
"I"
I II III IV
The selection statements used when the number statement has no argument are given in the following table:
Statement
Value
Description
count
XPath
What to count
from
XPath
Where to start counting from
level
See below
How to count
The "level" statement indicates how to count tags:
Value
Behavior
single
Count from first ancestor node
multiple
Count from any ancestor node
any
Count from any node
In the following example, the value of $this is formatted with three digits of output and the number of "section" elements before the current context value is emitted.
number $this {
format "001";
}
number {
count section;
}
A SLAX script consists of a set of templates. There are two types of templates, match templates and named templates. This section discusses each of these types.
In addition to the template processing, templates can be given explicit names and called directly, allowing a programming style that follows more traditional procedural languages. Named templates are called like function, returning their XML output nodes to the caller, where they can be merged into the caller's XML output tree.
Named templates are defined using their name and parameters and invoked using the "call" statement. The template definition consists of the "template" keyword, the template name, a set of parameters, and a braces-delineated block of code. Parameter declarations can be either inline, with the parameter name and optionally an equals sign ("=") and a value expression. Additional parameters can be declared inside the block using the "param" statement.
The template is invoked using the "call" statement, which consists of the "call" keyword followed by a set of parameter bindings. These binding are a comma-separated list of parameter names, optionally followed by an equal sign and a value expression. If the value is not given, the current value of that variable or parameter is passed, giving a simple shorthand for passing parameters if common names are used. Additional template parameters can be supplied inside the block using the "with" statement.
Named templates accept parameters by name, rather than by position. This means the caller needs to indicate the name of the desired parameter when passing a value:
call test($arg1 = 1, $arg4 = 4, $arg2 = 2);
As a convenience, arguments with the same local name as the argument name can be passed directly:
var $message = "test " _ $name _ " is " _ $status;
call emit-message($message)
This is identical to "call emit-message($message = $message)", but is simpler and the use of common names increased readability.
The call statement can be used as the initial value of a variable or parameter, without using an enclosing set of braces:
In addition, template arguments can be passed using the "with" statement. This statement is placed inside braces after the "call" statement, and gives the parameter name and the value:
call test {
with $arg1 = 1;
with $arg4 = 4;
with $arg2 = 2;
}
note::
Note that in SLAX parameter names are always prefixed with the dollar sign ("$"). This is not true for XSLT, where the dollar sign is not used when defining variables.
Beginning with SLAX-1.2, elements may be used directly as argument values for templates. Arguments can be either a single element or a block of SLAX code, placed inside braces.
While rarely used, XSLT allows named template to be used as match templates. This is done using the "match" statement after the "template" statement. For more information on "match" statements, see The "match" Statement.
SYNTAX::
'template' template-name 'match' xpath-expression '{'
template-contents
'}'
template test match paragraph ($message) {
<message> $message;
}
match / {
call test($message = "called as named template");
apply-templates {
with $message = "called as match template";
}
}
The processing model for SLAX is identical to that of XSLT. A set of XML input nodes are processed to generate a set of XML output nodes. This processing begins at the top of the XML input document and proceeds recursively through the entire document, using rules defined in the SLAX script, rules imported from other scripts, and a set of default rules defined by the XSLT specification.
Each rule defines the matching criteria that controls when the rule is applied, followed by a template for the creation of the XML output nodes. The processing engine inspects each node, finds the appropriate rule that matches that node, executes the template associated with the rules, builds the XML output nodes, and merges those nodes with the XML output nodes from other rules to build the XML output nodes.
The "match" statement defines a match template, with its matching criteria and its template. The keyword "match" is followed by an XPath expression that selects the nodes on which this template should be executed. This is followed by a set of curly braces containing the template.
The "apply‑templates" statement instructs the processing engine to apply the set of templates given in the script to a set of nodes. The statement takes as its argument an XPath expression that selects a set of nodes to use. If no expression is given, the current node is used.
The set of XML input nodes is processed according to the set of templates, and the XML output nodes are given to the context in which the apply-templates statement was issued.
Match templates are applied using the "apply‑templates" statement. The statement accepts an optional XPath expression, which is equivalent to the "select" in an <xsl:apply‑templates> element.
match configuration {
apply-template system/host-name;
}
match host-name {
<hello> .;
}
The "mode" statement allows the apply-template to choice a distinct set of rules to use during processing. The argument to the "mode" statement is a text string that identifies the mode for both the template and the template processing. Templates processing will only select templates that match the current mode value. If no mode statement is given with an "apply‑templates" invocation, then the current mode remains in effect.
In this example, template processing is invoked twice, first for mode "content" and then for mode "index".
The "mode" attribute of the <xsl:template> element is available using the "mode" statement. This statement can appear inside a "match" statement and inside an "apply‑templates" statement.
match * {
mode "one";
<one> .;
}
match * {
mode "two";
<two> string-length(.);
}
match / {
apply-templates version {
mode "one";
}
apply-templates version {
mode "two";
}
}
The "priority" statement sets the priority of the template, which is used as part of the conflict resolution when more that one template matches a source node. The highest priority rule is chosen. The argument to the "priority" statement is a real number (positive or negative).
In this example, the template is given a high priority:
Template can accept parameters from their callers, and scripts can accept parameters from the calling environment (typically the command line). The "param" statement declares a parameter, along with an optional default value.
SYNTAX::
'param' parameter-name ';'
'param' parameter-name '=' optional-value ';'
'param' parameter-name '=' '{' value-block '}'
EXAMPLE::
param $address = "10.1.2.3";
param $count = 25;
templace count {
/*
* This defines a local parameter $count and sets
* its value to that of the global parameter $count.
*/
param $count = $count;
}
Like variables, parameters are immutable. Once created, their value cannot be changed. See Variables for a discussion on dealing with immutable values.
Template parameters can also be defined in a C style following the template name:
The "with" statement supplies a value for a given parameter.
call area {
with $length = 2;
with $width = 100;
with $max;
}
Parameter values may also be passed using a C/perl style, but since arguments in SLAX (and XSLT) are passed by name, the parameter names are also given:
call area($width = 100, $length = 2, $max);
If a parameter is not supplied with a value, the current value of that parameter variable (in the current context) is used, meaning that
with $max;
is equivalent to:
with $max = $max;
Parameters may be passed to match templates using the "with" statement. The "with" statement consists of the keyword "with" and the name of the parameter, optionally followed by an equals sign ("=") and a value expression. If no value is given, the current value of that variable or parameter (in the current scope) is passed, giving a simple shorthand for passing parameters if common names are used.
match configuration {
var $domain = domain-name;
apply-template system/host-name {
with $message = "Invalid host-name";
with $domain;
}
}
match host-name {
param $message = "Error";
param $domain;
<hello> $message _ ":: " _ . _ " (" _ $domain _ ")";
}
The XSLT programming model used with SLAX calls for a traversal of the input data hierarchy. Since the first step is typically the match of the top of the hierarchy and the creation of the top-level tag of the output hierarchy. The "main" statement allows both of these objectives. Two forms of the statement are supported, with and without the output tag. Without an output element, the "main" template is equivalent to "match /". The token "main" is followed by a block of statements within a set of braces:
main {
<top> {
<answer> 42;
}
}
The "main" template can also be used with a top-level output element following the "main" token. The element can include attributes.
main <top> {
<answer> 42;
}
Both of the preceding examples are equivalent to the following XSLT:
This section covers the programming constructs in SLAX. SLAX tries to use language paradigms and constructs from traditional languages (primarily C and Perl) to aid the user in readability and familiarity. But SLAX is built on top of XSLT, and the declarative nature of XSLT means that many of its constructs are visible.
Variables in SLAX are immutable. Once defined, the value of a variable cannot be changed. This is a major change from traditional programming language, and requires a somewhat different approach to programming. There are two tools to help combat this view: recursion and complex variable assignments.
Recursion allows new values for variables to be set, based on the parameters passed in on the recursive call.
Complex variable assignments allow the use of programming constructs inside the variable definition:
var $message = {
if ($red > 40) {
expr "Invalid red value";
} else if ($blue > 80) {
expr "Invalid red value";
} else if ($green > 10) {
expr "Invalid green value";
}
}
This requires different approaches to problems. For example, instead of appending to a message as you loop thru a list of nodes, you would need to make the message initial value contain a "for‑each" loop that emits parts of the message:
var $message = {
<output> "acceptable colors: ";
for-each (color) {
if (red <= 40 && blue <= 80 && green < 10) {
<output> " " _ name _
" (" _ red _ "," _ green _ "," _ blue _ ")";
}
}
}
Variable and parameter syntax uses the "var" and "param" statements to declare variables and parameters, respectively. SLAX declarations differ from XSLT in that the variable name contains the dollar sign even in the declaration, which is unlike the "name" attribute of <xsl:variable> and <xsl:parameter>. This was done to enhance the consistency of the language.
Variable are defined using the "var" statement, which accept a variable name and an initial value. Variable names (and parameter names) always begin with a dollar sign ("$").
SYNTAX::
'var' name '=' initial-value ';'
'var' name '=' '{' initial-value-block '}'
The initial value can be an XPath expresion, an element, or a block of statements (enclosed in braces) that emit a set of nodes.
var $x = 4;
var $y = <price> cost div weight;
var $z = {
expr "this: ";
copy-of $this;
}
In XSLT, all variables are immutable, meaning that once created, their value cannot be changed. This creates a distinct programming environment which is challenging to new programmers. Immutable variables allow various optimizations and advanced streaming functionality.
Given the use case and scenarios for libslax (especially our usage patterns in JUNOS), we've added mutable variables, which are variables that can be changed. The "set" statement allows a new value to be assigned to a variable and the "append" statement allows the value to be extended, with new data appended to it.
The most annoying "features" of XSLT is the concept of "Result Tree Fragments" (aka RTF). These fragments are produced with nodes are created that are not directly emitted as output. The main source is variable or parameter definitions that have complex content.
In this example, an RTF is generated, and then each of the three valid operations is performed:
var $rtf = <rtf> {
<rats> "bad";
}
if ($rtf == "bad") { /* Converts the RTF into a string */
copy-of $rtf; /* Emits the RTF to the output tree */
/* Convert RTF to a node set (see discussion below) */
var $node-set = ext:node-set($rtf);
}
Any XPath operation performed against an RTF will result in an "Invalid type" error.
In truth, the only interesting thing to do with an RTF is to convert it to a node set, which is not a standard XPath/XSLT operation. Most scripts will use the extension function "ext:node‑set()" (which is specific to libxslt) or "exslt:node‑set()" (which is in the EXSLT extension library; see http://exslt.org for additional information).
ns ext = "http://xmlsoft.org/XSLT/namespace";
...
var $alist = ext:node-set($alist-raw);
This must be done when a variable or paramter has a complex initial value:
var $this-raw = <this> {
<that>;
<the-other>;
}
var $this = ext:node-set($this-raw);
Fortunately for SLAX programmers, the ":=" operator does away with these conversion issues, as the following section details.
The ":=" operator is designed to hide the conversion of RTFs to node sets from the programmer. It is used in assigning initial values to variables and parameters.
var $this := <this> {
<that> "one";
<the-other> "one";
}
if ($this/that == "one") {
<output> "not an invalid type error";
}
Calling named templates can also produce RTFs, since the "call" statement would be considered complex variable content. But using the ":=" operator removes this problem:
var $output := call matching-color($match = "corn");
Behind the scenes, SLAX is performing the ext:node-set() call but the details are hidden from the user.
SLAX supports an "if" statement that uses a C-like syntax. The expressions that appear in parentheses are extended form of XPath expressions, which support the double equal sign ("==") in place of XPath's single equal sign ("="). This allows C programmers to avoid slipping into dangerous habits.
if (this && that || the/other[one]) {
/* SLAX has a simple "if" statement */
} else if (yet[more == "fun"]) {
/* ... and it has "else if" */
} else {
/* ... and "else" */
}
Depending on the presence of the "else" clause, an "if" statement can be transformed into either an <xsl:if> element or an <xsl:choose> element.
if (starts-with(name, "fe-")) {
if (mtu < 1500) {
/* Deal with fast ethernet interfaces with low MTUs */
}
} else {
if (mtu > 8096) {
/* Deal with non-fe interfaces with high MTUs */
}
}
The XSLT equivalent:
<xsl:choose>
<xsl:when select="starts-with(name, 'fe-')">
<xsl:if test="mtu < 1500">
<!-- Deal with fast ethernet interfaces with low MTUs -->
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:if test="mtu > 8096">
<!-- Deal with non-fe interfaces with high MTUs -->
</xsl:if>
</xsl:otherwise>
</xsl:choose>
The XPath expression is evaluated into a set of nodes, and then each node is considered as the "context" node, the contents of the "for‑each" statement are evaluated.
The "for‑each" statement mimics functionality of the <xsl:for‑each> element. The statement consists of the "for‑each" keyword, the parentheses-delimited select expression, and a block.
In addition to the standard XSLT "for‑each" statement, SLAX incorporates a "for" statement that allows iteration through a node set without changing the context (".").
The variable is assigned each member of the node-set selected by the expression in sequence, and the contents are then evaluated.
for $item (item-list) {
<item> $item;
}
Internally, this is translated into normal XSLT constructs involving a pair of nested for-each loops, one to iterate and one to put the context back to the previous setting. This allows the script writer to ignore the context change.
The "while" statement allows a block of code to be repeated until a condition is no longer true. This construct is only useful when combined with mutable variables (The "mvar" Statement).
The xpath-expression is cast to a boolean type and if true, the contents are evaluated. The context is not changed. This loop continues until the expression is no longer true. Care must be taken to avoid infinite loops.
mvar $seen;
mvar $count = 1;
while (not($seen)) {
if (item[$count]/value) {
set $seen = true();
}
set $count = $count + 1;
}
Often a loop is required to iterator through a range of integer values, such a 1 to 10. SLAX introduces the "..." operator to generate sequences of such numbers:
for $i (1 ... 10) {
<player number=$i>;
}
The operator translates into an XPath function that generates the sequence as a node set, which contains a node for each value. The "for" and "for‑each" statements can be used to iterate thru the nodes in a sequence.
The use of slax:value() make the "?:" operator non-standard, in that it requires a non-standard extension function. Use of the "?:" should be limited to environments where this function is available.
The use of <xsl:copy‑of> means that attributes cannot be used in a ?: expression, directly or indirectly.
/* These are examples of invalid use of attributes */
var $direct = $test ? @broken : will-not-work;
var $attrib = @some-attribute;
var $indirect = $test ? $attrib : wont-work-either;
Functions are one of the coolest extensions defined by EXSLT. They allow a script to define an extension function that is available in XPath expressions. Functions have several advantages over templates:
Arguments are passed by position, not name
Return values _can_ be objects (not RTFs)
Can be used in expressions
Can be resolved dynamically (using EXSLT's dyn:evaluate())
The argument-list is a comma separated list of parameter names, which will be positionally assigned based on the function call. Trailing arguments can have default values, in a similar way to templates. If there are fewer parameters in the invocation than in the definition, then the default values will be used for any trailing arguments. If is an error for the function to be invoke with more arguments than are defined.
function size ($width, $length, $scale = 1) {
...
}
Function parameters can also be defined using the "param" statement.
If the first line of a SLAX script begins with the characters "#!", the rest of that line is ignored. This allows scripts to be invoked directly by name on a unix command line. Additional details are available at the following site:
(Personally, I've never heard it pronounced "shebang" before; we call it "pound‑bang" around here, just as we say "pound define" rather than "hash define".)
See Argument Handling for details about the slaxproc utility argument handling and the "#!" line.
Scripts files can use multiple source files, allowing library files to be shared among a number of source files. The "import" and "include" statements incorporate the templates and functions from one file into another, allowing them to be used implicitly or explicitly by the XSLT engine. Files need not be SLAX files, but can be traditional XSLT files.
A script may include another script using the "include" statement. Content from included files is conceptually inserted into the script at the point where the "include" statement is used. The "import" statement works similarly, but the contents of an imported file are given a lower priority (for expression matching and template or function redefinition). Conceptually, imported files are inserted at the top of the script, in the order in which they are given in the source files.
The "key" statement defines a key for use by the "key()" XPath function. Keys allow a script to locate nodes in a document that are referenced by other nodes. Each key definition indicates the nodes on which the key is defined and the value of the key. The key() function can then be used to locate the appropriate node.
The "key‑name" uniquely identifies the key within the script, and is passed as the first argument to the key() function. The "match" statement gives an XPath expression selecting the set of nodes on which the keys are found, and the "value" statement gives an XPath expression for the value of the key.
The format-name is the name passed as the third argument to the format-number() XPath function. The statements under the "decimal‑format" statement follow the meaning of their counterparts under the <xsl:decimal‑format> element, as detailed in the reference below.
Typical scripts work by generating XML content as a result tree, but occasionally a script may need to make explicit, immediate output. The statements in this section allow for such output.
The message-expression is an XPath expression that is emitted as output, typically on the standard error file descriptor. Alternatively, a block of statements can be used to generate the content of the message. The output of the block will be converted to a string using the normal XSLT rules.
if (not(valid)) {
message name() _ " invalid";
} else if (failed) {
message {
expr "Failed";
if ($count > 1) {
expr ", again!";
}
}
}
The "terminate" statement mimics The "message" Statement. The message-expression is an XPath expression that is emitted as output, like the "message" statement. Alternatively, a block of statements can be used to generate the content of the message. The output of the block will be converted to a string using the normal XSLT rules.
After emitting the message, the script stops any further processing.
Trace output is vital to writing, debugging, and maintaining scripts. SLAX introduces a trace facility that will record XPath expressions or template contents in a trace file. If tracing is not enabled, then the trace template is not evaluated and no trace output is generated. The enabling of tracing and the naming of trace files is not covered here, since it is typically a feature of the environment in which a SLAX script is called. For example, the "slaxproc" command uses the "‑t" and "-T file" to enable tracing.
The trace-message is an XPath expression that is written to the trace file. The trace-template is executed and the results are written to the trace file.
SLAX includes a means of retaining or removing text nodes that contain only whitespace. Whitespace for XML is the space, tab, newline or carriage return characters.
The "strip‑space" statement mimics the <xsl:strip‑space> element, instructing the XSLT processor that certain elements should have internal whitespace removed.
The "preserve‑space" statement mimics the <xsl:preserve‑space> element, instructing the XSLT processor that certain elements should have internal whitespace retained.
The "version" statement contains the current version of the SLAX language, allowing scripts and interpreters to progress independently. Old engines will not understand new constructs and should stop with an error when a version number that is unknown to them is seen. New engines should accept any previous language version number, so allow old scripts to run on new engines.
SYNTAX::
'version' version-number ';'
The version-number should be either "1.1" or "1.0". The current version is "1.1" and newly developed scripts should use this version number.
version 1.1;
All SLAX stylesheets must begin with a "version" statement, which gives the version number for the SLAX language. This is currently fixed at "1.1" and will increase as the language evolves. Version 1.1 is completely backward compatible with version 1.0, but adds additional functionality that may cause issues for implementations of SLAX 1.0.
SLAX version 1.1 implies XML version 1.0 and XSLT version 1.1.
In addition, the "xsl" namespace is implicitly defined (as 'xmlns:xsl="http://www.w3.org/1999/XSL/Transform"').
SLAX defines a set of built-in extension functions. When one of these functions is referenced, the SLAX namespace is automatically added to the script.
Use the slax:base64-decode function to decode BASE64 encoded data. BASE64 is means of encoding arbitrary data into a radix-64 format that can be more easily transmitted, typically via STMP or HTTP.
The argument is a string of BASE64 encoded data and an option string which is used to replace any non-XML control characters, if any, in the decoded string. If this argument is an empty string, then non-xml characters will be removed. The decoded data is returned to the caller.
SYNTAX::
string slax:base64-decode(string [,control])
EXAMPLE::
var $real-data = slax:base64-decode($encoded-data, "@");
Use the slax:base64-encode function to encode a string of data in the BASE64 encoding format. BASE64 is means of encoding arbitrary data into a radix-64 format that can be more easily transmitted, typically via STMP or HTTP.
The argument is a string of data, and the encoded data is returned.
SYNTAX::
string slax:base64-encode(string)
EXAMPLE::
var $encoded-data = slax:base64-encode($real-data);
Use the slax:break-lines function to break multi-line text content into multiple elements, each containing a single line of text.
SYNTAX::
node-set slax:break-lines(node-set)
EXAMPLE::
var $lines = slax:break-lines(.//p);
for-each ($lines) {
if (start-with("pfe:", .)) {
<output> .;
}
}
For historical reasons, this function is also available using the name "slax:break_lines" (with an underscore instead of a dash). Scripts should however avoid using this name.
Use the slax:dampen() function to limit the rate of occurrence of a named event. If slax:dampen() is called more than "max" times in "time‑period" minutes, it will return "true", allowing the caller to react to this condition.
Use the slax:document() function to read a data from a file or URL. The data can be encoded in any character set (defaulting to "utf‑8") and can be BASE64 encoded. In addition, non-XML control characters, if any, can be replaced.
SYNTAX::
string slax:document(url [, options])
EXAMPLE::
var $data = slax:document($url);
var $options := {
<encoding> "ascii";
<format> "base64";
<non-xml> "#";
}
var $data2 = slax:document($url, $options);
Option
Description
<encoding>
Character encoding scheme ("utf-8")
<format>
"base64" for BASE64-encoded data
<non-xml>
Replace non-xml characters with this string
If the <non‑xml> value is an empty string, then non-xml characters will be removed, otherwise they will be replaced with the given string.
Use the slax:evaluate() function to evaluate a SLAX expression. This permits expressions using the extended syntax that SLAX provides in addition to what is allowed in XPath (see Expressions). The results of the expression are returned.
SYNTAX::
object slax:evaluate(expression)
EXAMPLE::
var $result = slax:evaluate("expr[name == '&']");
Use the slax:first-of() function to find the first value present in a test of arguments. The first non-empty or non-zero length string will be returned.
SYNTAX::
object slax:first-of(object+)
EXAMPLE::
var $title = slax:first-of(@title, $title, "Unknown");
Use the slax:get-command() function to return an input string provided by the user in response to a given prompt string. If the "readline" (or "libedit") library was found at install time, the returned value is entered in the readline history, and will be available via the readline history keystrokes (Control-P and Control-N).
SYNTAX::
string slax:get-command(prompt)
EXAMPLE::
var $response = slax:get-command("# ");
Use the slax:get-secret() function to return an input string provided by the user in response to a given prompt string. Any text entered by the user will not be displayed or echoed back to the user, making this function suitable for obtaining secret information such as passwords.
SYNTAX::
string slax:get-secret(prompt)
EXAMPLE::
var $response = slax:get-secret("Enter password: ");
Use the slax:printf() function to format text in the manner of the standard "C" "printf" function (printf(3)). The normal printf format values are honored, as are a number of "%j" extensions.
Match a regex, returning a node set of the full string matched plus any parenthesized matches. Options include "b", "i", "n", "^", and "$", for boolean results, ICASE, NEWLINE, NOTBOL, and NOTEOL.
SLAX is available as an open-source project with the "New BSD" license. Current releases, source code, documentation, and support materials can be downloaded from:
The core of the distribution is the libslax library, which incorporates a SLAX parser to read SLAX files, a SLAX writer to write SLAX files, a debugger, a profiler, and a commandline tool.
The reader turns a SLAX source file into an XSLT tree (xmlDocPtr) using the xsltSetLoaderFunc() hook. The writer turns an XSLT tree (xmlDocPtr) into a file containing SLAX statements.
To support SLAX in your application, link with libslax and call the libslax initializer function:
The SLAX software distribution contains a library (libslax) and a command line tool (slaxproc). The command line tool can be used to convert between XSLT and SLAX syntax, as well as run stylesheets and check syntax.
Usage: slaxproc [mode] [options] [script] [files]
Modes:
--check OR -c: check syntax and content for a SLAX script
--format OR -F: format (pretty print) a SLAX script
--json-to-xml: Turn JSON data into XML
--run OR -r: run a SLAX script (the default mode)
--show-select: show XPath selection from the input document
--show-variable: show contents of a global variable
--slax-to-xslt OR -x: turn SLAX into XSLT
--xml-to-json: turn XML into JSON
--xpath <xpath> OR -X <xpath>: select XPath data from input
--xslt-to-slax OR -s: turn XSLT into SLAX
Options:
--debug OR -d: enable the SLAX/XSLT debugger
--empty OR -E: give an empty document for input
--exslt OR -e: enable the EXSLT library
--expression <expr>: convert an expression
--help OR -h: display this help message
--html OR -H: Parse input data as HTML
--ignore-arguments: Do not process any further arguments
--include <dir> OR -I <dir>: search dir for includes/imports
--indent OR -g: indent output ala output-method/indent
--input <file> OR -i <file>: take input from the given file
--json-tagging: tag json-style input with the 'json' attribute
--keep-text: mini-templates should not discard text
--lib <dir> OR -L <dir>: search dir for extension libraries
--log <file>: use given log file
--mini-template <code> OR -m <code>: wrap template code in script
--name <file> OR -n <file>: read the script from the given file
--no-randomize: do not initialize the random number generator
--no-tty: Do not use tty for sdb and other input needs
--no-json-types: do not insert 'type' attribute for --json-to-xml
--output <file> OR -o <file>: make output into the given file
--param <name> <value> OR -a <name> <value>: pass parameters
--partial OR -p: allow partial SLAX input to --slax-to-xslt
--slax-output OR -S: emit SLAX-style XML output
--trace <file> OR -t <file>: write trace data to a file
--verbose OR -v: enable debugging output (slaxLog())
--version OR -V: show version information (and exit)
--write-version <version> OR -w <version>: write in version
Project libslax home page: https://github.com/Juniper/libslax
Files can be referenced positionally (where the script file first) or by using the "‑n", "‑i", and "‑o" options.
To use slaxproc to convert a SLAX file to XSLT:
$ slaxproc mine.slax new.xsl
To convert an XSLT file to SLAX:
$ slaxproc existing.xsl new.slax
To run a script:
$ slaxproc mine.slax infile.xml outfile.xml
Use the "‑g" option to produce good-looking output by enabling indenting (aka "pretty‑printing") of output. In this example, since the output filename is not given, the output is written to the standard output stream (stdout):
$ slaxproc -g mine.slax infile.xml
Use the "‑p" flag to perform conversion of SLAX and XML formats for partial data files. This can be used as a filter inside an editor to convert a region from one format to the other:
$ cat in.xml | slaxproc -s -p > out.slax
Use the "‑w" option to restrict the output of slaxproc to 1.0 features.
These options can be in any order and can be intermixed with other arguments. If none of the values are given, they can still be parsed positionally. In this example, the script name is positional but the input and output file names are positional.
SLAX supports the "#!" unix scripting mechanism, allowing the first line of a script to begin with the characters '#' and '!' followed by a path to the executable that runs the script and a set of command line arguments. For SLAX scripts, this might be something like:
#!/usr/bin/slaxproc -n
or:
#!/opt/local/bin/slaxproc -n
The operating system will add the name of the scripts and any command line arguments to the command line that follows the "#!". Adding the "‑n" option (as shown above) allows additional arguments to be passed in on the command line. Flexible argument parsing allows aliases and
The -E option tells slaxproc to use an empty input document, removing the need for the "‑i" option or a positional argument.
If the input or output arguments have the value "‑" (or is not given), the standard input or standard output file will be used. This allows slaxproc to be used as a traditional unix filter.
Command line options to slaxproc can be divided into two types. Mode options control the operation of slaxproc, and are mutually exclusive. Behavioral options tailor the behavior of slaxproc in minor ways.
Perform syntax and content check for a SLAX script, reporting any errors detected. This mode is useful for off-box syntax checks for scripts before installing or uploading them.
--format OR -F
Format (aka "pretty print") a SLAX script, correcting indentation and spacing to the style preferred by the author (that is, me).
--json-to-xml
Transform JSON input into XML, using the conventions defined in JSON Elements.
--run OR -r
Run a SLAX script. The script name, input file name, and output file name can be provided via command line options and/or using positional arguments as described in Argument Handling. Input defaults to standard input and output defaults to standard output. "‑r" is the default mode for slaxproc.
--show-select
Show an XPath selection from the input document. Used to extract selections from a script out for external consumption. This allows the consumer to avoid a SLAX parser, but still have visibility into the contents of the script.
--show-variable
Show contents of a global variable. Used to extract static variable contents for external consumption. This allows the consumer of the data to avoid a SLAX parser, but still have access to the static contents of global variables, such as the $arguments variable.
--slax-to-xslt OR -x
Convert a SLAX script into XSLT format. The script name and output file name can be provided via command line options and/or using positional arguments as described in Argument Handling.
--xml-to-json
Transform XML input into JSON, using the conventions defined in JSON Elements.
--xpath <xpath> OR -X <xpath>
Select data matching an XPath data from input document. This allows slaxproc to operate as a filter.
--xslt-to-slax OR -s
Convert a XSLT script into SLAX format. The script name and output file name can be provided via command line options and/or using positional arguments as described in Argument Handling.
Enable the SLAX/XSLT debugger. See The SLAX Debugger (sdb) for complete details on the operation of the debugger.
--empty OR -E
Provide an empty document as the input data set. This is useful for scripts that do not expect or need meaningful input.
--exslt OR -e
Enables the EXSLT library, which provides a set of standard extension functions. See exslt.org for more information.
--expression <expr>
Converts a SLAX expression to an XPATH one, or vice versa, depending on the presence of --slax-to-xslt and --xslt-to-slax.
--help OR -h
Displays this help message and exits.
--html OR -H
Parse input data using the HTML parser, which differs from XML.
--ignore-arguments
Do not process any further arguments. This can be combined with "#!" to allow distinct styles of argument parsing.
--include <dir> OR -I <dir>
Add a directory to the list of directories searched for include and /import files. The environment variable SLAXPATH can be set to a list of search directories, separated by colons.
--indent OR -g
Indent output to make it good looking. This option is identical to the behavior triggered by "output-method { indent 'true'; }".
--input <file> OR -i <file>
Use the given file for input.
--json-tagging
Tag JSON elements as they are parsing into XML with the 'json' attribute. This allows the --format mode to transform them back into JSON format.
--keep-text
When building a script from mini-templates, do not add a template to discard normal text. By default XSLT will display unmatched text data. This option preserves this default behavior instead of replacing it with the more typically desirable discard action.
--lib <dir> OR -L <dir>
Add a directory to the list of directories searched for extension libraries.
--log <file>
Write log data to the given file.
--mini-template <code> or -m <code>
Allows a simple script to be passed in via the command line using one of more "‑m" options. The argument to "‑m" is typically a template, such as a named or match template
--name <file> OR -n <file>
Read the SLAX script from the given file.
--no-json-types
Do not generate the 'type' attribute in the XML generated by --json-to-xml. This type is needed to 'round‑trip' data back into JSON, but is not needed for simple XML output.
--no-randomize
Do not initialize the random number generator. This is useful if you want the script to return identical data for a series of invocation, which is typically only used during testing.
--no-tty
Do not use tty for sdb and other tty-related input needs.
--output <file> OR -o <file>
Write output into the given file.
--param <name> <value> OR -a <name> <value>
Pass a parameter to the script using the name/value pair provided. Note that all parameters are string parameters, so normal quoting rules apply.
--partial OR -p
Allow the input data to contain a partial SLAX script, which can be used with the "‑‑slax‑to‑xslt" to perform partial transformations.
--slax-output OR -S
Write the result using SLAX-style XML (braces, etc)
--trace <file> OR -t <file>
Write trace data to the given file.
--verbose OR -v
Adds very verbose internal debugging output to the trace data output, including calls to the slaxLog() function.
--version OR -V
Show version information and exit.
--write-version <version> OR -w <version>
Write in the given version number on the output file for "‑x" or "‑s" output. This can be also be used to limit the conversion to avoid SLAX 1.1 feature (using "-w 1.0").
The SLAX distribution includes a debugger called "sdb", which can be accessed via the "slaxproc" command using the "‑d" option. The debugger resembles "gdb" command syntax and operation.
(sdb) help
List of commands:
break [loc] Add a breakpoint at [file:]line or template
callflow [val] Enable call flow tracing
continue [loc] Continue running the script
delete [num] Delete all (or one) breakpoints
finish Finish the current template
help Show this help message
info Showing info about the script being debugged
list [loc] List contents of the current script
next Execute the over instruction, stepping over calls
over Execute the current instruction hierarchy
print <xpath> Print the value of an XPath expression
profile [val] Turn profiler on or off
reload Reload the script contents
run Restart the script
step Execute the next instruction, stepping into calls
where Show the backtrace of template calls
quit Quit debugger
The "info" command can display the following information:
(sdb) info help
List of commands:
info breakpoints Display current breakpoints
info profile [brief] Report profiling information
Many of these commands follow their "gdb" counterparts, to the extent possible.
The location for the "break", "continue", and "list" commands can be either a line number of the current file, a filename and a line number, separated by a colon, or the name of a template.
(sdb) b 14
Breakpoint 1 at file ../tests/core/test-empty-21.slax, line 14
(sdb) b 19
Breakpoint 2 at file ../tests/core/test-empty-21.slax, line 19
(sdb) b three
Breakpoint 3 at file ../tests/core/test-empty-21.slax, line 24
(sdb) info br
List of breakpoints:
#1 template one at ../tests/core/test-empty-21.slax:14
#2 template two at ../tests/core/test-empty-21.slax:19
#3 template three at ../tests/core/test-empty-21.slax:24
(sdb)
Information on the profiler is in the next section (The SLAX Profiler).
The SLAX debugger includes a profiler which can report information about the activity and performance of a script. The profiler is automatically enabled when the debugger is started, and tracks script execution until the script terminates. At any point, profiling information can be displayed or cleared, and the profiler can be temporarily disabled or enabled.
Use the "profile" command to access the profiler:
(sdb) help profile
List of commands:
profile clear Clear profiling information
profile off Disable profiling
profile on Enable profiling
profile report [brief] Report profiling information
(sdb)
The profile report includes the following information:
Line -- line number of the source file
Hits -- number of times this line was executed
User -- the number of microseconds if "user" time spent processing this line
U/Hit -- average number of microseconds per hit
System -- the number of microseconds if "system" time spent processing this line
S/Hit -- average number of microseconds per hit
Source -- Source code line
The "brief" option instructs sdb to avoid showing lines that were not hit, since there is no valid information for them. Without the "brief" option, dashes are displayed.
In the following example, the source code data is heavily truncated (with "....") to allow the material to fit on this page. sdb would not truncate these lines:
(sdb) run
<?xml version="1.0"?>
<message>Down rev PIC in Fruvenator, Fru-Master 3000</message>
Script exited normally.
(sdb) profile report
Line Hits User U/Hit System S/Hit Source
1 - - - - - version 1.0;
2 - - - - -
3 2 4 2.00 8 4.00 match / {
4 1 25 25.00 13 13.00 var ....
5 - - - - -
6 - - - - - for-each....
7 1 45 45.00 10 10.00 ..
8 1 12 12.00 5 5.00 <message>
9 1 45 45.00 15 15.00 ....
10 - - - - - }
11 - - - - - }
Total 6 131 51 Total
(sdb) pro rep b
Line Hits User U/Hit System S/Hit Source
3 2 4 2.00 8 4.00 match / {
4 1 25 25.00 13 13.00 var ....
7 1 45 45.00 10 10.00 ....
8 1 12 12.00 5 5.00 <message>
9 1 45 45.00 15 15.00 ....
Total 6 131 51 Total
(sdb)
This information not only shows how much time is spent during code execution, but also shows which lines are being executed, which can help debug scripts where the execution does not match expectations.
The profiling is not "Monte Carlo", or clock based, but is based on trace data generated as each SLAX instruction is executed, giving more precise data.
The "callflow" command enables the printing of informational data when levels of the script are entered and exited. The lines are simple, but reference the instruction, filename, and line number of the frame:
callflow: 0: enter <xsl:template> in match / at empty-15.slax:5
callflow: 1: enter <xsl:variable> at empty-15.slax:13
callflow: 1: exit <xsl:variable> at empty-15.slax:13
callflow: 1: enter <xsl:variable> at empty-15.slax:20
callflow: 1: exit <xsl:variable> at empty-15.slax:20
callflow: 0: exit <xsl:template> in match / at empty-15.slax:5
libslax supports a means of dynamically loading extension libraries. After a script is parsed, any extension prefixes are determined, along with their namespaces. Each namespace are then URL-escaped and a ".ext" suffix is appended to generate a filename for the extension library that supports that namespace.
Extension libraries should be placed in the directory named that is either:
the /usr/local/lib/slax/extensions directory
the "lib/slax/extensions" directory under the "configure --prefix" option given at build time
the directory specified with the "configure --with-extension-dir" option given at build time
one of the directories listed in the environment variable SLAX_EXTDIR (colon separated)
one of the directories provided via the --lib/-L argument to "slaxproc"
The "bit" extension library has functions that interpret a string as a series of bit, allowing arbitrary length bit arrays and operations on those arrays.
curl and libcurl are software components that allow access to a number of protocols, include http, https, smtp, ftp, and scp. They are open source and available from the web site http://curl.haxx.se/libcurl/. Please refer to that site for additional information.
Curl operations are directed using a set of elements, passed to the curl extension functions. These elements closely mimic the options used by the native C libcurl API via libcurl's curl_easy_setopt() function), as documented on the following web page:
Once these values are set, a call to curl_easy_perform() performs the requested transfer.
In the SLAX curl extension library, these options are represented as individual elements. This means the <url> element is mapped to the "CURLOPT_URL" option, the <method> option is mapped to the CURLOPT_CUSTOMREQUEST option, and so forth.
These elements can be used in three ways:
The curl:single() extension function allows a set of options to be used in a single transfer operation with no persistent connection handle.
The curl:perform() extension function allows a set of options to be used with a persistent connection handle. The handle is returned from the curl:open() extension function and can be closed with the curl:close() extension function.
The curl:set() extension function can be used to record a set of options with a connection handle and keep those options active for the lifetime of the connection.
For example, if the script needs to transfer a number of files, it can record the <username> and <password> options and avoid repeating them in every curl:perform() call.
This section gives details on the elements supported. The functions themselves are documented in the next section (curl Extension Functions).
The <errors> element controls how HTML and XML parsing errors are handled. The default (display them on stderr) is often not attractive. Instead errors can be ignored, logged, or recorded, based on the contents of the <errors> element:
Value
Special behavior
default
Errors are displayed on stderr
ignore
Errors are discarded
log
Errors are logged (via slaxLog())
record
Errors are recorded
When <errors> is set to "record", all errors appear in a string under the <errors> element in the XML node (as returned by e.g. curl:perform). If no errors are generated, the <errors> element will not be present, allowing it to be used as a test for errors:
var $opt = {
<url> $url;
<format> "html";
<errors> "record";
}
var $res = curl:single($opts);
if ($res/errors) {
terminate "failure: " _ $res/errors;
}
The <format> element gives the expected format of the returned results, allowing the curl extension to automatically make the content available in the native format.
<format> "xml";
Format name
Special behavior
html
Result is parsed as HTML
name
Result is name=value pairs
text
None
url-encoded
Result is n1=v1&n2=v2 pairs
xml
Result is parsed as XML
The "name" encoding is used for name=value pairs separated by newlines, where the url-encoded encoding is used when the name=value pairs are separated by "&".
The parsed data is returned in the <data> element, using <name> elements. In the following example results, <format> was set to "url‑encoded":
The <insecure> element indicates a willingness to tolerate insecure communications operations. In particular, it will allow SSL Certs without checking the common name.
The <verbose> element requests an insanely detailed level of debug information that can be useful when debugging requests. The curl extension will display detailed information about the operations and communication of the curl transfer.
The "curl:perform" extension function performs simple transfers using a persistent connection handle, as provided by curl:open (curl:open).
The arguments are the connection handle and a set of option elements as listed in curl Elements. The returned object is an XML hierarchy containing the results of the transfer.
SYNTAX::
object curl:perform(handle, options)
The returned object may contain the following elements:
Element
Contents
url
The requested URL
curl-success
Indicates sucess (empty)
raw-headers
Raw header fields from the reply
raw-data
Raw data from the reply
error
Contains error message text, if any
header
Parsed header fields
data
Parsed data
The <header> element can contain the following elements:
Element
Contents
code
HTTP reply code
version
HTTP reply version string
message
HTTP reply message
field
HTTP reply fields (with @name and value)
The following is an example of the <header> element, with header fields parsed into <field> elements.
This script gets a vanilla web page, but just to be interesting, includes a header field for the HTTP header and a parameter that is incorporated into the requested URL.
Used to specify the backend database engine that is used to access/store data
<engine> "sqlite";
sqlite is the currently supported backend engine. <engine> element can also take mysql/mongodb as values once the adapters for corresponding database engines are made available
Represents a single instance from collection. Contains fields and their corresponding values in that record. Used when inserting/manipulating data in the datastore. Example,
Used to specify a condition that must to be satisfied when operating with data instances from datastore. This forms the condition used with WHERE clause when operating with SQL datastore
<condition> will contain following mandatory elements
Element
Description
selector
Name of the field to apply this condition
operator
Operator for this condition. Can take one of comparison
or logical operators (<, >, <=, >=, =, LIKE, IN, NOT)
Used to specify the fields and order by which the result set must be sorted before returning. List of fields can be specified using <by> and order using <order>. "asc" and "desc", corresponds to ascending and descending order are the only valid values for order, which defaults to asc.
Used to open a database connection using provided options. Options contain backend engine and database name. For example,
var $options = {
<engine> "sqlite";
<database> "test.db";
}
var $handle = db:open($options);
db:open() returns database handle (a session cookie), that can be used to perform further operations on this opened connection.
If sqlcipher is available, db:open() also takes an optional <access> config where in we can specify the key that can be used to encrypt/decrypt the database. In such a case, options will look as below
db:create() returns result node which contains status of the operation. Status is returned as ok in case of success and has error with message if it fails
db:insert() returns a result node and includes result of operation in status. Status is returned as ok in case of success and error with message in case of failure.
Above example will update all the employee instances whose id is greater than 2 with names Monica, to Ross. db:update() returns result set like create/insert with status of the operation.
Above operation deletes up to 10 employee instances whose id is greater than 4. db:delete() returns result set with status similar to insert/update/create operations
Above example returns a cursor to result set containing names first 10 employees skipping first 5 whose id is more than 5 sorted in descending order. We will have to use db:fetch() call to retrieve each of these result instances.
This function call is used to fetch result instance using cursor returned from find/query call.
var $result = db:fetch($cursor);
Returned result contains status and <instance> with fields and values if available. Status will be returned as <data> if instance is available. It will be <done> if the query ran to completion and no more data is available to be read.
db:fetch() also takes an optional second argument that can be used to specify additional data. This can be useful when fetching on the cursor returned from custom query using db:query().
This function can be used to run custom queries. For example,
var $queryString = "INSERT INTO employee (id, name) "
_ "VALUES (@id, @name)";
var $cursor = db:query($queryString);
var $input = {
<id> 11;
<name> "Phoebe";
}
var $result = db:fetch($cursor, $input);
Above example runs a custom query and receives cursor from db:query() and used db:fetch() to insert data into employee collection using the previous cursor.
SLAX and XSLT use recursion as a programming tool for iteration, but unlimited recursion can lead to disaster. To avoid this, the libxslt engine limits the depth of recursive calls to 3,000. This limit should be find for almost all uses, but it the value is not suitable, it can be adjusted using the xutil:max-call-depth() function.
If invoked without an argument, the function returns the current value. If a number is passed as the argument, that number is used as the new max call depth limit.
EXAMPLE::
var $limit = xutil:max-call-depth();
expr xutil:max-call-depth($limit * 2);
The xutil:xml-to-string() function turns XML content into a string. This is different than the normal XPath stringification, which discards open and close tag. xml-to-string will encode tags as part of the string.
EXAMPLE::
var $xml = <dog> "red";
var $str = xutil:xml-to-string($xml);
/* str is now the string "<dog>red</dog>" */
The xutil:json-to-xml() function turns a string containing JSON data into the native representation of that data in XML.
EXAMPLE::
var $data = "[ { "a" : 4, "name": "fish"}, 4, 5]";
var $xml = xutil:json-to-xml($data);
message "title is " _ $xml/json/name;
An optional second parameter contains a node set of the following optional elements:
Element
Value
Description
types
"no"
Do not encode type information
root
string
Name of root node to be returned ("json")
var $options = {
<root> "my-top";
<types> "no";
}
var $xml = xutil:json-to-xml($data, $options);
The XML returned from json-to-xml() is decorated with attributes (including the "type" and "name" attributes) which allow the data to be converted back into JSON using xml-to-json(). Refer to that function for additional information.
The xutil:xml-to-json() function turns XML content into a string of JSON data. This is different than the normal XPath stringification, which discards open and close tag. xml-to-json will encode tags as JSON objects inside a string
EXAMPLE::
var $xml = <json> {
<color> "red";
}
var $str = xutil:xml-to-json($xml);
/* str is now the string '{ "color": "red" }' */
An optional second parameter contains a node set of the following optional elements:
The "os" extension library provides a set of functions to invoke operating system-related operations on the local host. Note that these are _not_ run on the remote target, but on the machine where the script is being executed.
The return value of many os:* functions consists of a set of zero or more error nodes. Each node may contain an <error> element, which in turn may contain the following elements:
Element
Description
errno
Error message based on errno
path
The path that triggered the error
message
The error message
In addition, the <errno> element contains a "code" attribute which holds the tag for the errno value, if known.
<error>
<errno code="ENOENT">No such file or directory</errno>
<message>unknown group: phil</message>
</error>
The os:chmod function changes the permissions of one or more files, allowing or preventing different sets of users from accessing those files.
The first argument is a mode specification similar to the chmod command, with either an octal number to set the permissions to, or an expression consisting of one or more of the letters 'u', 'g', 'o', and 'a' (for user, group, other, and all) followed by '+' or '‑' (for setting or clearing) and one or more of the letters 'r', 'w', and 'x' (for read, write, and execute). The expression "ug+rw" would give read and write permission to the user and group which own the file or directory.
The second and subsequent arguments can be either a path name string or a nodeset of <file>, <directory>, and <wildcard> elements, with the former two contain path for files and directories, and the latter contains shell/glob-style wildcard patterns. The following patterns are permitted:
'*' matches zero or more characters
'?' matches any single character
'[...]' matches any of the enclosed characters
'{...}' matches any of the enclosed comma-separated sequences
For example, <wildcard> "*.txt" matches all text files.
The second and subsequent arguments can be either a path name string or a nodeset of <file>, <directory>, and <wildcard> elements. See os:chmod for details.
The os:exit-code function sets the exit code for the process running the script. This can be used to indicate an error to the caller. The argument to the function is the exit code value, in numeric form.
The os:mkdir function makes directories, similar to the "mkdir" command or the POSIX "mkdir" library function. These are two arguments; the first is name of the directory to be made and the second is an node-set containing options to be used during the operation. The options can include the values in the following table.
Element
Description
create
Error if the last element of the path exists
mode
Octal value of directory permissions
path
Create intermediate directories as required
SYNTAX::
node-set os:mkdir(path [, options]);
EXAMPLE::
var $res = os:mkdir("/tmp/foo");
var $opts = {
<mode> "0700";
<path>;
<create>;
}
var $res2 = os:mkdir("/tmp/foo/a/b/c/d/e/f", $opts);
If the value for <mode> is a string, it will be converted to an integer using the default numeric base of 8 (octal), so '<mode> "644"' will work, but '<mode> 644' will see 644 as a number with base 10 (decimal), which will result in undesirable results since 644 base 10 is 01204 base 8.
If the value of <mode> is not a valid mode integer value, it will be ignored.
The return value of os:mkdir is a node-set which may contain an <error> element is an error occurred. This element may contain the following elements:
Element
Description
errno
Error message based on errno
path
The path that triggered the error
message
The error message
In addition, the <errno> element contains a "code" attribute which holds the tag for the errno value, if known.
If successful, nothing is returned. On failure, a set of error nodes is returned. See Error Nodes for details.
The os:stat function returns information about files and directories, similar to the POSIX stat() function, returning a node-set of <entry> elements containing details about each file.
The arguments to the os:stat() function are either strings or node-sets. os:stat() allows any number of arguments. If the argument is a string, it is used as a path specification and information on matching files is returned. The path specification can include glob-style wildcards (e.g. test*.c, *.slax). If the argument is a node-set, then the node can contains the following elements:
Element
Description
brief
Only summary information is emitted
depth
The number of subdirectory levels to descend
hidden
Return information on hidden files
name
File specification (same as string argument)
recurse
Show all subdirectories
Note the <name> element functions identically to the string argument details given above.
var $files = os:stat("/etc/m*");
var $options = {
<hidden>;
<depth> 3;
}
var $logs = os:stat("/var/log/*txt", $options);
for-each ($logs) {
message name _ " is a " _ type;
}
The return value is a node-set of <entry> elements. Each entry contains the following elements:
Element
Description
Brief
name
Path to the entry
Y
type
Type of file (see below)
Y
executable
Present if entry is executable
Y
symlink
Present if entry is a symbolic link
Y
symlink-target
Contents of the symbolic link
N
permissions
Permissions for the entry
N
owner
Name of the owning user
N
group
Name of the owning group
N
links
Number of hard links to this entry
N
size
Number of bytes used by the entry
N
date
Time and date of last modification
N
entry
Directory contents
N
Only elements tagged "Y" are emitted when the <brief> option is used.
The <type> element contains one of the following:
Value
Description
directory
Directory containing additional files
file
Regular file
character
Character-oriented device (tty)
block
Block-oriented device (disk)
link
Symbolic link
socket
AF_UNIX Socket
fifo
Named pipe (First-in/first-out)
unknown
Other/unknown file type
In some cases, attributes are used to attach useful information to elements. The following table lists these attributes and values.
This section contains a few examples, converted from the libxslt test/ directory. The XSLT form can be found in the libxslt source code. They were converted using the "slaxproc" tool.
XPath expressions use a style of type promotion that coerces values into the particular type needed for the expression. For example, if a predicate refers to a node, then that predicate is true if the node exists. The value of the node is not considered, just it's existence.
For example, the expression "chapter[section]" selects all chapters that have a section element as a child.
Similarly, if a predicate uses a function that needs a string, the argument is converted to a string value by concatenating all the text values of that node and all that node's child elements.
For example, the expression "chapter[starts-with(section, 'A')]" will inspect all <chapter> elements, convert their <section> elements to strings, and select those whose string value starts with 'A'. This may be an expensive operation.
As the last touch to SLAX-1.1, I've added the ternary operator from C, allowing expressions like:
var $a = $b ? $c : $d;
var $e = $f ?: $g;
The caveat is that this uses an extension function slax:value() which may not be available in all XSLT environments. Coders must consider whether should a restriction deems this operator unusable. Portability considerations are identical to mutable variables (mvars).
XSLT has immutable variables. This was done to support various optimizations and advanced streaming functionality. But it remains one of the most painful parts of XSLT. We use SLAX in JUNOS and provide the ability to perform XML-based RPCs to local and remote JUNOS boxes. One RPC allows the script to store and retrieve values in an SNMP MIB (the jnxUtility MIB). We have users using this to "fake" mutable variables, so for our environment, any theoretical arguments against the value of mutable variables are lost. They are happening, and the question becomes whether we want to force script writers into mental anguish to allow them.
Yes, exactly. That was an apologetical defense of the following code, which implements mutable variables. Dio, abbi pieta della mia anima.
The rest of this section contacts mind-numbing comments on the implementation and inner working of mutable variables.
For the typical scriptor, the important implications are:
Non-standard feature: mutable variables are not available outside the libslax environment. This will significantly affect the portability of your scripts. Avoid mutable variables if you want to use your scripts in other XSLT implementations or without libslax.
Memory Overhead: Due to the lifespan of XML elements and RTFs inside libxslt, mutable variables must retain copies of their previous values (when non-scalar values are used) to avoid dangling references. This means that heavy use of mutable variables will significantly affect memory overhead, until the mutable variables fall out of scope.
Axis Implications: Since values for mutable variables are copied (see above), the operations of axes will be affected. This is a relatively minor issue, but should be noted.
Let's consider the memory issues associated with mutable variables. libxslt gives two ways to track memory WRT garbage collection:
contexts
as RTF/RVT (type XPATH_XSLT_TREE)
variables
strings (simple; forget I even mentioned them) or node sets (type XPATH_NODESET)
via the nodesetval field
does not track nodes, but references nodes in other trees
The key is that by having node sets refer to nodes "in situ" where they reside on other documents, the idea of refering to nodes in the input document is preserved. Node sets don't require an additional memory hook or a reference count.
The key functions here are xmlXPathNewValueTree() and xmlXPathNewNodeSet(). Both return a fresh xmlXPathObject, but xmlXPathNewValueTree will set the xmlXPathObject's "boolval" to 1, which tells xmlXPathFreeObject() to free the nodes contained in a nodeset, not just the nodeTab that holds the references.
Also note that if one of the nodes in the node set is a document (type XML_DOCUMENT_NODE) then xmlFreeDoc() is called to free the document. For RTFs, the only member of the nodeset is the root of the document, so freeing that node will free the entire document.
All this works well for immutable objects and RTFs, but does not allow my mutable variables to work cleanly. This is quite annoying.
I need to allow a variable to hold a nodeset, a document, or a scalar value, without caring about the previous value. But I need to hold on to the previous values to allow others to refer to them without dangling references. Dangling References
Consider the following input document:
<top>
<x1/>
<x2/>
<x3/>
</top>
The following code makes a nodeset (type XPATH_NODESET) whose nodeTab array points into the input document:
var $x = top/*[starts-with("x", name())];
The following code make an RTF/RVT (type XPATH_XSLT_TREE), whose "fake" document contains a root element (type XML_DOCUMENT_NODE) that contains the "top" element node.
var $y = <top> {
<x1>;
<x2>;
<x3>;
}
The following code makes a nodeset (type XPATH_NODESET) that refers to nodes in the "fake" document under $y:
var $z = $y/*[starts-with("x", name())];
Now consider the following code:
mvar $z = $y/*[starts-with("x", name())];
var $a = $z[1];
if ($a) {
set $z = <rvt> "a"; /* RVT */
var $b = $z[1]; /* refers to nodes in "fake" $y doc */
set $z = <next> "b"; /* RVT */
var $c = $z[1]; /* refers to node in <next> RVT */
<a> $a;
<b> $b;
<c> $c;
}
In this chunk of code, the changing value of $z cannot change the nodes recorded as the values of $a, $b, or $c. Since I can't count on the context or variable memory garbage collections, my only choice is to roll my own. This is quite annoying.
The only means of retaining arbitrary previous values of a mutable variable is to have a complete history of previous values.
The "overhead" for an mvar must contain all previous values for the mvar, so references to the node in the mvar (from other variables) don't become dangling when those values are freed. This is not true for scalar values that do not set the nodesetval field.
Yes, this is pretty much as ugly as it sounds. After a variable has been made, it cannot be changed without being risking impacting existing references to it.
So a mutable variable needs to make two things, a real variable, whose value can be munged at will, and a hook to handle memory management.
The Rules
Assigning a scalar value to an mvar just sets the variables value (var->value).
Assigning a non-scalar value to an mvar means making deep copy, keeping this copy in "overhead".
But where does the "overhead" live?
In classic SLAX style, the overhead is kept in a shadow variable. The shadow variable (svar) holds an RTF/RVT that contains all the nodes ever assigned to the variable, a living history of all values of the variable.
where slax:mvar-init() is an extension function that returns the value of another variable, either as a straight value or as a nodeset.
If an mvar is only ever assigned scalar values, the svar will not be touched. When a non-scalar value is assigned to an mvar, the content is copied to the svar and the mvar is given a nodeset that refers to the content inside the svar. Appending to a mvar means adding that content to the svar and then appending the node pointers to the mvar.
If the mvar has a scalar value, appending discards that value. If the appended value is a scalar value, then the value is simply assigned to the mvar. This will be hopelessly confusing, but there's little that can be done, since appending to an RTF to a number or a number to an RTF makes little sense. We will raise an error for this condition, to let the scriptor know what's going on. Memory
When the mvar is freed, its "boolval" is zero, so the nodes are not touched but the nodesetval/nodeTab are freed. When the svar is freed, its "boolval" is non-zero, so xmlXPathFreeObject will free the nodes referenced in the nodesetval's nodeTab. The only node there will be the root document of a "fake" RTF document, which will contain all the historical values of the mvar. In short, the normal libxslt memory management will wipe up after us. Implications
The chief implications are:
memory utilization -- mvar assignments are very sticky and only released when the mvar (and its svar) go out of scope
axis -- since the document that contains the mvar contents is a living document, code cannot depend on an axis staying unchanged. I'm not sure of what this means yet, but following::foo is a nodeset that may change over time, though it won't change once fetched (e.g. into a specific variable).
I have worked with XSLT for over ten years, as part of my work for Juniper Networks. Beginning in 2001, we made an XML API for our line of routers so that any command that can be issued at the command line (CLI) can be issued as an XML RPC and the response received in XML. This work was the foundation for the IETF NETCONF protocol (RFC6241) (see also RFC6244).
Internally, we used this API with XSLT to make our Junoscope network management platform, and were happy working with XSLT using multiple XSLT implementations.
In the 2005-2006 timeframe, we started developing on-box script capabilities using XSLT. I like the niche and properties of XSLT, but the syntax makes development and maintenance problematic. First class constructs are buried in attributes, with unreadable encodings. Customers objections were fairly strong, and they asked for a more perl-like syntax. SLAX was our answer.
SLAX simplifies the syntax of XSLT, making an encoding that makes scripts more readable, maintainable, and helps the reader to see what's going on. XML escaping is replaced by unix/perl/c-style escaping. Control elements like <xsl:if> are replaced with the familiar "if" statement. Minor details are more transparent.
The majority of our scripts are simple, following the pattern:
if (find/something/bad) {
call error($message = "found something bad");
}
The integration of XPath into familiar control statements make the script writers job fairly trivial.
At the same time, using XSLT constrains our scripting environment and limits what scripts can and cannot do. We do not need to worry about system access, processes, connections, sockets, or other features that are easily available in perl or other scripting languages. The scripts emit XML that instructs our environment on what actions to take, so those actions can be controlled.
So SLAX meets our needs. I hope making this an open source projects allows it to be useful to a broader community.
The SLAX language is named for "eXtensible Stylesheet Language Alternate syntaX". Juniper started development on SLAX as part of the on-box scripting features in the 2004/2005 time frame. The name "SLAX" was adopted after the Juniper management requested that we remove the leading "X" from the original internal name.
At about this same time, the "SLAX" linux distro was named, but not being involved in the linux world (we're a FreeBSD house), we were not aware of this name conflict for many years.
When we were made aware of the name conflict, we consulted with various parts of the Juniper family, and no one was interested in changing the language name. We repeated this procedure as we were publishing this open source version, but again, no one was interested in doing the internal and external work to change the language name, since the name conflict was considered minor and not an issue for our customers.
libslax provides a means of dynamically loading extension libraries based on the contents of the "extension‑element‑prefixes" attribute. During initialization, a parsed SLAX document is inspected for both the "extension‑element‑prefix" attribute on the <stylesheet> element, and the "xsl:extension‑element‑prefix" on arbitrary tags.
The prefixes found in the document are translated into namespace URIs, which are then escaped using the URL-escaping algorithm that turns unacceptable characters into three character strings like "%2F".
An extension of ".ext" is appended to the URL-escaped URI and this file is searched but in ${SLAX_EXTDIR} and any directories given via the "‑‑lib/‑L" argument.
When the extension library is found, we dlopen() it and look for the magic symbol "slaxDynLibInit". This function is called with two arguments, the API version number and a struct that contains information about the extension library.
/*
* This structure is an interface between the libslax main code
* and the code in the extension library. This structure should
* only be extended by additions to the end.
*/
typedef struct slax_dyn_arg_s {
unsigned da_version; /* Version of the caller */
void *da_handle; /* Handle from dlopen() */
void *da_custom; /* Memory hook for the extension */
char *da_uri; /* URI */
slax_function_table_t *da_functions; /* Functions */
slax_element_table_t *da_elements; /* Elements */
} slax_dyn_arg_t;
The da_functions and da_elements allow the library to register and unregister there functions and elements.
At cleanup time, in addition to removing functions and elements, a search is made for the library to find the symbol "slaxDynLibClean". If the symbol is found, it is called also.
The file slaxdyn.h defines some macros for helping define extension libraries.
When a default prefix is used with an extension library, the prefix is mapped to the extension using a symbolic link ("ln -s"). The source of the symlink should be the prefix with a ".prefix" appended and should be located in the extension directory ("${SLAX_EXTDIR}"). The target of the symlink should be the namespace URI with the escaping and ".ext" extension as described above.
See the Makefile.am file in any of the libslax extension directories for examples of creating the appropriate symlink.
My documentation style tends to be man-page-like, rather than tutorial-ish. But folks at Juniper Networks have made some outrageously great documentation and it's available on the Juniper website. Many thanks for Curtis Call, Jeremy Schulman, and others for doing this work.
This Week: Junos Automation Reference for SLAX 1.0 does not cover any of the new SLAX-1.1 material, but is an incredible reference book.