Home > Software design >  How to flatten XML with xmlstarlet?
How to flatten XML with xmlstarlet?

Time:01-27

I have an XML file that I'd like to flatten.

input.xml:

<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item id="item1">
    <oldproperty oldname="mykey" oldvalue="keyname1"/>
    <oldproperty oldname="myval" oldvalue="value1"/>
  </item>
  <item id="item2">
    <oldproperty oldname="mykey" oldvalue="keyname2"/>
    <oldproperty oldname="myval" oldvalue="value2"/>
  </item>
  <item id="item3">
    <oldproperty oldname="mykey" oldvalue="keyname3"/>
    <oldproperty oldname="myval" oldvalue="value3"/>
  </item>
</items>

Desired output:

<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item id="item1" newkey="keyname1" newvalue="value1"/>
  <item id="item2" newkey="keyname2" newvalue="value2"/>
  <item id="item3" newkey="keyname3" newvalue="value3"/>
</items>

QUESTION: How can I do that with xmlstarlet?

CodePudding user response:

The desired output can be produced by xmlstarlet edit:

xmlstarlet edit \
  -s '*/*' -t attr -n newkey -v '' \
  -u '$prev' -x 'string(../oldproperty[@oldname="mykey"]/@oldvalue)' \
  -s '*/*' -t attr -n newvalue -v '' \
  -u '$prev' -x 'string(../oldproperty[@oldname="myval"]/@oldvalue)' \
  -d '*/*/oldproperty' \
file.xml
  • unlike -s (--subnode)'s -v (--value) the -x (--expr) clause of the -u (--update) option takes an XPath argument, hence the two-step approach
  • the $prev variable refers to the node(s) created by the most recent -s, -i, or -a option which all define or redefine it (see xmlstarlet.txt for examples of $prev)
  • */* may be replaced with items/item

or xmlstarlet select:

xmlstarlet select --xml-decl -E 'UTF-8' --indent -t \
  -e '{name(*)}' \
    -m '*/*' \
      -e '{name()}' \
        -a 'id'       -v '@id' -b \
        -a 'newkey'   -v '*[@oldname="mykey"]/@oldvalue' -b \
        -a 'newvalue' -v '*[@oldname="myval"]/@oldvalue' \
file.xml
  • -e (--elem) emits an element (here using an XSLT attribute value template)
  • -a (--attr) emits an attribute, -v (--value-of) takes an XPath argument
  • *[@oldname="…"] may be replaced with oldproperty[@oldname="…"]
  • */* may be replaced with items/item

(Assuming POSIX shell syntax.)

  • Related