2


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?

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
Dhanabalan
  • 572
  • 5
  • 19
  • Sorry for the late reply. Yes it does. I'm taking this sample approach to actual implementation hope it works there. I'll let you know if I faced any issues there. Thanks all again – Dhanabalan Mar 22 '18 at 18:03

3 Answers3

7

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
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
4

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>
Allan
  • 12,117
  • 3
  • 27
  • 51
3

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>
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • Nice perl parsing solution +1! This time as I have also judged that this was a parser solution that would be the best, I have proposed a XSLT solution! Bonne nuit au fait ;-) – Allan Mar 16 '18 at 03:25