Reputation: 9
I am having a very long xml and I wish to update the attribute value of one of the tag which is very deep nested so don't want to go node by node. Also structure is not same for the intended node always as can be seen below: Input XML is:
<Re>
<Co Class="Parameter" ID="CSCP001" Status="Available">
<FileSpec URL="c://mine/testfiles/wln/c.txt"/>
<CoOp Operation="Tag" SourceCS="RGB" SourceObjects="All">
<FileSpec Resource="SourceProfile" URL="c://mine/testfiles/wln/d.txt"/>
</CoOp>
</Co>
<Ru Class="Parameter" ID="IDR002" PartIDKeys="Run" Status="Available">
<Ru EndOfDocument="true" Pages="0" Run="1" RunTag="First">
<La>
<FileSpec URL="c://mine/testfiles/wln/e.txt"/>
</La>
</Ru>
</Ru>
</Re>
and I wish to have output xml as
<Re>
<Co Class="Parameter" ID="CSCP001" Status="Available">
<FileSpec URL="d://yours/wln/c.txt"/>
<CoOp Operation="Tag" SourceCS="RGB" SourceObjects="All">
<FileSpec Resource="SourceProfile" URL="d://yours/wln/d.txt"/>
</CoOp>
</Co>
<Ru Class="Parameter" ID="IDR002" PartIDKeys="Run" Status="Available">
<Ru EndOfDocument="true" Pages="0" Run="1" RunTag="First">
<La>
<FileSpec URL="d://yours/wln/e.txt"/>
</La>
</Ru>
</Ru>
</Re>
I tried using xml simple, xmllib but not able to do the required. I am new in perl programming.
use XML::LibXML qw( );
use XML::LibXML;
use Data::Dumper;
my $xml = "a.txt";
my $xpath_expression = 'FileSpec';
my $parser = XML::LibXML->new();
my $doc = $parser->parse_file($xml) or warn "Could not";
my $parser1 = XML::LibXML::Element->new($xml);
for my $FileSpec1 ($doc->getElementsByTagName('FileSpec'))
{
print $FileSpec1;
my $xpath = '$FileSpec1/@URL';
my ($attr) = $doc->findnodes($xpath);
$attr->setValue('dfdsa');
my ($URL1) = $FileSpec1->findvalue('@URL');
print $URL1;
}
I tried using $node->setAttribute( $aname, $avalue ); but this is throwing exceptions. Please advice.
Upvotes: 0
Views: 3903
Reputation: 36282
You can try with XML::Twig
module. It has the twig_handlers
option that selects the tags you want and triggers a handler. The default variable $_
has the element and its method set_att()
lets you change its value easily:
#!/usr/bin/env perl
use warnings;
use strict;
use XML::Twig;
my $new_url = q{d://yours/wln/d.txt};
my $twig = XML::Twig->new(
twig_handlers => {
'FileSpec' => sub { $_->set_att( 'URL', $new_url ) }
},
pretty_print => 'indented',
)->parsefile( shift )->print();
Run it like:
perl script.pl xmlfile
That yields:
<Re>
<Co Class="Parameter" ID="CSCP001" Status="Available">
<FileSpec URL="d://yours/wln/d.txt"/>
<CoOp Operation="Tag" SourceCS="RGB" SourceObjects="All">
<FileSpec Resource="SourceProfile" URL="d://yours/wln/d.txt"/>
</CoOp>
</Co>
<Ru Class="Parameter" ID="IDR002" PartIDKeys="Run" Status="Available">
<Ru EndOfDocument="true" Pages="0" Run="1" RunTag="First">
<La>
<FileSpec URL="d://yours/wln/d.txt"/>
</La>
</Ru>
</Ru>
</Re>
EDIT: Mirod's version pointed out in comments of a more efficient parsing using twig_roots()
:
#!/usr/bin/env perl
use warnings;
use strict;
use XML::Twig;
my $new_url = q{d://yours/wln/d.txt};
my $twig = XML::Twig->new(
twig_roots => {
'FileSpec' => sub { $_->set_att( 'URL', $new_url ); $_->flush }
},
twig_print_outside_roots => 1,
pretty_print => 'indented',
)->parsefile( shift );
Upvotes: 1
Reputation: 242333
Your code is too complicated. You need no parser, no elements, just find the urls and change them:
#!/usr/bin/perl
use warnings;
use strict;
use XML::LibXML;
my $xml = 'XML::LibXML'->load_xml(location => 'a.xml') ;
for my $url ($xml->findnodes('//FileSpec/@URL')) {
my $value = $url->getValue;
$value =~ s{c://mine/testfiles}{d://yours};
$url->setValue($value);
}
$xml->toFile('new.xml');
Upvotes: 4