Query XML with XPath
Goal: Select elements, attributes, and text from an XML file with a path expression such as //user[@role="dev"]/name/text(), instead of walking the tree by hand.
Prerequisites: The xpath.mq extension module. Copy xpath.mq into your module directory, place it anywhere and point at it with -L <dir>, or import it over HTTP with --allow-http-import and import "github.com/harehare/xpath.mq". Read the file with -I xml, so the query’s input is the parsed tree.
Query
The names of the users whose role is dev:
$ mq -I xml 'import "xpath" | xpath::xpath_query(., "//user[@role=\"dev\"]/name/text()")' users.xml
Input (users.xml)
<?xml version="1.0"?>
<users>
<user id="1" role="admin"><name>Alice</name><email>[email protected]</email></user>
<user id="2" role="dev"><name>Bob</name></user>
<user id="3" role="dev"><name>Carol</name><email>[email protected]</email></user>
</users>
Output
["Bob", "Carol"]
More paths
Each of these runs against the same users.xml:
| XPath | Result |
|---|---|
//user/@id | ["1", "2", "3"] |
//user[email]/name/text() | ["Alice", "Carol"] |
//user[contains(email, "carol")]/@id | ["3"] |
//user[2]/name/text() | ["Bob"] |
To get a single value instead of an array, use xpath_first, which returns None when there is no match:
$ mq -I xml 'import "xpath" | xpath::xpath_first(., "//user[@id=\"3\"]/name/text()")' users.xml
Carol
Notes
xpath_queryreturns an array. An element step (//user) returns the element dicts,@namereturns the attribute’s string, andtext()returns the element’s text. Pass the elements to thexmlmodule to keep working on them, for examplemap(fn(u): xml::xml_text(xml::xml_find(u, "name"));).- No match returns
[], which prints nothing in the default output format. Add-F jsonto see the[]. - This is an abbreviated XPath. It supports child, descendant (
//), self, parent,*wildcards,@attr,text(),name(), positional and attribute or child predicates, andand,or,not(...),contains(...),starts-with(...). It does not implement the full XPath function library, so fall back toxml_find_allfor anything else. - A malformed path raises an error rather than returning
[]. - For simple lookups by tag name, the built-in
xml_find_allneeds no extra module. See Read values from an XML file.