How to insert a new element under another with xmlstarlet?

跟風遠走 提交于 2019-11-27 19:22:31
npostavs

Use -s (or --subnode) instead of -i. Regarding the bonus, you can't insert an element with an attribute directly but since every edit operation is performed in sequence, to insert an element and then add an attribute:

> xml ed -s /config -t elem -n sub -v "" -i /config/sub -t attr -n class -v com.foo test.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<sub class="com.foo"></sub></config>

I had a similar problem: I had a Tomcat configuration file (server.xml), and had to insert a <Resource> tag with pre-defined attributes into the <GlobalNamingResources> section.

Here is how it looked before:

<GlobalNamingResources>
    <!-- Editable user database that can also be used
         by UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase"
              auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>

Here is what I wanted to achieve:

<GlobalNamingResources>
    <!-- Editable user database that can also be used
         by UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase"
              auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
    <Resource name="jdbc/templateassets"
              auth="Container"
              type="javax.sql.DataSource"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://DBHOST:DBPORT/DBNAME?createDatabaseIfNotExist=false&amp;useUnicode=true&amp;characterEncoding=utf-8"
              username="DBUSER"
              password="DBPASS"
              maxActive="150"
              maxIdle="10"
              initialSize="10"
              validationQuery="SELECT 1"
              testOnBorrow="true" />
</GlobalNamingResources>

Here is how I did it (snippet from a shell script):

if [ -n "$(xmlstarlet sel -T -t -v "/Server/GlobalNamingResources/Resource[@name='jdbc/templateassets']/@name" server.xml)" ]; then
  echo "Resource jdbc/templateassets already defined in server.xml"
else
  echo "Adding resource jdbc/templateassets to <GlobalNamingResources> in server.xml"
  xmlstarlet ed -P -S -L -s /Server/GlobalNamingResources -t elem -n ResourceTMP -v "" \
    -i //ResourceTMP -t attr -n "name" -v "jdbc/templateassets" \
    -i //ResourceTMP -t attr -n "auth" -v "Container" \
    -i //ResourceTMP -t attr -n "type" -v "javax.sql.DataSource" \
    -i //ResourceTMP -t attr -n "driverClassName" -v "com.mysql.jdbc.Driver" \
    -i //ResourceTMP -t attr -n "url" -v "jdbc:mysql://DBHOST:DBPORT/DBNAME?createDatabaseIfNotExist=false&useUnicode=true&characterEncoding=utf-8" \
    -i //ResourceTMP -t attr -n "username" -v "DBUSER" \
    -i //ResourceTMP -t attr -n "password" -v "DBPASS" \
    -i //ResourceTMP -t attr -n "maxActive" -v "150" \
    -i //ResourceTMP -t attr -n "maxIdle" -v "10" \
    -i //ResourceTMP -t attr -n "initialSize" -v "10" \
    -i //ResourceTMP -t attr -n "validationQuery" -v "SELECT 1" \
    -i //ResourceTMP -t attr -n "testOnBorrow" -v "true" \
    -r //ResourceTMP -v Resource \
    server.xml
fi

The trick is to temporarily give a unique name to the new element, so that it can be found later with an XPATH expression. After all attributes have been added, the name is changed back to Resource (with -r).

The meaning of the other xmlstarlet options:

-P (or --pf)        - preserve original formatting
-S (or --ps)        - preserve non-significant spaces
-L (or --inplace)   - edit file inplace

From version 1.4.0 of XMLStarlet (dated 2012-08-26), you can use $prev (or $xstar:prev) as the argument to -i, -a, and -s to refer to the last nodeset inserted. See the examples in the XMLStarlet source code in the files doc/xmlstarlet.txt, examples/ed-backref1, examples/ed-backref2, and examples/ed-backref-delete. You no longer need to use the trick of inserting the element with a temporary element name and then renaming it at the end. The example examples/ed-backref2 is particularly helpful in showing how to define a variable to use to refer to a (the) previously-created note so that you don't need to do tricks such as $prev/.. to "navigate" out of a node.

George Smith

The example did not work until I wrapped <GlobalNamingResources> into a <Server> element.

I tried the trick from cellux above.,It worked great! Thanks!! But, the formatting was not persisted, just to try, I got rid of options -P and -S, and the formatting issues were gone! I am using CentOS. May be this can help someone.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!