How to append a newline after every match using xmlint --xpath

shell, xml, xmllint, xpath

Solution

Hello from the year 2020!

As of v2.9.9 of libxml, this behavior has been fixed in `xmllint` itself.

echo \
'<textarea name="command" class="setting-input fixed-width"
 rows="9">1</textarea>
<textarea name="command" class="setting-input fixed-width"
 rows="5">2</textarea>' \
  | xmllint --xpath '//textarea[@name="command"]/text()' --html -

# result:
# 1
# 2

However, if you're using anything older than that, and don't want to build libxml from source just to get the fixed `xmllint`, you'll need one of the other workarounds here. As of this writing, the latest CentOS 8, for example, is still using a version of libxml (2.9.7) that behaves the way the OP describes.

As I gather from this SO answer, it's theoretically possible to feed a command into the `--shell` option of older (<2.9.9) versions of `xmllint`, and this will produce each node on a separate line. However, you end up having to post-process it with `sed` or `grep` to remove the visual detritus of shell mode's (human-oriented) output. It's not ideal.

XMLStarlet, if available, offers another solution, but you do need to use `xmlstarlet fo` to format your HTML fragment into valid XML before using `xmlstarlet sel` to extract nodes:

echo \
'<textarea name="command" class="setting-input fixed-width"
 rows="9">1</textarea>
<textarea name="command" class="setting-input fixed-width"
 rows="5">2</textarea>' \
  | xmlstarlet fo -H -R \
  | xmlstarlet sel -T -t -v '//textarea[@name="command"]' -n

If the `Attempt to load network entity` message from the second `xmlstarlet` invocation annoys you, just add `2>/dev/null` at the very end to suppress it (at the risk of suppressing other messages printed to standard error).

The XMLStarlet options explained (see also the user's guide):

- `fo -H -R` — format the output, expecting HTML input, and recovering as much bad input as possible

- this will add an `<html>` root node, making the fragment in the OP's example valid XML

- `sel -T -t -v //xpath -n` — select nodes based on XPath `//xpath`

- output plain text (`-T`) instead of XML

- using the given template (`-t`) that returns the value (`-v`) of the node rather than the node itself (allowing you to forgo using `text()` in the XPath expression)

- finally, add a newline (`-n`)

Edit(s): Removed half-implemented `xmllint --shell` solution because it was just bad. Added an XMLStarlet example that actually works with the OP's data.

Problem

I have the following HTML code: ``` <textarea name="command" class="setting-input fixed-width" rows="9">1</textarea><textarea name="command" class="setting-input fixed-width" rows="5">2</textarea> ``` I would like to parse it to receive such output: ``` 1 2 ``` Currently I am using: ``` xmllint --xpath '//textarea[@name="command"]/text()' --html ``` but it does not append a newline after each match.

Original source

Related problems