'Script to update xml value


I am trying to do update on xml file based on condition using AWK Script. Could anyone assist me on this?

students.xml

<students>
    <student>
        <stuId>1</stuId>
        <name>A</name>
        <mark>75</mark>
        <result></result>
    </student>
    <student>
        <stuId>2</stuId>
        <name>B</name>
        <mark>35</mark>
        <result></result>
    </student>
    <student>
        <stuId>1</stuId>
        <name>C</name>
        <mark>94</mark>
        <result></result>
    </student>
</students>

Code I tried so far

I am able to extract tag values using below code

BEGIN { RS="<[^>]+>" } 
{ print  RT, $0 }

This prints all the tag and values as expected.

I want to update the <result> tag as pass if marks > 40 else fail

Output

<students>
    <student>
        <stuId>1</stuId>
        <name>A</name>
        <mark>75</mark>
        <result>pass</result>
    </student>
    <student>
        <stuId>2</stuId>
        <name>B</name>
        <mark>35</mark>
        <result>fail</result>
    </student>
    <student>
        <stuId>1</stuId>
        <name>C</name>
        <mark>94</mark>
        <result>pass</result>
    </student>
</students>

Could any one assist me on this?



Solution 1:[1]

Don't try to parse XML with , instead use a real parser :

warning the XML file is edited on the fly!

With :

#!/usr/bin/env perl
# edit file.xml file in place
use strict; use warnings;

use XML::LibXML;

my $xl = XML::LibXML->new();
my $xml = $xl->load_xml(location => '/tmp/file.xml') ;

for my $node ($xml->findnodes('//student/result')) {
    my $mark = $node->findnodes('../mark/text()')->string_value;
    $node->removeChildNodes();
    if ($mark > 40) {
        $node->appendText('pass');
    }
    else {
        $node->appendText('fail');
    }
}

$xml->toFile('/tmp/file.xml');

Modified file :

<?xml version="1.0"?>
<students>
  <student>
    <stuId>1</stuId>
    <name>A</name>
    <mark>75</mark>
    <result>pass</result>
  </student>
  <student>
    <stuId>2</stuId>
    <name>B</name>
    <mark>35</mark>
    <result>fail</result>
  </student>
  <student>
    <stuId>1</stuId>
    <name>C</name>
    <mark>94</mark>
    <result>pass</result>
  </student>
</students>

Solution 2:[2]

Another option is to use the ed (edit) command of xmlstarlet...

xmlstarlet ed -L -u "//student[mark >= 40]/result" -v "pass" -u "//student[40 > mark]/result" -v "fail" students.xml

CAUTION: The -L in the command line will edit the file inplace. Be sure to remove it if that is not the behavior you want.

You can also use XSLT 1.0 with xmlstartlet (the tr (transform) command)...

update.xsl

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="student[mark >= 40]/result">
    <xsl:copy>pass</xsl:copy>
  </xsl:template>

  <xsl:template match="student[40 > mark]/result">
    <xsl:copy>fail</xsl:copy>
  </xsl:template>

</xsl:stylesheet>

command line

xmlstarlet tr update.xsl students.xml

Solution 3:[3]

I would also recommend to avoid a XML parser/processor approach here: If you don't like perl you can use a full XML technology approach by using XSLT:

INPUT:

$ more students.xml
::::::::::::::
students.xml
::::::::::::::
<students>
    <student>
        <stuId>1</stuId>
        <name>A</name>
        <mark>75</mark>
        <result></result>
    </student>
    <student>
        <stuId>2</stuId>
        <name>B</name>
        <mark>35</mark>
        <result></result>
    </student>
    <student>
        <stuId>1</stuId>
        <name>C</name>
        <mark>94</mark>
        <result></result>
    </student>
</students>

XSLT stylesheet:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform (copy everything) -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<!-- when you reach result take action-->
<xsl:template match="result">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
                        <!-- fetch the value of mark of the parent node -->
                        <xsl:variable name="mark" select="../mark" />
                        <xsl:choose>
                        <!-- if over 40 -->
                         <xsl:when test="$mark > 40">
                           <xsl:text>pass</xsl:text>
                         </xsl:when>
                         <!-- else -->
                         <xsl:otherwise>
                           <xsl:text>fail</xsl:text>
                         </xsl:otherwise>
                   </xsl:choose>
    </xsl:copy>   
</xsl:template>

</xsl:stylesheet>

COMMAND:

$ xsltproc --output students_grade.xml students.xsl  students.xml 

OUTPUT:

more students_grade.xml 
<?xml version="1.0" encoding="UTF-8"?>
<students>
  <student>
    <stuId>1</stuId>
    <name>A</name>
    <mark>75</mark>
    <result>pass</result>
  </student>
  <student>
    <stuId>2</stuId>
    <name>B</name>
    <mark>35</mark>
    <result>fail</result>
  </student>
  <student>
    <stuId>1</stuId>
    <name>C</name>
    <mark>94</mark>
    <result>pass</result>
  </student>
</students>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Glorfindel
Solution 2
Solution 3 Allan