Xmllint and XmlStarlet

Published: Thu 17 August 2017

In Blog.

Daniel Miessler has a plucky article on his Christmas present circa 2014, that is: some cool command-line fu courtesy of xmllint. Xmllint is nifty utility that allows one to easily validate XML documents, as well as some useful other things, like extract bits of XML documents.

I've talked about dpic previously—it's a nifty tool that allows one to knock up small diagrams in lightning speed. Since svg is the output, and svg is really just XML, then tools like xmllint can come in useful when doing post-processing on the output of dpic.

Although xmllint is useful for extracting information, its primary purpose is to do validation on XML documents, and it doesn't have a facility for rewriting XML. One could do tricks with a tool like 2xml (also previously blogged about), and I'd imagine that that would be a good idea for minor changes, but for trickier post-processing we'd need something a little more “XML-aware”.

Enter xmlstarlet which is set up to do command-line editing of XML; precisely what we need to do a little post-dpic tweaking. Dpic is fairly limited in the “fanciness” of its output (I suspect, by design), but if we want to add some sparkle to dpic diagrams, we have options if the output is in a mealleable form.

scale = 25.4
box "Foxy" rad 3

This produces some fairly mundane output:

Mundane dpic output

Let's give it a drop-shadow:

Step 1: Duplicate the rectangle

cat ./output/images/mundane-box.svg | sed '/rect/p'
Two Rectangles

This doesn't look a whole lot different since right now, the rectangles are on top of one another.

Step 2: Offset the first rectangle a little below and to the right

cat ./output/images/mundane-box.svg | sed '/rect/p' \
| xmlstarlet ed -u '(//_:rect)[1]/@x' -x '(//_:rect)[1]/@x + 2' \
| xmlstarlet ed -u '(//_:rect)[1]/@y' -x '(//_:rect)[1]/@y + 2'

The added xmlstarlet commands take as parameters XPath expressions that refer to the first rectangle. The -u parameter means “update” and the -x parameter indicates that we want to execute another XPath expression in order to determine what value to update to. XPath is quite powerful, and arithmetic operations on existing floating point values are possible.

Offset Rectangles

Step 3: Get the second rectangle to “cover” the first

cat ./output/images/mundane-box.svg | sed '/rect/p' \
| xmlstarlet ed -u '(//_:rect)[1]/@x' -x '(//_:rect)[1]/@x + 2' \
| xmlstarlet ed -u '(//_:rect)[1]/@y' -x '(//_:rect)[1]/@y + 2' \
| xmlstarlet ed -i '(//_:rect)[2]' -t attr -n fill -v white

We do this by adding an xmlstarlet pipe entry to insert a fill attribute into the second rectangle node. Since the picture is “painted” in the order in which it’s parsed, the second rectangle’s fill will blot out that part of the first rectangle which we don't want to see.

Offset Rectangles Covered

Step 4: Get the first rectangle to be a “shadow”

cat ./output/images/mundane-box.svg | sed '/rect/p' \
| xmlstarlet ed -u '(//_:rect)[1]/@x' -x '(//_:rect)[1]/@x + 2' \
| xmlstarlet ed -u '(//_:rect)[1]/@y' -x '(//_:rect)[1]/@y + 2' \
| xmlstarlet ed -i '(//_:rect)[2]' -t attr -n fill -v white \
| xmlstarlet ed -i '(//_:rect)[1]' -t attr -n stroke -v none \
| xmlstarlet ed -i '(//_:rect)[1]' -t attr -n fill -v lightgray

To turn the rectangle into a shadow, we remove the stroke (stroke="none") and insert another attribute, (fill="lightgray").

Offset Rectangles Shadowed

Following is a diff of the original image, as produced by dpic, and the final version after some xmlstarlet piping (and a bit of sed in the beginning to duplicate the rectangle):

< <rect x="0.5" y="0.5" ... width="67.5" height="45" />
> <rect x="2.5" y="2.5" ... width="67.5" height="45" stroke="none" fill="lightgray"/>
> <rect x="0.5" y="0.5" ... width="67.5" height="45" fill="white"/>

xmlstarlet is a powerful tool for quickly getting an existing piece of XML into the right form. My only complaint is that it can be a little verbose; obviously one can address this to a certain degree by using aliases, e.g.:

alias xe="xmlstarlet ed"

Alternatively, of course, we could draw rounded rectangles with shadows quite easily using a tool like Inkscape. However, that would necessitate use of a certain ubiquitous rodent, and prevent us from exercising our command-line fu “muscle”.

Comments !