如何使用 xmlstarlet 在另一个元素下插入新元素?

Posted

技术标签:

【中文标题】如何使用 xmlstarlet 在另一个元素下插入新元素?【英文标题】:How to insert a new element under another with xmlstarlet? 【发布时间】:2011-08-22 16:31:05 【问题描述】:
$ vim test.xml

<?xml version="1.0" encoding="UTF-8" ?>
<config>
</config>
$ xmlstarlet ed -i "/config" -t elem -n "sub" -v "" test.xml
<?xml version="1.0" encoding="UTF-8"?>
<sub></sub>
<config>
</config>

但我希望 sub 成为 config 的子级。我应该如何更改xpath parameter of -i?

奖励: 是否可以使用属性直接插入孩子,甚至可以将其设置为一个值? 类似的东西:

$ xmlstarlet ed -i "/config" -t elem -n "sub" -v ""  -a attr -n "class" -v "com.foo" test.xml

【问题讨论】:

@see technomancy.org/xml/add-a-subnode-command-line-xmlstarlet @see altova.com/list/comp.text.xml/200810/msg1000290454.html 【参考方案1】:

我遇到了类似的问题:我有一个 Tomcat 配置文件 (server.xml),并且必须将带有预定义属性的 &lt;Resource&gt; 标记插入到 &lt;GlobalNamingResources&gt; 部分。

这是以前的样子:

<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>

这是我想要实现的目标:

<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>

这是我的做法(来自 shell 脚本的 sn-p):

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

诀窍是暂时给新元素一个唯一的名称,以便以后可以通过 XPATH 表达式找到它。添加完所有属性后,名称将改回 Resource(使用 -r)。

xmlstarlet 其他选项的含义:

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

【讨论】:

最后的编辑动作(重命名)改变了游戏规则。非常感谢你! +1 重命名,这是确保您只获得用于编辑的资源然后在最后重命名的好方法,谢谢。【参考方案2】:

使用-s(或--subnode)代替-i。关于奖励,您不能直接插入带有属性的元素,但是由于每个编辑操作都是按顺序执行的,因此插入一个元素然后添加一个属性:

> 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>

【讨论】:

这种方法的问题是任何其他同名的 XML 元素也会获取该属性,这可能是不希望的。解决此问题的一种方法是添加具有临时名称的元素,您可以通过 XPath 唯一地添加该名称并添加属性,然后在完成后保留该元素。【参考方案3】:

从 XMLStarlet 1.4.0 版(日期为 2012-08-26)开始,您可以使用 $prev(或 $xstar:prev)作为 -i-a-s 的参数来引用插入的最后一个节点集。请参阅文件doc/xmlstarlet.txtexamples/ed-backref1examples/ed-backref2examples/ed-backref-delete 中的XMLStarlet 源代码中的示例。您不再需要使用插入带有临时元素名称的元素然后在最后重命名它的技巧。示例examples/ed-backref2 特别有助于展示如何定义一个变量来引用(该)先前创建的注释,这样您就不需要使用诸如$prev/.. 之类的技巧来“导航”出节点。

【讨论】:

酷,您能用(当前)最高票数制作一个更好的答案版本吗? ***.com/a/9172796/2015768【参考方案4】:

在我将 &lt;GlobalNamingResources&gt; 包装到 &lt;Server&gt; 元素中之前,该示例不起作用。

【讨论】:

【参考方案5】:

正如@npoostavs 所提到的,正确的答案是使用“子节点”。要使用新的“$prev”改进答案,您可以执行以下操作:

xml ed --inplace \
       --subnode /config --type elem --name "sub" \
       --var new_node '$prev' \
       --insert '$new_node' --type attr --name "class" --value "com.foo" \ 
       test.xml

有以下解释:

--inplace    Change the file "test.xml" directly
--subnode    Add a new node called "class" below "/config"
--var        Assign the newly created node to the variable new_node 
             Use single quotes to prevent bash replacing the variable
--insert     Insert attribute and value to the newly created node

【讨论】:

【参考方案6】:

我尝试了上面 cellux 的技巧。效果很好!谢谢!! 但是,格式化并没有持久化,只是为了尝试,我去掉了选项 -P 和 -S,格式化问题就消失了!我正在使用 CentOS。也许这可以帮助某人。

【讨论】:

以上是关于如何使用 xmlstarlet 在另一个元素下插入新元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 xmlstarlet 为 xml 文件的每个节点添加不同的属性

javascript 在另一个之后或之前插入元素

在 plist 文件中如何通过 xmlstarlet 工具在唯一键标记后提取字符串文本

PHP 如何插入嵌套元素 Libreoffice 样式

xmlstarlet 默认命名空间

使用 xmlstarlet 或 xmllint 获取属性值