Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The sxml Command

sxml xpath is one of several commands. Each one does one job, and they all work the same way: options start with --, results go to standard output, and errors go to standard error with the file, line and column where they happened. Let's take a quick tour, using the same draft.xml as before. The commands for queries, stylesheets and schemas each have a chapter of their own later in the book.

Checking documents

sxml check checks that documents are well-formed: that they follow the rules of XML, such as every start tag having a matching end tag. When they do, it prints nothing and exits with status 0:

$ sxml check draft.xml; echo "exit status $?"
exit status 0

This file has a paragraph t that is never closed:

broken.xml:

<rfc docName="draft-example-vane-protocol-01">
  <middle>
    <section anchor="states">
      <name>Flag States</name>
      <t anchor="req-2">A flag <bcp14>MUST</bcp14> be stable.
    </section>
  </middle>
</rfc>
$ sxml check broken.xml; echo "exit status $?"
broken.xml:6:5: not well-formed: the text does not match the XML 1.0 grammar here (XML 1.0, section 2.1)
exit status 1

The parser noticed the problem at line 6, where </section> arrived while t was still open.

Validating documents

Well-formed is only the first step. A schema describes what a kind of document must contain: which elements, in which order, with which types of values. draft.xsd is an XSD schema for our draft. It says, among other things, that each section has a name and then one or more paragraphs, and that a bcp14 holds one of five key words:

draft.xsd:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="rfc">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="front">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="title" type="xs:string"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="middle">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="section" type="section" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
      <xs:attribute name="docName" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="section">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="t" type="paragraph" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="anchor" type="xs:NCName" use="required"/>
  </xs:complexType>
  <xs:complexType name="paragraph" mixed="true">
    <xs:sequence>
      <xs:element name="bcp14" type="keyword" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="anchor" type="xs:NCName"/>
  </xs:complexType>
  <xs:simpleType name="keyword">
    <xs:restriction base="xs:string">
      <xs:enumeration value="MUST"/>
      <xs:enumeration value="MUST NOT"/>
      <xs:enumeration value="SHOULD"/>
      <xs:enumeration value="SHOULD NOT"/>
      <xs:enumeration value="MAY"/>
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

sxml validate checks documents against it. Our draft is valid:

$ sxml validate --schema=draft.xsd draft.xml

This one is not:

invalid.xml:

<rfc docName="draft-example-vane-protocol-01">
  <front>
    <title>The Vane Protocol for Feature Flag Rollouts</title>
  </front>
  <middle>
    <section anchor="states">
      <t anchor="req-2">A flag <bcp14>MIGHT</bcp14> be stable when it is created.</t>
    </section>
  </middle>
</rfc>
$ sxml validate --schema=draft.xsd invalid.xml
invalid.xml:7:7: cvc-complex-type.2.4: element 't' is not allowed here; expected 'name'
invalid.xml:7:32: cvc-enumeration-valid: element 'bcp14' has the value 'MIGHT', which is not valid for type 'keyword': the value is not one of the enumeration facets (XSD 1.1 Part 2, 4.3.5.4); the enumeration facets are 'MUST', 'MUST NOT', 'SHOULD', 'SHOULD NOT', 'MAY'

The section has lost its name, and the requirement says MIGHT, which is not a key word. Each error names the rule of XSD that failed, such as cvc-complex-type.2.4, and says what was expected.

Running queries

sxml xquery runs XQuery, a programming language that contains XPath and adds variables, loops, sorting, functions and a way to build new XML. This query makes an index of the requirements, sorted by key word:

requirements.xq:

declare option output:indent "yes";

<requirements>{
  for $t in //t[@anchor]
  order by $t/bcp14, $t/@anchor
  return <requirement anchor="{ $t/@anchor }" keyword="{ $t/bcp14 }">{
    normalize-space($t)
  }</requirement>
}</requirements>
$ sxml xquery --context-item=draft.xml requirements.xq
<requirements>
  <requirement anchor="req-2" keyword="MUST">A flag MUST be stable when it is created.</requirement>
  <requirement anchor="req-7" keyword="MUST">After three failed checks, a service MUST pause the flag.</requirement>
  <requirement anchor="req-3" keyword="SHOULD">A rolling-out flag SHOULD be stable again after ten minutes.</requirement>
</requirements>

The for clause goes through the requirements, order by sorts them by key word and then by anchor, and return builds a new requirement element for each one. The curly braces put computed values into the XML. The first line asks for indented output.

A short query can be given on the command line with --expression:

$ sxml xquery --expression='<greeting>{ "Hello, " || "XQuery" }</greeting>'
<greeting>Hello, XQuery</greeting>

Transforming documents

sxml transform runs an XSLT stylesheet. XSLT works by rules: each template says what to make when the transformation meets a certain kind of node. This stylesheet turns the draft into an HTML outline:

outline.xsl:

<xsl:stylesheet version="4.0" expand-text="yes"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" indent="yes"/>
  <xsl:template match="/rfc">
    <html>
      <body>
        <h1>{front/title}</h1>
        <xsl:apply-templates select="middle/section"/>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="section">
    <h2>{position()}. {name}</h2>
    <ul>
      <xsl:apply-templates select="t[@anchor]"/>
    </ul>
  </xsl:template>
  <xsl:template match="t">
    <li id="{@anchor}">{.}</li>
  </xsl:template>
</xsl:stylesheet>
$ sxml transform outline.xsl draft.xml
<!DOCTYPE html>
<html>
  <body>
    <h1>The Vane Protocol for Feature Flag Rollouts</h1>
    <h2>1. Flag States</h2>
    <ul>
      <li id="req-2">A flag MUST be stable when it is created.</li>
      <li id="req-3">A rolling-out flag SHOULD be stable again after ten minutes.</li>
    </ul>
    <h2>2. Pause</h2>
    <ul>
      <li id="req-7">After three failed checks, a service MUST pause the flag.</li>
    </ul>
  </body>
</html>

The first template matches the rfc element and writes the page around the sections. Inside it, xsl:apply-templates processes each section, the second template writes a heading and a list for it, and the third turns each requirement into a list item. With expand-text="yes", {name} in the text is replaced by the value of the expression name, and {position()} by the section's position among the sections processed.

Writing documents again

sxml serialize parses a document and writes it again, for example indented, or by another output method:

$ echo '<section><name>Pause</name><t>Three failed checks.</t></section>' | sxml serialize --indent -
<section>
  <name>Pause</name>
  <t>Three failed checks.</t>
</section>

A file name of - means standard input, in every command.

Exit status

Every command exits with the same status codes, so scripts can tell what went wrong:

StatusMeaning
0Success
1The operation failed: a document is not well-formed or not valid, or a query or stylesheet raised an error
2Usage error: an unknown command or option, or a bad option value
3A file could not be read or written
4An internal error, such as running out of memory or exceeding a limit

A mistyped command or option is a usage error, and sxml suggests what you may have meant:

$ sxml chek draft.xml
sxml: unknown command 'chek'; did you mean 'check'?
Usage: sxml [--version] COMMAND [ARGUMENT...]
Try 'sxml --help'.

Summary

You now know the whole sxml command at a glance:

  • check for well-formedness and validate for schemas;
  • xpath for expressions, xquery for queries and transform for stylesheets;
  • serialize to write a document again.

sxml reads only the local files you name, never uses the network and writes a file only when an option asks it to. Now it's time to learn the languages properly, starting with XPath on the whole Vane draft.