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

Hello all,
I wrote a script to condense a very large XML file with XML::Twig on ActivePerl 5.6.1 - worked great! I upgraded to ActivePerl 5.8.8 and it broke with
"Can't call method "flush" on an undefined value at C:/Perl/site/lib/X +ML/Twig.pm line 6832, <INFILE> line 4083."
Here is the offending section, it breaks with the first flush():
select \*OUTFILE; my $t = new XML::Twig( twig_roots => { # 'MemberID' => \&member, 'Aging' => \&balance, 'Transaction' => \&transaction +, 'Line' => \&line, 'Chits' => \&chits}, pretty_print => 'indented', twig_print_outside_roots => \*OUTFILE, + # print the rest ); $t -> parsefile ("temp.xml"); <snip> sub balance { my ($t, $balance) = @_ ; my %balance; $balance{30} = $balance->field( 'BAL_30' ); $balance{60} = $balance->field( 'BAL_60' ); $balance{90} = $balance->field( 'BAL_90' ); $balance{120} = $balance->field( 'BAL_120' ); $balance{150} = $balance->field( 'BAL_150' ); $balance{180} = $balance->field( 'BAL_180' ); local ($balance90, $balance120, $balance150, $balance180); $balance30 = $balance{30}; $balance60 = $balance{60}; $balance90 = $balance{90}; $balance120 = $balance{120}; $balance150 = $balance{150}; $balance180 = $balance{180}; local $balancelate = ($balance90 + $balance120 + $balance150 + $ba +lance180); $balancelate =$rounder->round($balancelate); $balance->delete(90); $balance->delete(120); $balance->delete(150); $balance->delete(180); my $balance30elt = new XML::Twig::Elt('BAL_30', $balance30); $balance30elt->set_text($balance30); $balance30elt->set_att(Period => 1); $balance30elt->paste('first_child', $balance); my $balance60elt = new XML::Twig::Elt('BAL_60', $balance60); $balance60elt->set_text($balance60); $balance60elt->set_att(Period => 2); $balance60elt->paste('last_child', $balance); my $balance90 = new XML::Twig::Elt('BAL_90', "$balancelate"); $balance90->set_text( "$balancelate" ); $balance90->set_att(Period => 3); $balance90->paste('last_child', $balance); $balance -> flush(\*OUTFILE); }
I want to use WxPerl to put a nice Windows wrapper around it, so I need the 5.8.x. Any help or insight would be greatly appreciated! -Mike

Edited by planetscape - removed pre tags, replaced with code tags

Replies are listed 'Best First'.
Re: XML-Twig - AS5.6.1 vs AS 5.8.8
by runrig (Abbot) on Mar 29, 2006 at 00:34 UTC
    $element->delete() does not take an argument, it deletes the element itself, I think you intend for those deletes to be more like (this takes care of all in one go):
    # Delete BAL_* nodes $balance->cut_children(qr/^BAL_\d+$/);
    Note also that you are not deleting the BAL_30 and BAL_60 nodes, so you end up with two each of those (the originals and the ones you create with attributes). The cut_children() above assumes you didn't want the duplicates.
Re: XML-Twig - AS5.6.1 vs AS 5.8.8
by mirod (Canon) on Mar 29, 2006 at 08:09 UTC

    There are a few problems with your code:

    • first, as mentionned by runrig, you delete $balance and then you flush it. So $balance is deleted, and becomes undef-ed, so you can't call a method on it (are you sure this code worked in 5.6.1?)
    • you do a couple of times things like this:
      my $balance30elt = new XML::Twig::Elt('BAL_30', $balance30); $balance30elt->set_text($balance30); $balance30elt->set_att(Period => 1); $balance30elt->paste('first_child', $balance);
      The set_text is completely useless (you set the text in the new above, and BTW new XML::Twig::Elt is a deprecated way of calling a method, XML::Twig::Elt->new is the proper way.
      In any case, you can replace all those lines by the more compact
      $balance->insert_new_elt( first_child, # position 'BAL_30', # tag { Period => 1 }, # attributes $balance, # content );
      And actually it looks like you don't even need to delete then re-create the BAL_30 (and BAL_60) element. You could just do:
      $balance->first_child( 'BAL_30') # get the element ->del_atts # if it had atts originally ->set_att( Period => 1) ;

    Does it make sense?

      Yes, it does. Thanks, Michael. I implemented the method calls as you suggested, and it works, except for one small thing - the first time $balance->flush; is called, it writes a <Root> tag immediately preceding the <Aging> tag. As you can surmise, I'm processing a member billing file, and it doesn't write the <Root> tag on subsequent members, just the first one. I can hack my way around it, but I don't understand why it is happening. Thanks. -Mike
        A minimal test case with data would be helpful. Do you mean that each Aging element is a child of a different Root element? And that the parent element of your entire document is some other-named sort of "root" element? Or that all of your Aging elements are children of one Root element (in which case it sounds like it's behaving correctly)?