1

I want to update attributes in xml using perl.The problem here is when I am updaing the attributes of xml it is happening but the xml format is being changed.Breaking my head but no use !

Can anyone pls suggest me some perl code to update attributes in xml with out affecting the xml format

I used the perl code as shown below

#!/usr/bin/perl
use strict;
use warnings;
use XML::Simple;

my $xml_file = '3.xml';

my $xml = XMLin(
$xml_file,
KeepRoot => 1,
ForceArray => 1
);

$xml->{outer1}->[0]->{inner1}->[1]->{name}->[0]->{first} = 'Shane Bond';

XMLout(
  $xml,
KeepRoot => 1,
NoAttr => 1,
OutputFile => $xml_file,
);

Input xml:

<outer1>
  <inner1>
    <name>Stonecold</name>
    <org>wwf</org>
    <profession>
      <Bowler>hai</Bowler>
    </profession>
  </inner1>
  <inner1>
     <name first = "Shanebond" />
     <org>newzealand</org>
     <profession>Shane Bond</profession>
  </inner1>
  <inner1>
     <name>brain schemidit</name>
     <org>Google</org>
     <profession>Chairman</profession>
  </inner1>
</outer1>

Expected Output xml:

<outer1>
  <inner1>
    <name>Stonecold</name>
    <org>wwf</org>
    <profession>
      <Bowler>hai</Bowler>
    </profession>
  </inner1>
  <inner1>
     <name first = "Shane Bond" />
     <org>newzealand</org>
     <profession>Shane Bond</profession>
  </inner1>
  <inner1>
     <name>brain schemidit</name>
     <org>Google</org>
     <profession>Chairman</profession>
  </inner1>
</outer1>

Actual Output xml:

<outer1>
  <inner1>
    <name>Stonecold</name>
    <org>wwf</org>
    <profession>
     <Bowler>hai</Bowler>
    </profession>
  </inner1>
  <inner1>
    <name>
      <first>Shane Bond</first>
    </name>
    <org>newzealand</org>
    <profession>Shane Bond</profession>
  </inner1>
  <inner1>
    <name>brain schemidit</name>
    <org>Google</org>
    <profession>Chairman</profession>
  </inner1>
</outer1>
kmmmf
  • 13
  • 2
  • [*Why is XML::Simple "Discouraged"?*](http://stackoverflow.com/questions/33267765/why-is-xmlsimple-discouraged) – Borodin Apr 29 '16 at 14:35

3 Answers3

1

You set NoAttr => 1 for XMLout(). The XML::Simple documentation says:

NoAttr => 1 # in+out - handy

When used with XMLout(), the generated XML will contain no attributes. All hash key/values will be represented as nested elements instead.

When used with XMLin(), any attributes in the XML will be ignored.

You want an attribute, but turn attributes off?

I tried: print XMLout( XMLin("t.xml", KeepRoot => 1, ForceArray => 1), KeepRoot => 1 );

The diff of both files looks ok:

$ diff -bBEup t.xml t2.xml 
--- t.xml   2016-04-29 10:36:28.446578760 +0200
+++ t2.xml  2016-04-29 10:39:03.450073658 +0200
@@ -7,7 +7,7 @@
     </profession>
   </inner1>
   <inner1>
-     <name first = "Shanebond" />
+    <name first="Shanebond" />
      <org>newzealand</org>
      <profession>Shane Bond</profession>
   </inner1>

Everything looks ok without NoAttr:

$x = XMLin("t.xml", KeepRoot => 1, ForceArray => 1);
$x->{outer1}->[0]->{inner1}->[1]->{name}->[0]->{first} = "Larry";
print XMLout($x, KeepRoot => 1);

<outer1>
  <inner1>
    <name>Stonecold</name>
    <org>wwf</org>
    <profession>
      <Bowler>hai</Bowler>
    </profession>
  </inner1>
  <inner1>
    <name first="Larry" />
    <org>newzealand</org>
    <profession>Shane Bond</profession>
  </inner1>
  <inner1>
    <name>brain schemidit</name>
    <org>Google</org>
    <profession>Chairman</profession>
  </inner1>
</outer1>
Sebastian
  • 2,472
  • 1
  • 18
  • 31
0

There are many ways to skin this cat. One is to use XML::LibXML. Your example would look like this;

use v5.12;
use warnings;
use XML::LibXML;

my $filename = '3.xml' ;
my $xpath    = '//name[contains(@first, "Shane")]' ;

my $dom = XML::LibXML->load_xml(
    location => $filename
);

for my $td ($dom->findnodes($xpath)) {
    $td->setAttribute( "first" , "Shane Bond" );
}

say $dom->toString();       # print the updated XML
$dom->toFile("3.xml.new");  # alterntaively, dump it to a file

when ran on the file above, it produces;

<?xml version="1.0"?>
<outer1>
  <inner1>
    <name>Stonecold</name>
    <org>wwf</org>
    <profession>
      <Bowler>hai</Bowler>
    </profession>
  </inner1>
  <inner1>
     <name first="Shane Bond"/>
     <org>newzealand</org>
     <profession>Shane Bond</profession>
  </inner1>
  <inner1>
     <name>brain schemidit</name>
     <org>Google</org>
     <profession>Chairman</profession>
  </inner1>
</outer1>

Xpath is a query language - in this case the $xpath variable is a request for any nodes in the document with the name name and an attribute called first that contains the string Shane. An alternative way to do it is to set $xpath to simply //name and the second iteration through the loop would have the correct node.

There is an excellent "Tutorial by example" on XML::LibXML by Grant McLean here. A little reading there should solve any similar problems.

Marty
  • 2,788
  • 11
  • 17
  • Hi Marty,thank u very much for sharing the code above , I can see the expected output on the console but the xml file is not updated which is needed.Can you pls suggest anything for xml file updation. – kmmmf Apr 29 '16 at 10:55
  • Hi Marty,its working..........! xml file updation is working fine and sorry for the late reply – kmmmf May 03 '16 at 10:11
0

Why is XML::Simple "Discouraged"?

This is one of the reason XML::Simple is a bad choice.

Something like XML::Twig can do it like this:

#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig -> new ( pretty_print => 'indented_a');
$twig -> parse ( \*DATA );
$twig -> findnodes ( '//inner1/name', 1 ) -> set_att('first', "Shane Bond");
$twig -> print;

__DATA__
<outer1>
  <inner1>
    <name>Stonecold</name>
    <org>wwf</org>
    <profession>
      <Bowler>hai</Bowler>
    </profession>
  </inner1>
  <inner1>
     <name first = "Shanebond" />
     <org>newzealand</org>
     <profession>Shane Bond</profession>
  </inner1>
  <inner1>
     <name>brain schemidit</name>
     <org>Google</org>
     <profession>Chairman</profession>
  </inner1>
</outer1>

But actually, I'd suggest that rather than 'ordering' the nodes, you can just use XPATH to find the one you want:

$twig -> findnodes ( '//inner1/name[@first="Shanebond"]', 0 ) -> set_att('first', "Shane Bond");

This - rather than just selecting the 'second' element, finds the one where the first attribute is incorrect and fixes it.

So making your code:

#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig -> new ( pretty_print => 'indented_a');
$twig -> parsefile ( '3.xml' )
$twig -> findnodes ( '//inner1/name[@first="Shanebond"]', 0 ) -> set_att('first', "Shane Bond");

open ( my $output, '>', '3.new.xml' ) or die $!;
print {$output} $twig -> sprint; 
close ( $output );
Community
  • 1
  • 1
Sobrique
  • 52,974
  • 7
  • 60
  • 101