How do I insert an element directly after another element with XMLStarlet? - xmlstarlet

With this example XML:
<rootnode>
<element-a />
<element-b />
<element-d />
<element-e />
</rootnode>
How do I insert element <element-c/> directly after the element <element-b/> using XMLStarlet?

xml ed -i (or --insert) will put the it before the node, xml ed -a (or --append) will put it after, so you can use either one of:
xml ed -i /rootnode/element-d -t elem -n element-c -v "" file.xml
xml ed -a /rootnode/element-b -t elem -n element-c -v "" file.xml

Related

Extracting and modifying XML with deep structure from Linux command line

I would like to select and change a value in an XML file. I'm trying to use xmlstarlet for this.
I have this file
<?xml version='1.0' encoding='UTF-8'?>
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd">
<House>
<Id>
<Number>1</Number>
</Id>
</House>
<Car>
<Id>
<Number>2</Number>
</Id>
</Car>
</DeviceDescription>
My problem is the xmlns= field which xmlstarlet is picky about. Without this field I can use
xmlstarlet sel -t -v '/Description/House/Id/Number' /tmp/x.xml
I found that I can use a default namespace like this, but that returns both Id's
xmlstarlet sel -t -m "//_:Id" -v '_:Number' /tmp/x.xml
How do I specify a full path?
To only match the House id, add it to the -m argument:
xml sel -t -m '//_:House/_:Id' -v '_:Number'
If you want to use the namespace, specify it with -N, e.g.:
xml sel -N ns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" \
-t -v 'ns:DeviceDescription/ns:House/ns:Id/ns:Number'
So to update the value:
xml ed -N ns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" \
-u 'ns:DeviceDescription/ns:House/ns:Id/ns:Number' -v 3
Output:
<?xml version="1.0" encoding="UTF-8"?>
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd">
<House>
<Id>
<Number>3</Number>
</Id>
</House>
<Car>
<Id>
<Number>2</Number>
</Id>
</Car>
</DeviceDescription>

XMLStarlet - Adding RSS feed items

Been using an example I found here (https://stackoverflow.com/a/14397390/3168446) and I just noticed it's not adding items correctly. The following example is actually adding items outside of the channel-tag. Anyone know the correct way to make this work?
feed.xml:
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>My RSS Feed</title>
<description>This is my RSS Feed</description>
</channel>
</rss>
shell script:
#!/bin/sh
TITLE="My RSS entry"
LINK="http://example.com/entry4711"
DATE="`date`"
DESC="Good news"
GUID="http://example.com/entry4711"
xmlstarlet ed -L -a "//channel" -t elem -n item -v "" \
-s "//item[1]" -t elem -n title -v "$TITLE" \
-s "//item[1]" -t elem -n link -v "$LINK" \
-s "//item[1]" -t elem -n pubDate -v "$DATE" \
-s "//item[1]" -t elem -n description -v "$DESC" \
-s "//item[1]" -t elem -n guid -v "$GUID" \
-d "//item[position()>10]" feed.xml ;
windows command line example:
xml ed -L -a "//channel" -t elem -n item -v "" -s "//item[1]" -t elem -n title -v "My RSS entry" -s "//item[1]" -t elem -n link -v "http://example.com/entry4711" -s "//item[1]" -t elem -n pubDate -v "Sat, 26 Jul 2014 01:14:30 +0200" -s "//item[1]" -t elem -n description -v "Good news" -s "//item[1]" -t elem -n guid -v "http://example.com/entry4711" -d "//item[position()>10]" feed.xml
output feed.xml:
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>My RSS Feed</title>
<description>This is my RSS Feed</description>
</channel>
<item>
<title>My RSS entry</title>
<link>http://example.com/entry4711</link>
<pubDate>Sat, 26 Jul 2014 01:14:30 +0200</pubDate>
<description>Good news</description>
<guid>http://example.com/entry4711</guid>
</item>
</rss>
As you see in the output the item is added outside of the channel-tag so the feed won't validate.
Wasn't actually that hard after a good night sleep.
The reason every item is added outside of the channel tag is because I used;
xml ed -L -a "//channel"
Solution is to use a tag in the feed-template that is not used within the items you're adding. I'm using the generator-tag.
Example feed.xml with added generator-tag:
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>My RSS Feed</title>
<description>This is my RSS Feed</description>
<link>http://example.com/entry4711</link>
<generator>your-rss-generator</generator>
</channel>
</rss>
Shell script where I replace //channel with //generator:
#!/bin/sh
TITLE="My RSS entry"
LINK="http://example.com/entry4711"
DATE="`date`"
DESC="Good news"
GUID="http://example.com/entry4711"
xmlstarlet ed -L -a "//generator" -t elem -n item -v "" \
-s "//item[1]" -t elem -n title -v "$TITLE" \
-s "//item[1]" -t elem -n link -v "$LINK" \
-s "//item[1]" -t elem -n pubDate -v "$DATE" \
-s "//item[1]" -t elem -n description -v "$DESC" \
-s "//item[1]" -t elem -n guid -v "$GUID" \
-d "//item[position()>10]" feed.xml ;
Windows command line where I replace //channel with //generator:
xml ed -L -a "//generator" -t elem -n item -v "" -s "//item[1]" -t elem -n title -v "My RSS entry" -s "//item[1]" -t elem -n link -v "http://example.com/entry4711" -s "//item[1]" -t elem -n pubDate -v "Sat, 26 Jul 2014 01:14:30 +0200" -s "//item[1]" -t elem -n description -v "Good news" -s "//item[1]" -t elem -n guid -v "http://example.com/entry4711" -d "//item[position()>10]" feed.xml
This will make every new item go within the channel-tag.

Extracting multiple value using xmlstarlet

How can I extract "failed" from all element and add them up?
<gateway>
<smscs>
<count>3</count>
<smsc>
<id>a</id>
<received><sms>0</sms><dlr>0</dlr></received>
<sent><sms>10537</sms><dlr>0</dlr></sent>
<failed>13</failed>
<queued>6272</queued>
</smsc>
<smsc>
<id>b</id>
<received><sms>0</sms><dlr>0</dlr></received>
<sent><sms>10530</sms><dlr>0</dlr></sent>
<failed>10</failed>
<queued>6284</queued>
</smsc>
<smsc>
<id>c</id>
<received><sms>0</sms><dlr>0</dlr></received>
<sent><sms>10679</sms><dlr>0</dlr></sent>
<failed>11</failed>
<queued>6291</queued>
</smsc>
</smscs>
</gateway>
I simply used
xmlstarlet sel -t -v "sum(/gateway/smscs/smsc/failed)" -n input.xml
which returned
34
The idea is to use the sum() function which takes a node-set and returns the sum of all the elements' string-values converted to numbers.
Solved with
xmlstarlet sel -t -m "gateway/smscs/smsc/failed" -v "." -n | awk '{s+=$1} END {print s}'

xmlstarlet to insert tags

I am using the command:
xmlstarlet ed --omit-decl --subnode "/boinc" --type elem -n app -v "" project_00.xml > project_01.xml
However, I would like to insert two more tags into that one:
<app>
<name>name</name>
<nikname>nikname</nikname>
</app>
In my project_00.xml, I already have others tag app and it is causing conflicts.
The problem with this command:
xmlstarlet ed --subnode "/boinc" --type elem -n app -v "" project_00.xml| xmlstarlet ed --subnode //app --type elem -n name -v "newApp"| xmlstarlet ed --subnode //app --type elem -n user_friendly_name -v "New.App" > project_01.xml
is that it created this.:
<app name="wilson">
<name>wilson</name>
<user_friendly_name>Mr.Wilson</user_friendly_name>
<name>newApp</name>
<user_friendly_name>New.App</user_friendly_name>
</app>
<app>
<name>newApp</name>
<user_friendly_name>New.App</user_friendly_name>
</app>
Does know the exactly command?
I tried this command but it replicated the to all app tags
xmlstarlet ed -s "/boinc" -t elem -n app -v "" -s "/boinc/app" -t elem -n name -v "name" -s "/boinc/app" -t elem -n user_friendly_name -v "New.App" project_00.xml > project_01.xml
Basically, you need an XPath expression to match the node you just inserted; since --subnode always puts new children in last place you can use /boinc/app[last()]:
xmlstarlet ed \
--subnode /boinc --type elem -n app -v '' \
--subnode '/boinc/app[last()]' --type elem -n name -v newApp \
--subnode '/boinc/app[last()]' --type elem -n user_friendly_name -v New.App \
project_00.xml > project_01.xml

Extract values from XML using xmlstarlet

How can I extract using xmlstarlet the local port from this xml example:
<?xml version="1.0"?>
<opmn xmlns="http://www.oracle.com/ias-instance">
<notification-server>
<port local="6101" remote="6200" request="6003"/>
<log-file path="$ORACLE_HOME\opmn\logs\ons.log" level="4" rotation-size="1500000"/>
<ssl enabled="true" wallet-file="$ORACLE_HOME\opmn\conf\ssl.wlt\default"/>
</notification-server>
</opmn>
xml sel -N ias=http://www.oracle.com/ias-instance -t -v //ias:port/#local example.xml
Or more precise
xml sel -N ias=http://www.oracle.com/ias-instance -t -v /ias:opmn/ias:notification-server/ias:port/#local example.xml

Resources