Home > Blockchain >  How to add new elements in a XML File using Xmlstarlet
How to add new elements in a XML File using Xmlstarlet

Time:12-24

I want to edit an XML file and add 2 more new elements to it. I am able to add one new element but the second element is not getting added properly.

context.xml file -

<?xml version="1.0" encoding="UTF-8"?>
<Context>
</Context>

Expected output -

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <Resource name="jdbc/DBRead" auth="Container"
        type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://127.0.0.1:5432/DBRead"
        username="abc" password="xyz" maxTotal="20" maxIdle="10"
        maxWaitMillis="-1"/>
  <Resource name="jdbc/DBWrite" auth="Container"
        type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://127.0.0.1:5432/DBWrite"
        username="abc" password="xyz" maxTotal="20" maxIdle="10"
        maxWaitMillis="-1"/>
</Context>

I have written the following script to add the two new Resource tags but only one Resource tag is getting added properly -

changeXml.sh -

#!/bin/sh
xmlstarlet ed -L -s '//Context' -t elem -n 'Resource' -s '//Context/Resource' -t attr -n 'name' -v 'jdbc/DBRead' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'auth' -v 'Container' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'type' -v 'javax.sql.DataSource' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'driverClassName' -v 'org.postgresql.Driver' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'url' -v 'jdbc:postgresql://127.0.0.1:5432/DBRead' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'username' -v 'abc' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'password' -v 'xyz' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'maxTotal' -v '20' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'maxIdle' -v '10' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'maxWaitMillis' -v '-1' /home/context.xml;
xmlstarlet ed -L -s '//Context' -t elem -n 'Resource' -s '//Context/Resource' -t attr -n 'name' -v 'jdbc/DBWrite' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'auth' -v 'Container' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'type' -v 'javax.sql.DataSource' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'driverClassName' -v 'org.postgresql.Driver' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'url' -v 'jdbc:postgresql://127.0.0.1:5432/DBWrite' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'username' -v 'abc' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'password' -v 'xyz' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'maxTotal' -v '20' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'maxIdle' -v '10' /home/context.xml;
xmlstarlet ed -L -s '//Context/Resource' -t attr -n 'maxWaitMillis' -v '-1' /home/context.xml;

The output that I am getting with this script is -

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <Resource name="jdbc/DBRead" auth="Container"
            type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
            url="jdbc:postgresql://127.0.0.1:5432/DBRead"
            username="abc" password="xyz" maxTotal="20" maxIdle="10"
            maxWaitMillis="-1" name="jdbc/DBRead"/>
  <Resource name="jdbc/DBRead"/>
</Context>

It is giving error - Attribute name redefined and producing the above output. Please suggest how to get the expected output. I have also tried renaming the 2nd Resource element to ResourceTMP but that also gives me the same output. Thanks in advance.

CodePudding user response:

You can add set -x as second line to your script, this will make every line echo before it is executed, finding the cause of the error easier:

The output will look like this:

$ ./changeXml.sh
  xmlstarlet ed -L -s //Context -t elem -n Resource -s //Context/Resource -t attr -n name -v jdbc/DBRead context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n auth -v Container context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n type -v javax.sql.DataSource context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n driverClassName -v org.postgresql.Driver context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n url -v jdbc:postgresql://127.0.0.1:5432/DBRead context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n username -v abc context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n password -v xyz context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n maxTotal -v 20 context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n maxIdle -v 10 context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n maxWaitMillis -v -1 context.xml -b
failed to load external entity "-b"
  xmlstarlet ed -L -s //Context -t elem -n Resource -s //Context/Resource -t attr -n name -v jdbc/DBWrite context.xml
  xmlstarlet ed -L -s //Context/Resource -t attr -n auth -v Container context.xml
context.xml:3.101: Attribute name redefined
password="xyz" maxTotal="20" maxIdle="10" maxWaitMillis="-1" name="jdbc/DBWrite"
                                                                               ^
  xmlstarlet ed -L -s //Context/Resource -t attr -n type -v javax.sql.DataSource context.xml
context.xml:3.101: Attribute name redefined
password="xyz" maxTotal="20" maxIdle="10" maxWaitMillis="-1" name="jdbc/DBWrite"
...

The error is on the line containing:

xmlstarlet ed -L -s //Context/Resource -t attr -n maxWaitMillis -v -1 context.xml -b

and

xmlstarlet ed --help

does NOT show a -b option

I think you should fix this bug first, before fixing the error in line:

xmlstarlet ed -L -s //Context/Resource -t attr -n auth -v Container context.xml

This is probably cause by the previous line in your script which also seems to add an attribute named auth.

EDIT:

  1. I removed the ; at the end of each line
  2. I removed the use if '//' when adding 1 element, or 1 attribute. you can use index (like [1]) for accessing the correct node.

The script look (after these changes as):

#!/bin/sh
#set -x
xmlstarlet ed -L -s '/Context' -t elem -n 'Resource' -s '/Context/Resource[1]' -t attr -n 'name' -v 'jdbc/DBRead' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'auth' -v 'Container' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'type' -v 'javax.sql.DataSource' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'driverClassName' -v 'org.postgresql.Driver' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'url' -v 'jdbc:postgresql://127.0.0.1:5432/DBRead' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'username' -v 'abc' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'password' -v 'xyz' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'maxTotal' -v '20' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'maxIdle' -v '10' context.xml
xmlstarlet ed -L -s '/Context/Resource[1]' -t attr -n 'maxWaitMillis' -v '-1' context.xml
xmlstarlet ed -L -s '/Context' -t elem -n 'Resource' -s '/Context/Resource[2]' -t attr -n 'name' -v 'jdbc/DBWrite' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'auth' -v 'Container' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'type' -v 'javax.sql.DataSource' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'driverClassName' -v 'org.postgresql.Driver' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'url' -v 'jdbc:postgresql://127.0.0.1:5432/DBWrite' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'username' -v 'abc' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'password' -v 'xyz' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'maxTotal' -v '20' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'maxIdle' -v '10' context.xml
xmlstarlet ed -L -s '/Context/Resource[2]' -t attr -n 'maxWaitMillis' -v '-1' context.xml
xmlstarlet fo context.xml
  • I commented the second line (so I no longer see every line executed
  • I added xmlstarlet fo context.xml to immediate check the contents after all is executed.

finally the XML looks like:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <Resource name="jdbc/DBRead" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://127.0.0.1:5432/DBRead" username="abc" password="xyz" maxTotal="20" maxIdle="10" maxWaitMillis="-1"/>
  <Resource name="jdbc/DBWrite" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://127.0.0.1:5432/DBWrite" username="abc" password="xyz" maxTotal="20" maxIdle="10" maxWaitMillis="-1"/>
</Context>

CodePudding user response:

xmlstarlet edit supports multiple editing actions. Following should generate the desired output in a single invocation:

xmlstarlet edit -L \
  -s '*' -t 'elem' -n 'Resource' \
  --var r1 '$prev' \
  -s '*' -t 'elem' -n 'Resource' \
  --var r2 '$prev' \
  -s '$r1' -t 'attr' -n 'name' -v 'jdbc/DBRead' \
  -s '$r1' -t 'attr' -n 'auth' -v 'Container' \
  -s '$r1' -t 'attr' -n 'type' -v 'javax.sql.DataSource' \
  …
  -s '$r1' -t 'attr' -n 'maxWaitMillis' -v '-1' \
  -u '$r2' -x '$r1/@*' \
  -u '$r2/@name' -v 'jdbc/DBWrite' \
  -u '$r2/@url' -x 'concat(substring-before(.,"/DBRead"),"/DBWrite")' \
  -i '*/*' -t 'text' -n 'ignored' -v '  ' \
  -a '*/*' -t 'text' -n 'ignored' -v '' \
  -u '$prev' -x 'substring('"$(printf '"\nA"')"',1,1)' \
file.xml

where:

  • --var defines a named variable, and 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 --var and $prev)
  • is where the omitted -s options belong
  • the 2nd Resource element is created as a copy of the 1st, then edited to make the two DBWrite nodes right
  • the last 3 actions handle indentation
  • Related