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

I have written a log error parser using XML::Twig (of course, the logs are in XML) where the status message is at the same level as the request data. When I write a TwigHandler for the section of data that contains an error, I need to retrieve my parent's sibling (my uncle) so that I can pass the offending request along with the error. When I try to access that in my sub-routine, the elements for my parent and grandparent exist, but my parent's sibling does not. An example will be better: The XML:
<log> <msg> <error name="ID not found" /> <request name="myRequest"/> </msg> </log>
A snippet that is *like* my code:
# Begin code my $handler = { 'log/msg/error' => \&mysub }; my $twig = Twig->new( TwigRoots => { 'log/msg' => 1 }, TwigHandlers => $handler }); # The handler should only be invoked when an error exists sub mysub { my ($twig, $elt) = @_; $elt->parent()->parent()->print; $twig->purge; } #End code
The output of this is:
<log><msg><error name="ID not found" /></msg></log>
No matter what I do, I can't see the "request" that is part of the original TwigRoot. I have tried using parent()->sibling("request") and other things, such as parent->parent->next_child("request"). It appears that only the sub-elements are passed in to $twig. Good thoughts are appreciated... DrSax

Replies are listed 'Best First'.
Re: XML::Twig - can't find my uncle!
by merlyn (Sage) on May 23, 2001 at 03:17 UTC

      OK, let's clear some misconceptions about XML::Twig with some explanations and a bit of code (I'll add this to the doc of the module for next version):

      • if you are using twig_roots then the tree will be composed only of the document root, the twig_roots elements and all of their sub-elements,
      • twig_handlers on the other hands do not impact the way the tree is built, the handler is called as soon as the element is completely parsed, and the entire tree that has been built previously, including all of the element sub-elements is accessible...
      • except when when you use either flush or purge: in this case the module will delete as much of the tree as can possibly be, which means that only the direct ancestors of the current element will remain, each with one single child.

      Now for something completely similar:

      #!/bin/perl -w use strict; use XML::Twig; my $doc= '<doc> <l1> <l2> <e1>e1</e1> <e2>e2</e2> <e3>e3</e3> </l2> </l1> </doc>'; my $twig= new XML::Twig( twig_roots => { l2 => 1 } twig_handlers => { e2 => \&e2} ); $twig->parse( $doc); sub e2 { my( $t, $e2)= @_; print "ancestors: "; print map { $_->gi . " " } $e2->ancestors; # print l2 doc print "\n"; print "siblings: "; print $e2->prev_sibling->gi; # print e1 print " no next sibling\n" unless $e2->next_sibling; # e3 is not t +here yet print "purge!\n"; $t->purge; print "ancestors: "; print map { $_->gi . " " } $e2->ancestors; # print l2 doc (still + there) print "\n"; print "siblings: "; print " no prev sibling\n" unless $e2->prev_sibling; # e1 is gone print " no next sibling\n" unless $e2->next_sibling; # e3 is still + not there }
      Randal,

      I was afraid that what you say is sooth, but then I noticed all the ancestral things available to Elt's and figured I'd give it a go.
      I may have to look at XML::XPath, but I liked the ease of use on Twig. I will also see if mirod can dig me out.
      Thanks for responding!
      DrSax
Re: XML::Twig - can't find my uncle!
by mirod (Canon) on May 23, 2001 at 16:03 UTC

    I am sorry I cannot answer right now (I am at XML Europe and web access is pretty difficult here, all of 4 machines from xml.com).

    Maybe the problem is that when you purge the twig the uncles disappear. Or that anything outside the twig_roots is not part of the twig (it is not loaded at all).

    Sorry for the lack of support, just send me an email next week and I will have a look at it.

    Update: OK, we're lucky, I managed to grab a machine for a couple of minutes, so here it is:

    Your code does not work because when you set a handler on /log/msg/error it is called just after the end tag for error has been parsed. At this point the request element has not yet been parsed.

    From the handler on /log/msg/error you can access the element previous siblings, but not the next ones, they have not been parsed yet.

    The easiest solution is probably to put a handler on /log/msg and from there test whether there is an error child. At this point the entire msg element will have been parsed and you will be able to access all of its children.

    Good question though, I'll make sure it makes it into the FAQ.

Re: XML::Twig - can't find my uncle!
by aardvark (Pilgrim) on May 23, 2001 at 04:21 UTC
    I hate unnecessary attributes. I try to use them only when it refines the meaning of the value of the element. If you create your xml with element values, I think you will have an easier time getting to them. Like this:
    <log> <msg> <error> ID not found </error> <request> myRequest </request> </msg> </log>
    But sometimes we just have to deal with the hand we are dealt. I found a way to get at your attribute values by registering two TwigHandlers. I'm sure mirod has a much more elegent solution than this:
    use strict; use XML::Twig; my $file = 'uncle.xml'; my $twig = new XML::Twig( TwigHandlers => { 'log/msg/error' => \&print_error, 'log/msg/request' => \&print_request, }); $twig->parsefile($file) or die "can't parse $!\n"; sub print_error { my ($t, $elt) = @_; print "ERROR = " . $elt->att('name') . "\n"; } sub print_request { my ($t, $elt) = @_; print "REQUEST = " . $elt->att('name') . "\n"; }

    Get Strong Together!!

      aardvark,
      Thanks for looking in to this. As it turns out, I need to get the request only when there is an error and i need to co-relate these, so the code you are not seeing in my piddling example creates a nice structure to process, allowing requests to be repaired and re-submitted from a Web page.
      The print_request subroutine will fire for all requests, I believe, which leads to behavior that I don't want. I will see if mirod responds with something.
      Again, thanks for your time,
      DrSax
        ok, how about this?
        use strict; use XML::Twig; my $file = 'uncle.xml'; my $twig = new XML::Twig; $twig->parsefile($file); my $root = $twig->root; my @messages = $root->children; foreach my $msg (@messages) { my $error = $msg->first_child('error'); my $result = $msg->first_child('request'); my $error_name = $error->att('name'); my $result_name = $result->att('name'); if ($error_name && $result_name) { print "ERROR : $error_name \n"; print "RESULT : $result_name \n"; } }
        Get Strong Together!!
Re: XML::Twig - can't find my uncle!
by traveler (Parson) on May 23, 2001 at 02:01 UTC
    I cannot get AxKit to compile on the only machine I can easily get to, but, IIRC Twig sees request not as your parent's sibling, but as your sibling. Just try $elt->sibling('request')->print; (or maybe you need a $elt->twig->sibling('request')->print;). Sorry I can't test this right now, but maybe it will get you on the right track.