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

I am using XML::Parser as part of a subroutine to find a specific element and attribute. I have run up against the warning "Variable "$targetAttribute" will not stay shared at ./parser.pl line 35 (#1)". From reading the fabulous perlmonk archives (and the diagnostics) I realize that the reason for this warning is that the nested sub I have written is visible globally and perl can't know where to bind names. The suggested fix is an anonymous sub - but, given that I need to pass a reference to this nested subroutine - it's a handler for events generated by the parsefile method - as an option to the XML::Parser object, I don't know how (or even if I can) use an anonymous sub. Any suggestions?
use strict; use warnings; use diagnostics; my $historyFile = 'gsHistToMassSto'; my $doneFlag = findTargetAttribute('last','WorkFlowStatus','status',$h +istoryFile); print "My DONE flag = $doneFlag\n"; eval {my $result = findTargetAttribute('first','WorkFlow','status',$hi +storyFile); print "My result is $result\n";}; sub findTargetAttribute{ use XML::Parser; my ($location,$targetElement,$targetAttribute,$file) = @_; my $parser = XML::Parser->new(Handlers => {Start => \&handle_start}) +; my @results; print "$location, $targetElement, $targetAttribute, $file\n"; $parser->parsefile($file); if($location eq 'first'){return $results[0]} if($location eq 'last'){return $results[-1]} else {return @results} sub handle_start { my($expat, $element, %attrs) = @_; if($element eq $targetElement){ print "Target = $targetElement\n"; if(%attrs){ while(my($key, $value) = each(%attrs)){ if($key eq $targetAttribute){push @results,$value;} } } } } }

Replies are listed 'Best First'.
Re: nested subroutines
by Elian (Parson) on Jun 19, 2003 at 19:19 UTC
    You can. The code would look like:
    my $sub_handle = sub { my($expat, $element, %attrs) = @_; if($element eq $targetElement){ print "Target = $targetElement\n"; if(%attrs){ while(my($key, $value) = each(%attrs)){ if($key eq $targetAttribute){push @results,$value;} } } } } my $parser = XML::Parser->new(Handlers => {Start => $sub_handle}); my @results; print "$location, $targetElement, $targetAttribute, $file\n"; $parser->parsefile($file); if($location eq 'first'){return $results[0]} if($location eq 'last'){return $results[-1]} else {return @results}
    (Note the reordering there) sub with no name creates an anonymous sub and returns a reference, so just do that first and use the reference it returns rather than taking a reference to a named sub.
Re: nested subroutines
by Zaxo (Archbishop) on Jun 19, 2003 at 20:31 UTC

    Your nested sub creates a closure by its comparisons with $targetElement and $targetAttribute. Each time handle_start() is run, it will use those values as they were the first time findTargetAttribute() was run, as if preserved in amber. Here is a little example of this:

    $ perl -e'sub foo { my ($x,$y)=@_; sub bar {print "bar",$x,$y,$/} $x.$ +y} bar(); foo qw(baz quux); bar(); foo qw(oof zab); bar()' bar barbazquux barbazquux $

    That is likely to give you "surprising" behavior at some point. Closures could be helpful with this problem, but accidental closures are rarely useful.

    Here's a variation which may be more useful, binding the values from the most recent execution of foo():

    $ perl -e'{ my($x,$y); sub foo { ($x,$y)=@_;$x.$y} sub bar{print "bar" +,$x,$y,$/} } bar(); foo qw(baz quux); bar(); foo qw(oof zab); bar()' bar barbazquux baroofzab $
    That might actually be handy for a handler.

    Such constructions are all mystical tricks, and VB'ers in maintainance are bound to screw them up.

    After Compline,
    Zaxo

Re: nested subroutines
by marknm (Acolyte) on Jun 19, 2003 at 19:55 UTC
    Thanks, Elian - that's exactly what I needed to know. And Nkuvu, it was suggested to me by one of my colleagues that I just make it another sub (not nested). I'm think that would work, but 1) I always want to learn something new and 2) I wanted to make this sub more cohesive and less coupled to the rest of the script, just because that's the way I best understand code.
      That's perfectly understandable, and commendable. I just wasn't sure if it was a requirement on the part of the code, or the module, or your own style...
Re: nested subroutines
by Nkuvu (Priest) on Jun 19, 2003 at 19:22 UTC
    Is there any reason you need this to be a nested sub? What's wrong with making it a normal sub and calling it from within the findTargetAttribute sub? Note that this is a real question -- I'm failing to see a reason this couldn't be done... unless perhaps it has something to do with scoping issues.