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

Hello Monks,
I have developped a way to add new elements with XML::Simple. I would like to have some suggestion from you guys. Any suggestions or comments are welcome. If you found another way more simple, please let me know.

Here the code:
use strict; use warnings; use XML::Simple; use DATA::Dumper; #Variable my $counter = 0; my $countertrans = 0; my $file = (\*DATA); my $arrays = [qw/trans item status assignee/]; my $opt = XMLin($file, keyattr => 1, forcearray => $arrays); my $id = 'maxis'; my $lastid = 'legas'; foreach my $trans (@{$opt->{'trans'}}) { my $transs = $opt->{'trans'}[$countertrans]{'kool'}; if ($transs eq 'max') { foreach my $item (@{$trans->{'item'}}) { my $currentid = $opt->{'trans'}[0]{'item'}[0]{'id'}; $counter++; if ($lastid eq $currentid) { $opt->{'trans'}[$countertrans]{'item'}[$counter]{'id'} + = $id || die "Error!!"; $opt->{'trans'}[$countertrans]{'item'}[$counter]{'stat +us'} [0]= 'con' || die "Error!!"; $opt->{'trans'}[$countertrans]{'item'}[$counter]{'assi +gnee'}[0] = 'Jill' || die "Error"; print "My cuurent ID is $currentid, $item counter is $ +counter\n"; print "ID is $id\n"; print XMLout($opt); exit 1; } } } print "$trans"; $countertrans++ } print "Nothing"; __DATA__ <opt> <trans kool="max"> <item id="legas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> <trans kool ="kool"> <item id="gas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> </opt>


Thanks in advance,

Maxim

Replies are listed 'Best First'.
Re: Add new elements with XML::Simple
by jeffa (Bishop) on Oct 13, 2004 at 13:40 UTC

    What you have is a pretty specific solution, and as such, is really only useful for you. But, you can indeed trim the code down. Here is one to do that:

    use strict; use warnings; use XML::Simple; use Data::Dumper; my $data = XMLin(\*DATA, ForceArray => 1); for my $trans (@{ $data->{trans} }) { next unless $trans->{kool} eq 'max'; $trans->{item} = [$trans->{item}] unless ref $trans->{item} eq 'AR +RAY'; push @{ $trans->{item} }, { maxis => { assignee => ['jill'], status => ['con'] } }; } print XMLout($data, KeyAttr => 'id'); __DATA__ <opt> <trans kool="max"> <item id="legas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> <trans kool ="kool"> <item id="gas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> </opt>

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      Thanks Jeffa for your other way to trim the code down. I think, I have a problem because how do I know "trans" is the last one of my XML file.

      For example I have in this XML file, the last "trans" is kool:
      <opt> <trans kool="max"> <item id="legas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> <trans kool="sss"> <item id="legas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> <trans kool ="kool"> <item id="gas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> </opt>

      I think I need to use a loop with a counter. What do you think Jeffa?

      Maxim

        First, you hardly ever need an extra counter when you loop in Perl. That is a hold over from other languages, i had the same bad habit myself when i started Perl. Second, since you know which <trans> you want, the one with kool="kool", then you don't need to worry about counting them: just "ask" for the <trans> tag with attribute kool that is set to "kool". Third, use the right tool for the right job. In this case, XML::Twig is the Vorpal Blade you seek:

        use strict; use warnings; use XML::Twig; my $twig = XML::Twig->new(pretty_print => 'indented'); my $data = do {local $/;<DATA>}; $twig->parse($data); my $root = $twig->root; my @trans = $root->children; for my $trans (@trans) { next unless $trans->att('kool') eq 'kool'; my $item = XML::Twig::Elt->new(item => { id => 'maxis'}); my $assignee = XML::Twig::Elt->new(assignee => 'Jill') ->paste(last_child => $item); my $status = XML::Twig::Elt->new(status => 'con') ->paste(last_child => $item); $item->paste(last_child => $trans); } $twig->print; __DATA__ <opt> <trans kool="max"> <item id="legas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> <trans kool ="kool"> <item id="gas"> <status>contacted</status> <assignee>jack</assignee> </item> </trans> </opt>
        See? No need for an extraneous counter, the next unless $trans->att('kool') eq 'kool'; line will skip all <trans> tags that do not have an attribute named kool set to "kool".

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        
Re: Add new elements with XML::Simple
by grantm (Parson) on Oct 15, 2004 at 08:57 UTC

    I'm intrigued by what you expect keyattr => 1 to do.

    Setting KeyAttr to a scalar like this ...

    my $opt = XMLin($xml, KeyAttr => 'kool', forcearray => [ 'trans' ]);

    ... probably ought to throw an exception, but in fact it's an undocumented way to do this ...

    my $opt = XMLin($xml, KeyAttr => [ 'kool' ], forcearray => [ 'trans' ]);

    ... but setting it to '1' makes no sense at all since '1' is not a valid name for an XML attribute. Perhaps what you want to do is turn array folding off altogether, in which case you should set KeyAttr to an empty list:

    my $opt = XMLin($xml, KeyAttr => [ ], forcearray => [ 'trans' ]);