XML & XPath

Process XML with xmllint and xmlstarlet — XPath queries, validation, and in-place editing.

xmllint — Query and Format

Pretty-print XML — reformat for readability
xmllint --format response.xml
XPath query — extract a specific element value
xmllint --xpath '//hostname/text()' inventory.xml
XPath with attribute selection — find elements by attribute
xmllint --xpath '//device[@role="core"]/hostname/text()' inventory.xml
XPath returning multiple values — each on its own line
xmllint --xpath '//device/hostname/text()' inventory.xml 2>/dev/null | tr -s '\n'
Validate XML against a DTD — check structural correctness
xmllint --valid --dtdvalid schema.dtd config.xml --noout
Validate well-formedness only — no schema required
xmllint --noout config.xml && echo "Well-formed" || echo "Malformed"
Extract XML from an API response — curl + xmllint pipeline
curl -s https://api.example.com/data.xml | xmllint --format --xpath '//result' -

xmlstarlet — Select, Edit, Validate

Select element text — equivalent to XPath text() extraction
xmlstarlet sel -t -v '//device/hostname' inventory.xml
Select with template — format output with custom separators
xmlstarlet sel -t -m '//device' -v 'hostname' -o ',' -v 'ip' -n inventory.xml
Select attributes — extract attribute values from elements
xmlstarlet sel -t -v '//device/@role' inventory.xml
Count matching elements — useful for validation scripts
xmlstarlet sel -t -v 'count(//device)' inventory.xml
Edit XML — update an element value in-place
xmlstarlet ed -u '//device[@name="sw-core"]/vlan' -v '20' inventory.xml
Edit XML — add a new child element
xmlstarlet ed -s '//device[@name="sw-core"]' -t elem -n "location" -v "rack-a1" inventory.xml
Edit XML — delete an element
xmlstarlet ed -d '//device[@name="decommissioned"]' inventory.xml
In-place editing — modify the file directly (like sed -i)
xmlstarlet ed -L -u '//config/timeout' -v '60' config.xml
Validate against XSD schema
xmlstarlet val -e -s schema.xsd config.xml

Common XPath Expressions

Root element children — /root/element
xmlstarlet sel -t -v '/inventory/device/hostname' inventory.xml
Any depth search — //element finds at any nesting level
xmlstarlet sel -t -v '//hostname' inventory.xml
Predicate filtering — [condition] narrows the selection
# By position
xmlstarlet sel -t -v '//device[1]/hostname' inventory.xml

# By child element value
xmlstarlet sel -t -v '//device[vlan=10]/hostname' inventory.xml

# By attribute
xmlstarlet sel -t -v '//device[@enabled="true"]/hostname' inventory.xml
Boolean operators in predicates — and, or, not()
xmlstarlet sel -t -v '//device[@role="access" and vlan > 10]/hostname' inventory.xml
XPath functions — string-length, contains, starts-with
# Elements containing a substring
xmlstarlet sel -t -v '//device[contains(hostname, "core")]/ip' inventory.xml

# Elements starting with a prefix
xmlstarlet sel -t -v '//device[starts-with(hostname, "sw-")]/hostname' inventory.xml

Namespace Handling

Query with default namespace — must register it first
xmlstarlet sel -N ns="http://example.com/schema" \
  -t -v '//ns:device/ns:hostname' namespaced.xml
xmllint with namespace — use local-name() to ignore namespace prefix
xmllint --xpath '//*[local-name()="device"]/*[local-name()="hostname"]/text()' namespaced.xml
Strip namespaces entirely — simplify processing when namespace is noise
xmlstarlet ed -d '//@*[local-name()="schemaLocation"]' namespaced.xml \
  | sed 's/ xmlns[^"]*"[^"]*"//g' \
  | xmlstarlet sel -t -v '//device/hostname' -

XSLT Processing

Transform XML with an XSLT stylesheet — xsltproc
xsltproc transform.xsl input.xml > output.xml
Pass parameters to XSLT — inject values at transform time
xsltproc --stringparam environment "production" transform.xsl input.xml
Quick XSLT to extract CSV from XML — inline stylesheet
xsltproc - inventory.xml <<'XSLT'
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="/">
    <xsl:text>hostname,ip,role&#10;</xsl:text>
    <xsl:for-each select="//device">
      <xsl:value-of select="hostname"/>,<xsl:value-of select="ip"/>,<xsl:value-of select="@role"/>
      <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>
XSLT

API Response Processing

Cisco ISE API — extract endpoint data from XML response
curl -sk -u admin:password https://ise-01:9060/ers/config/endpoint \
  -H "Accept: application/xml" \
  | xmllint --format --xpath '//resources/resource/name/text()' -
Parse SOAP response — extract the payload from the envelope
xmlstarlet sel -N soap="http://schemas.xmlsoap.org/soap/envelope/" \
  -t -c '//soap:Body/*' response.xml | xmllint --format -
Extract error messages from XML API errors
xmlstarlet sel -t -v '//error/message' -n error_response.xml