-1

I am trying to compare two of xml files and update only for a certain key as a new file. The issue occurs when i export a zabbix template and try to import on the other environment, status should be left as destination one. Assume that i have two xml files,

source.xml

<zabbix_export>
    <version>5.0</version>
    <groups>
        <group>
            <name>zabbix</name>
        </group>
    </groups>
    <templates>
        <template>
            <template>testtemp</template>
            <name>testtemp</name>
            <groups>
                <group>
                    <name>zabbix</name>
                </group>
            </groups>
            <items>
                <item>
                    <name>test1</name>
                    <key>kernel.maxproc</key>
                    <triggers>
                        <trigger>
                            <expression>{last()}=0</expression>
                            <name>testtrig1</name>
                        </trigger>
                        <trigger>
                            <expression>{last()}=100</expression>
                            <name>testtrig2</name>
                        </trigger>
                    </triggers>
                </item>
            </items>
        </template>
    </templates>
</zabbix_export>

destination.xml

    <version>5.0</version>
    <groups>
        <group>
            <name> zabbix </name>
        </group>
    </groups>
    <templates>
        <template>
            <template>testtemp</template>
            <name>testtemp</name>
            <groups>
                <group>
                    <name>zabbix</name>
                </group>
            </groups>
            <items>
                <item>
                    <name>test1</name>
                    <key>kernel.maxproc</key>
                    <triggers>
                        <trigger>
                            <expression>{last()}=0</expression>
                            <name>testtrig1</name>
                            <status>DISABLED</status>
                        </trigger>
                    </triggers>
                </item>
            </items>
        </template>
    </templates>
</zabbix_export> 

So my goal would be to create a new file and put the key/value "DISABLED" as following.

final.xml

<zabbix_export>
    <version>5.0</version>
    <groups>
        <group>
            <name>zabbix</name>
        </group>
    </groups>
    <templates>
        <template>
            <template>testtemp</template>
            <name>testtemp</name>
            <groups>
                <group>
                    <name>zabbix</name>
                </group>
            </groups>
            <items>
                <item>
                    <name>test1</name>
                    <key>kernel.maxproc</key>
                    <triggers>
                        <trigger>
                            <expression>{last()}=0</expression>
                            <name>testtrig1</name>
                            <status>DISABLED</status>
                        </trigger>
                        <trigger>
                            <expression>{last()}=100</expression>
                            <name>testtrig2</name>
                        </trigger>
                    </triggers>
                </item>
            </items>
        </template>
    </templates>
</zabbix_export>

I've found one of the closest way to achieve this behave on the post Updating two xml file using xmlstarlet but still needs a small touch. So seems better to use 'xmlstarlet' since i need to run this babe in Debian natively.

It would be great at least give a clue how to use it in that way.

Thanks in advance,

linuxman
  • 61
  • 5
  • `destination.xml` isn't XML unless you insert `` at start of file. In `final.xml` `//group[1]/name` is from `source.xml` but `//trigger[1]` is from `destination.xml`; explanation? What [tag:xmlstarlet] command did you attempt? – urznow Dec 23 '21 at 16:43
  • only difference between two xml is `DISABLED` tag so all i want to insert this tag into final.xml if destination has it. – linuxman Dec 23 '21 at 18:58
  • If the only difference between destination and final is that final has `DISABLED`, why not check if destination has the tag and, if so, just use destination as final (that is, make a copy of destination and call it "final")? – Jack Fleeting Dec 23 '21 at 22:26
  • it depends. the thing is i have to insert `DISABLED` for certain upper key, in that case, for trigger named `testtrig1` but all other ``. so need to figure out for delta also. – linuxman Dec 24 '21 at 01:00
  • basically, if a trigger DISABLED on the destination but in the source, the final.xml should also be DISABLED for involved trigger. – linuxman Dec 24 '21 at 01:12

1 Answers1

0

Here are two ways to solve it, both assume that

  • trigger elements have unique names
  • a POSIX shell is used

First, an XSLT 1.0 transformation which will add a status element from a same-named trigger in destination.xml. It's a basic identity transform which modifies the appropriate triggers. In case you want to limit to certain status values you could add, for example, and $dstat = "DISABLED" to the xsl:if test clause.

<xsl:transform version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:strip-space elements="*"/>
  <xsl:param name="ddoc" select="'destination.xml'"/>
  <xsl:variable name="dtt" select="document($ddoc)//triggers/trigger"/>
  
  <xsl:template match="@*|node()" name="identity">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="triggers/trigger[not(status)]">
    <xsl:variable name="dstat" 
        select="$dtt[name = current()/name]/status"/>
    <xsl:copy>
      <xsl:apply-templates/>
      <xsl:if test="string($dstat)">
        <xsl:copy-of select="$dstat"/>
      </xsl:if>
    </xsl:copy>
  </xsl:template>

</xsl:transform>

Run as

xsltproc --stringparam ddoc destination.xml delta.xsl source.xml > final.xml

or

xmlstarlet tr delta.xsl -s ddoc=destination.xml source.xml > final.xml

Second, to do the same in shorthand is trickier since there are limitations on both the xmlstarlet select and xmlstarlet edit operations: the former doesn't copy entire input, the latter doesn't accept conditionals (except in XPath expressions). However, using select as a code generator to produce an edit command is possible. (To list the generated XSLT 1.0 code add -C option before -t.)

xmlstarlet sel -t \
  --var sq -o "'" -b \
  --var dq -o '"' -b \
  --var ddoc="'destination.xml'" \
  --var dtt='document($ddoc)//triggers/trigger' \
  -o 'xmlstarlet edit \' -n \
  -m '//triggers/trigger[not(status)]' \
    --var dstat='$dtt[name = current()/name]/status' \
    --if 'string($dstat)' \
      -o ' -s ' -v 'concat($sq,"//triggers/trigger[name=",$dq,current()/name,$dq,"]",$sq)' \
      -o ' -t elem -n status -v ' -v 'concat($sq,$dstat,$sq)' -o ' \' -n \
    -b \
  -b -f -n source.xml

For each status-less trigger in source.xml this command looks up the same-named element in destination.xml having a non-empty status element; on match emits an -s (subnode) clause for xmlstarlet edit to target the appropriate node in source.xml. -o outputs literal text, -n a newline, -f the input pathname, -b ends current container (-m, --if, --var). Variables sq and dq assist in quoting

Output:

xmlstarlet edit \
 -s '//triggers/trigger[name="testtrig1"]' -t elem -n status -v 'DISABLED' \
source.xml

Run as

xmlstarlet-sel-command-above | sh -s > final.xml

to execute the output as a shell script.

urznow
  • 1,576
  • 1
  • 4
  • 13