sureerat has asked for the wisdom of the Perl Monks concerning the following question:

Hello,

I'm trying to code a script to manage an XML list. My script will identify the record by an attribute named "path". If the record is not in the list, add it. If it's already in the list, just replace it.

... after that, the script return error. Please help suggest. Thanks.
#!/usr/bin/perl -w use strict; use XML::Twig; my %links = ( "img" => "/images/Blue_Test.jpg", "alt" => "Blue Image for test", "name" => "This is a test", "title" => "Hello World. This is a test page. Please click + here for more information.", "url" => "http://localhost/this_is_a_test.htm", "dcr" => "/templatedata/test2123/test/this_is_a_test", ); my $path = "/this_is_a_test3.htm"; my $file = "s1.xml"; my $x = XML::Twig::Elt->new( record => map { XML::Twig::Elt->new( $_ => $links{$_}) } sort keys %links) ->set_att("path",$path); if(!-e $file) { my $twig = XML::Twig::Elt->new(data => $x); open (XFILE, ">$file"); $twig->print(\*XFILE); # print it close XFILE; } else { my $twig= new XML::Twig(pretty_print => "indented", twig_handlers => # player will be cal +led { record => \&record } # when each player e +lement ); $twig->parsefile("$file"); sub record { my( $twig, $record)= @_; if($record->att('path') eq $path) { $x->replace($record); } else { $x->paste( 'last_child', $record->parent); } } $twig->flush; open (XFILE, ">$file"); $twig->print(\*XFILE); # print it close XFILE; }
The current XML file is:
<data> <record path="/this_is_a_test.htm"> <alt>Blue Test</alt> <dcr>/templatedata/test2123/test/this_is_a_test</dcr> <img>/images/Blue_Test.jpg</img> <name>This is a test</name> <title>Hello World. This is a test page. Please click here for mor +e information.</title> <url>http://localhost/this_is_a_test.htm</url> </record> <record path="/this_is_a_test2.htm"> <alt>Blue Test</alt> <dcr>/templatedata/test2123/test/this_is_a_test</dcr> <img>/images/Blue_Test.jpg</img> <name>This is a test</name> <title>Hello World. This is a test page. Please click here for mor +e information.</title> <url>http://localhost/this_is_a_test.htm</url> </record> </data>
---------------------------------

Hello,

Here is the note to update my post that I find the problem of my coding. This is the latest script that I think it's working. If you have any comment or suggestion to clean my script or make it better, please guide me. Thanks.

#!/usr/bin/perl -w use strict; use XML::Twig; my %links = ( "img" => "/images/Blue_Test.jpg", "alt" => "Blue Test Image", "name" => "This is a test", "title" => "Hello World. This is a test page. Please click + here for more information.", "url" => "http://localhost/this_is_a_test.htm", "dcr" => "/templatedata/test2123/test/this_is_a_test", ); my $path = "/this_is_a_test3.htm"; my $file = "s1.xml"; my $item_pasted; my $x = XML::Twig::Elt->new( record => map { XML::Twig::Elt->new( $_ => $links{$_}) } sort keys %links) ->set_att("path",$path); if(!-e $file) { my $twig = XML::Twig::Elt->new(data => $x); open (XFILE, ">$file"); $twig->print(\*XFILE); close XFILE; } else { my $twig= new XML::Twig(pretty_print => "indented", twig_handlers => { record => \&record } ); $twig->parsefile("$file"); $x->paste('last_child',$twig->root) unless($item_pasted); #$twig->flush; open (XFILE, ">$file"); $twig->print(\*XFILE); close XFILE; } sub record { my( $twig, $record)= @_; unless ($item_pasted) { my $rpath = $record->att('path'); if ($rpath eq $path) { $x->replace($record); $item_pasted=1; return } } }

Replies are listed 'Best First'.
Re: Use XML::Twig to manipulate XML data
by Tanktalus (Canon) on Oct 28, 2008 at 03:10 UTC

    Yeah, it does look like it's working. ++ for figuring it out :-)

    That said, there is some cleanup that could be done. First thing is that you have a bunch of common logic, and it's repeated. If you've never heard the acronym "DRY", here it is: Don't Repeat Yourself. If we can move your saving of your twig to outside the blocks, it'd be an improvement. One way is to make your $twig a bit more global (increase scope), another is to create a sub for saving that can be called and passed the $twig either way. I've gone for the former.

    Second is that when you save to a new file, you don't indent properly. There's nothing wrong with that - just be consistent. One way to do this is, again, descoping $twig somewhat.

    Your $item_pasted flag is something I would take the other way: scope it. The easiest way to do this is with an anonymous sub instead of a named sub. (You can also do this with a state variable in perl 5.10, but you don't *really* want a state variable here, you really do want it scoped properly.)

    Your file opening should use lexicals for a file handle, and you should try using the 3-arg version of open. Check for errors, and if you keep the scope down, you don't actually need to close the handle explicitly.

    No need to quote a single variable. $file is the same thing as "$file" (except when you're using an overloaded object, and not even always then, but let's not worry about that here, we aren't using any).

    So, what I end up with is:

    #!/usr/bin/perl -w use strict; use XML::Twig; my %links = ( "img" => "/images/Blue_Test.jpg", "alt" => "Blue Test Image", "name" => "This is a test", "title" => "Hello World. This is a test page. Please clic +k here for more information.", "url" => "http://localhost/this_is_a_test.htm", "dcr" => "/templatedata/test2123/test/this_is_a_test", ); my $path = "/this_is_a_test2.htm"; my $file = "s1.xml"; my $x = XML::Twig::Elt->new( record => map { XML::Twig::Elt->new( $_ => $links{$_}) } sort keys %links) ->set_att("path",$path); my $twig = XML::Twig->new(pretty_print => 'indented'); if(!-s $file) { $twig->set_root( XML::Twig::Elt->new(data => $x) ); } else { my $item_pasted; $twig->setTwigHandlers( { record => sub { my( $twig, $record)= @_; unless ($item_pasted) { my $rpath = $record->att('path' +); if ($rpath eq $path) { $x->replace($record); $item_pasted=1; return } } }, } ); $twig->parsefile($file); $x->paste('last_child',$twig->root) unless($item_pasted); } { open (my $xfile, ">", $file) or die "Can't save to $file: $!"; $twig->print(\*$xfile); }
    Hope that helps!

      Hello, Thank you very much for your comment. I will keep your guideline to improve my coding. ^_^