Adding a subnode to an XML file

Let's pretend you had this XML file. (It's an XML file that's similar to data from Open Street Map)

<?xml version="1.0" encoding="UTF-8"?> <osm version="0.6" generator="CGImap 0.0.2"> <bounds minlat="53.2875000" minlon="-6.2274000" maxlat="53.3046000" maxlon="-6.1941000"/> <node id="389719" lat="53.3067754" lon="-6.2129946" user="Jazzmax" uid="110583" visible="true" version="2" changeset="21774" timestamp="2009-04-01T03:08:52Z"/> <node id="19223905" lat="53.2954887" lon="-6.2035759" user="mackerski" uid="6367" visible="true" version="1" changeset="397382" timestamp="2007-09-08T23:45:14Z"> <tag k="created_by" v="JOSM"/> </node> <node id="19223906" lat="53.2941060" lon="-6.2025008" user="Nick Burrett" uid="166593" visible="true" version="5" changeset="6041772" timestamp="2010-10-14T18:29:52Z"> <tag k="highway" v="traffic_signals"/> </node> </osm>

You can use xmlstarlet (Ubuntu users: click here to install it) to add a new subnode to some nodes of this xml file on the command line.

This command will add a new subnode (<newsubnode>) to ever <node> tag in this document. It takes the input form input.xml and writes the output to output.xml:

xmlstarlet ed --subnode "/osm/node" --type elem -n newsubnode -v "" input.xml > output.xml

It will produce an XML file like this:

<?xml version="1.0" encoding="UTF-8"?> <osm version="0.6" generator="CGImap 0.0.2"> <bounds minlat="53.2875000" minlon="-6.2274000" maxlat="53.3046000" maxlon="-6.1941000"/> <node id="389719" lat="53.3067754" lon="-6.2129946" user="Jazzmax" uid="110583" visible="true" version="2" changeset="21774" timestamp="2009-04-01T03:08:52Z"> <newsubnode></newsubnode> </node> <node id="19223905" lat="53.2954887" lon="-6.2035759" user="mackerski" uid="6367" visible="true" version="1" changeset="397382" timestamp="2007-09-08T23:45:14Z"> <tag k="created_by" v="JOSM"/> <newsubnode></newsubnode> </node> <node id="19223906" lat="53.2941060" lon="-6.2025008" user="Nick Burrett" uid="166593" visible="true" version="5" changeset="6041772" timestamp="2010-10-14T18:29:52Z"> <tag k="highway" v="traffic_signals"/> <newsubnode></newsubnode> </node> </osm>

Adding text to the new node

Put something in the -v "" argument to put text in the new subnode.

xmlstarlet ed --subnode "/osm/node" --type elem -n newsubnode -v "TEXT GOES HERE" input.xml > output.xml

Produces this output.xml:

<?xml version="1.0" encoding="UTF-8"?> <osm version="0.6" generator="CGImap 0.0.2"> <bounds minlat="53.2875000" minlon="-6.2274000" maxlat="53.3046000" maxlon="-6.1941000"/> <node id="389719" lat="53.3067754" lon="-6.2129946" user="Jazzmax" uid="110583" visible="true" version="2" changeset="21774" timestamp="2009-04-01T03:08:52Z"> <newsubnode>TEXT GOES HERE</newsubnode> </node> <node id="19223905" lat="53.2954887" lon="-6.2035759" user="mackerski" uid="6367" visible="true" version="1" changeset="397382" timestamp="2007-09-08T23:45:14Z"> <tag k="created_by" v="JOSM"/> <newsubnode>TEXT GOES HERE</newsubnode> </node> <node id="19223906" lat="53.2941060" lon="-6.2025008" user="Nick Burrett" uid="166593" visible="true" version="5" changeset="6041772" timestamp="2010-10-14T18:29:52Z"> <tag k="highway" v="traffic_signals"/> <newsubnode>TEXT GOES HERE</newsubnode> </node> </osm>

Only adding a new subnode to certain nodes

The above examples add a <newsubnode> to every <node> in the document. If you change the XPath expression at the start, you can add this new subnode to only certain nodes. e.g. Here's how to add a subnode to every node that doesn't have a <tag k="highway" ...:

xmlstarlet ed --subnode "/osm/node[not(tag[@k='highway'])]" --type elem -n newsubnode -v "" input.xml > output.xml

That produces this output.xml:

<?xml version="1.0" encoding="UTF-8"?> <osm version="0.6" generator="CGImap 0.0.2"> <bounds minlat="53.2875000" minlon="-6.2274000" maxlat="53.3046000" maxlon="-6.1941000"/> <node id="389719" lat="53.3067754" lon="-6.2129946" user="Jazzmax" uid="110583" visible="true" version="2" changeset="21774" timestamp="2009-04-01T03:08:52Z"> <newsubnode></newsubnode> </node> <node id="19223905" lat="53.2954887" lon="-6.2035759" user="mackerski" uid="6367" visible="true" version="1" changeset="397382" timestamp="2007-09-08T23:45:14Z"> <tag k="created_by" v="JOSM"/> <newsubnode></newsubnode> </node> <node id="19223906" lat="53.2941060" lon="-6.2025008" user="Nick Burrett" uid="166593" visible="true" version="5" changeset="6041772" timestamp="2010-10-14T18:29:52Z"> <tag k="highway" v="traffic_signals"/> </node> </osm>

Adding attributes to the new node

When creating the new subnode, you can specify the text, however I can't find a way to add new attributes to the new node. I have solved this in the past by passing the output of the xmlstartlet to another xmlstarlet that will add the attributes. (see How to add an attribute to an XML node on the command line).

xmlstarlet ed --subnode "/osm/node" --type elem -n newsubnode -v "" input.xml | xmlstarlet ed --insert //newsubnode --type attr -n attrname -v attrvalue > output.xml

output.xml

<?xml version="1.0" encoding="UTF-8"?> <osm version="0.6" generator="CGImap 0.0.2"> <bounds minlat="53.2875000" minlon="-6.2274000" maxlat="53.3046000" maxlon="-6.1941000"/> <node id="389719" lat="53.3067754" lon="-6.2129946" user="Jazzmax" uid="110583" visible="true" version="2" changeset="21774" timestamp="2009-04-01T03:08:52Z"> <newsubnode attrname="attrvalue"/> </node> <node id="19223905" lat="53.2954887" lon="-6.2035759" user="mackerski" uid="6367" visible="true" version="1" changeset="397382" timestamp="2007-09-08T23:45:14Z"> <tag k="created_by" v="JOSM"/> <newsubnode attrname="attrvalue"/> </node> <node id="19223906" lat="53.2941060" lon="-6.2025008" user="Nick Burrett" uid="166593" visible="true" version="5" changeset="6041772" timestamp="2010-10-14T18:29:52Z"> <tag k="highway" v="traffic_signals"/> <newsubnode attrname="attrvalue"/> </node> </osm>

This is a bit ineffeciant since the xml file is parsed and written twice.

Related: Deleting an XML node on the command line, How to add an attribute to an XML node on the command line.

This entry is tagged: