How to search for a XML node and add or delete it using xmlstarlet

xmlstarlet cheat sheet
xmlstarlet bash
xmlstarlet html
xmlstarlet vs xmllint
xmlstarlet remove namespace
xmlstarlet el
xml cli
xmlstarlet format xml

I try to create a shell script that searches in an XML file for an attribute and create an element with the given attribute if this doesn't exist or delete the element if the attribute exists.

Here is the XML File:

<configuration name="distributor.conf" description="Distributor Configuration">
  <lists>       
    <list name="CRproductionLoadshare">
      <node name="fs100" weight="2"/>
      <node name="fs101" weight="2"/>     
    </list>
    <list name="AnyOtherGroup">
      <node name="fs100" weight="2"/>          
    </list>
  </lists>
</configuration>

And this is my Shellscript so far:

fs_name=fs
cnt=102
xmlstarlet ed \
  --var fs "'$fs_name$cnt'" \
  -a '//list' -t elem -n node -v "$fs_name$cnt" \
  -i '//node' -t attr -n name -v "$fs_name$cnt" \
  -i '//node' -t attr -n weight -v 2 \
  -d '//node[.=$fs]/text()' <distributor.conf.xml

The expected Output is

<configuration name="distributor.conf" description="Distributor Configuration">
  <lists>       
    <list name="CRproductionLoadshare">
      <node name="fs100" weight="2"/>
      <node name="fs101" weight="2"/>  
      <node name="fs102" weight="2"/>    
    </list>
    <list name="AnyOtherGroup">
      <node name="fs100" weight="2"/>          
    </list>
  </lists>
</configuration>

But my script work like this:

<?xml version="1.0"?>
<configuration name="distributor.conf" description="Distributor Configuration">
  <lists>
    <list name="CRproductionLoadshare">
      <node name="fs100" weight="2" name="fs102" weight="2"/>
      <node name="fs101" weight="2" name="fs102" weight="2"/>
    </list>
    <list name="AnyOtherGroup">
      <node name="fs100" weight="2" name="fs102" weight="2"/>          
    </list>
    <node name="fs102" weight="2"/>
  </lists>
</configuration>

How to change the shell script to reach the goal. At first, I want to add the node name="fs102" in case of that this node didn't exist.

The most difficult task at hand here is to build the XPath that selects the correct node.

Step 1: find the XPath you need

example 1: Select the node named list who has an attribute @name="CRproductionLoadshare" and has a child named node with attribute @name="fs100".

So you can search for the parent of that particular node named node.

$ xmlstarlet sel -t                                                            \
        -m '//node[@name="fs100"]/parent::list[@name="CRproductionLoadshare"]' \
        -c . -n foo.xml
<list name="CRproductionLoadshare">
      <node name="fs100" weight="2"/>
      <node name="fs101" weight="2"/>     
</list>

or a bit easier :

$ xmlstarlet sel -t                                                        \ 
       -m '//list[@name="CRproductionLoadshare" and node[@name="fs100"]]'  \
       -c . -n foo.xml

example 2: Select the node named list who has an attribute @name="CRproductionLoadshare" and does not have a child named node with attribute @name="fs102".

Here we can use the XPath not-function

$ xmlstarlet sel -t                                                        \ 
       -m '//list[@name="CRproductionLoadshare" and not(node[@name="fs102"])]'  \
       -c . -n foo.xml
<list name="CRproductionLoadshare">
      <node name="fs100" weight="2"/>
      <node name="fs101" weight="2"/>     
</list>
Step 2: edit your XML-file with the XPath you just found
A: Just add the node if it is not there

So, since you now know the correct XPath to select the node, you can edit the XML-file accordingly by first inserting a subnode -s and then updating its values and attributes with -i

$ xpath1='//list[@name="CRproductionLoadshare" and not(node[@name="fs102"])]'
$ xpath2='//list[@name="CRproductionLoadshare" and not(node[@name="fs102" and @weight="2"])]/node[last()]'
$ xmlstarlet ed -s ${xpath1} -t elem -n "node"   -v ""      \
                -i ${xpath2} -t attr -n "name"   -v "fs102" \
                -i ${xpath2} -t attr -n "weight" -v "2"     \
                foo.xml

which outputs

<configuration name="distributor.conf" description="Distributor Configuration">
  <lists>
    <list name="CRproductionLoadshare">
      <node name="fs100" weight="2"/>
      <node name="fs101" weight="2"/>
      <node name="fs102" weight="2"/>
    </list>
    <list name="AnyOtherGroup">
      <node name="fs100" weight="2"/>
    </list>
  </lists>
</configuration>
B: Toggle the node

Toggling can be done by adding a fake attribute and then remove the node with that attribute:

$ xpath0='//list[@name="CRproductionLoadshare"]/node[@name="fs102"]'
$ xpath1='//list[@name="CRproductionLoadshare" and not(node[@name="fs102" and @delete="1"])]'
$ xpath2='//list[@name="CRproductionLoadshare" and not(node[@name="fs102" and @delete="1"])]/node[last()]'
$ xpath3='//list[@name="CRproductionLoadshare"]/node[@name="fs102" and @delete="1"]'
$ xmlstarlet ed -i ${xpath0} -t attr -n "delete" -v "1"     \
                -s ${xpath1} -t elem -n "node"   -v ""      \
                -i ${xpath2} -t attr -n "name"   -v "fs102" \
                -i ${xpath2} -t attr -n "weight" -v "2"     \
                -d ${xpath3}                                \
                foo.xml

3. Editing XML documents - XMLStarlet, for editing <xml-file-or-uri> - input XML document file name/uri (stdin is used if --help or -h - display help where <action> -d or --delete <xpath> -i or --insert -​t (--type) elem|text|attr -n <name> -v (--value) <value> -s or --subnode <xpath> -t a command line toolkit to query/edit/check/transform XML documents (for more​  Adding new nodes based on the number of existing nodes using XSLT. Tag: xml,xslt. I want to modify the following xml using XSLT: I want to add a new node <sort

XmlStarlet Command Line XML Toolkit User's Guide, Modify or edit XML documents (ex. delete some elements) XmlStarlet 'select' or 'sel' option can be used to query or search XML documents. <xsl:element name​="name"> -a or --attr <name> - add attribute <xsl:attribute name="name"> -b or  A: Just add the node if it is not there. So, since you now know the correct XPath to select the node, you can edit the XML-file accordingly by first inserting a subnode -s and then updating its values and attributes with -i

The shell script that works for toggle the node looks like this:

fs_name=fs
cnt=102
inputfile=distributor.conf.xml

if [ -n "$(xmlstarlet sel -T -t -v "//list[@name='CRproductionLoadshare']/node[@name='$fs_name$cnt']/@name" $inputfile)" ]; then
  echo "$fs_name$cnt already defined in $inputfile"
  xmlstarlet ed -L -d "//list[@name='CRproductionLoadshare']/node[@name='$fs_name$cnt']" $inputfile
else
  echo "adding $fs_name$cnt to $inputfile"
  xmlstarlet ed -L -s  "//list[@name='CRproductionLoadshare']" -t elem -n TempNode -v "" \
    -i //TempNode -t attr -n "name" -v "$fs_name$cnt" \
    -i //TempNode -t attr -n "weight" -v "2" \
    -r //TempNode -v node \
    $inputfile
fi

Each time I run that script the inputfile toggle the node (add/delete) only in the list element with name CRproductionLoadshare.

Find a string in xml tag and delete a line which is part of match tag , I try to create a shell script that searches in an XML file for an attribute and xmlstarlet ed \ --var fs "'$fs_name$cnt'" \ -a '//list' -t elem -n node -v  You can modify an XML tree, removing elements, attributes, and other types of nodes. Removing a single element or a single attribute from an XML document is straightforward. However, when removing collections of elements or attributes, you should first materialize a collection into a list, and then delete the elements or attributes from the list.

how to define specific XML element in XPath to delete with xmlStarlet?, Assuming that the XML document is valid, for example like <?xml version="1.0"?> <root> <oneFi institution="12345678"> <bin _list="1"> <value  XMLStarlet is a set of command line utilities (tools) which can be used to transform, query, validate, and edit XML documents and files using simple set of shell commands in similar way it is done for plain text files using UNIX grep, sed, awk, diff, patch, join, etc commands.

Start working with XMLStarlet, I am trying to use xmlStarlet to delete a particular element in an XML file and I am The examples I find use attributes but there are no attributes in this XML file. What do I need to add to the XPath spec to restrict it for this adjacent element? Using nodes() method against a variable of xml type. In the following example, there's an XML document that has a <Root> top-level element and three <row> child elements. The query uses the nodes() method to set separate context nodes, one for each <row> element. The nodes() method returns a rowset with three rows.

XMLStarlet, To get started with XMLStarlet, you need to install it. But to do so, you need the XMLStarlet is a command line toolkit to query/edit/check/transform You use the edit command ( ed ) to remove the d nodes from the XML. Set the variable x to be the first title element node; Set the variable y to be the text node to remove; Remove the element node by using the removeChild() method from the parent node; It is not very common to use removeChild() just to remove the text from a node. The nodeValue property can be used instead. See next paragraph.

Comments
  • your input/output does not cover the 2nd part of your condition or delete it if it exist. Elaborate your conditions
  • Right, I want to create the add condition at first, if this works I will create the second, maybe in a separate script.
  • How do you decide where to put the node. As I see, you can place it in <list name="CRproductionLoadshare"> as well as <list name="AnyOtherGroup">. Why do you only want to create the node in the first potential parent and not in the second.
  • I want to add the node element in the list element with name CRproductionLoadshare, as in my answer I only add the element in the CRproductionLoadshare named list element.
  • Thanks, this script add the node but do not delete the element in case that still exist. I want to toggle the element add in case that didn't exist or delete in the case that exist. In my answer the script can toggle but in your answer there are more detailed information about how it work. If you also add the delete function I will add your answer as the right one.
  • @kockiren, so do I understand correctly that you want to: a) if the node is there, replace it with a new one and b) if the node is not there, add it. Or is it, a) if the node is there, delete it and b) if the node is not there, add it.
  • If the element node with attribute name="fs102" exist it should be deleted and if the element doesn't exist it should be create.
  • Thx, the node[last()] for addressing the last inserted node looks interesting, what I have found was with a temp variable, is this the same like your syntax?
  • @RomanPerekhrest, ++ve. you are champion of xmlstarlet learning from you a lot. would be grateful if you could add some explanation to this post too.
  • One more issue is to reference only the node in list CRproductionLoadshare, if there is a different list with different name than the new node will add in every list element. But maybe this is a different question? I have updated the question if you think it is for a new topic please delete my edit.
  • @RavinderSingh13, thanks, I've added some explanation
  • If I use your script and the node name fs102 exist in the CRproductionLoadshare list element you're script add one more node fs102 element