Reputation: 198
I want to insert additional elements into some variants of XML input. This script tries to demonstrate the input, and my attempt to insert code into the existing XML. As it can be seen, modifying a.xml
gives the expected output. But in b.xml
and c.xml
, the result is bogus. In b.xml
, the existing <c/>
is changed and another block of <b/>
is created. In c.xml
, the result is that d=""
is assigned twice.
There should be only a single <a><b>
, with several <c>
.
Any idea how to achieve that?
#!/bin/bash
# insert <a><b><c d="2"/>
set -e
td=`mktemp --directory --tmpdir=/dev/shm XXX`
trap "rm -rf '${td}'" EXIT
xmlstarlet --version
pushd "${td}"
cat > a.xml <<_EOX_
<a>
</a>
_EOX_
cat > b.xml <<_EOX_
<a>
<b>
<c/>
</b>
</a>
_EOX_
cat > c.xml <<_EOX_
<a>
<b>
<c d="1"/>
</b>
</a>
_EOX_
for i in *.xml
do
echo "$i"
cat "$i" | \
xmlstarlet ed -O \
-s 'a' -t elem -n b \
-s 'a/b' -t elem -n c \
-i 'a/b/c' -t attr -n d -v '2' |
xmlstarlet fo -o || echo "$?"
done
This produces the following output:
1.6.1
compiled against libxml2 2.9.7, linked with 20907
compiled against libxslt 1.1.32, linked with 10132
/dev/shm/tpX ~/work
a.xml
<a>
<b>
<c d="2"/>
</b>
</a>
37
b.xml
<a>
<b>
<c d="2"/>
<c d="2"/>
</b>
<b>
<c d="2"/>
</b>
</a>
80
c.xml
-:3.19: Attribute d redefined
<c d="1" d="2"/>
^
2
Upvotes: 1
Views: 733
Reputation: 26471
The problem is that you need to tell which node you want to change.
Add child b
only if b
does not exist in a
:
-s "/a[not(b)]" -t elem -n "b"
Add a new child c
in a/b
:
-s "/a/b[not(c)]" -t elem -n "c"
Add attribute d
to the newly added node c
which is in the last place:
-s "/a/b/c[last()]" -t attr -n "d" -v "2"
So the complete commands reads now:
xmlstarlet ed -O \
-s "/a[not(b)]" -t elem -n "b" \
-s "/a/b" -t elem -n "c" \
-s "/a/b/c[last()]" -t attr -n "d" -v "2"
Here we made use of the not()
function to indicate that we just want to select the nodes that do not contain that particular item.
Useful links:
Upvotes: 1