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

Hi,

I have a/an xml file with certain config info in it. I want to parse the xml file, search for the tag that contains the attribute that matches what I entered as param1 on the command line, and then return the contents of ALL the act tags associated with the matched header.

I have been working on this for a bit and have come up with the following xml file and code.

Looking at the code I feel like it belongs in the Obfuscation section though. Is there a simpler way of returning an array..? or $var for each match? I have to do something with each var anyway so which ever is easiest to maintain and read.

The xml file - Test.xml:
<?xml version="1.0"?> <!-- This is XML for the Header Mapping --> <map> <header name="ABCstatus"> <act>WTO</act> </header> <header name="ABCevent"> <act>WTO</act> <act>Call</act> </header> <header name="DEF-Call"> <act>Call</act> </header> <header name="Unknown"> <act>WTO</act> <act>SMS</act> <act>NetSend</act> </header> </map>

The hmmm don't even know what to call this code:

#! perl.exe use strict; use warnings; use XML::Simple; use Data::Dumper; my ($config, $head); unless ($head = $ARGV[0]) { my $str = "Usage: $0 name"; my $line = "-" x length($str); print "\n"; print "$str\n"; print "$line\n"; print "\n"; print "Setting Header as Unknown\n"; $head = "Unknown"; } my $file = "./Test.xml"; my $xs1 = XML::Simple->new(); unless ($config = $xs1->XMLin($file, forcearray => 1)) { print "Could NOT read $file IN:$!\n"; exit(); } my $hashrc = 0; print "\n"; #So I can see the structure while testing print Dumper($config); print "\n=======================================================\n"; print "Header = $head\n"; foreach my $key (keys (%{$config->{header}})) { my $tempkey = $config->{header}->{$key}->{'name'} . $key; print "\nTesting \$tempkey = $tempkey\n"; if ("$head" eq $config->{header}->{$key}->{'name'} . $key) { $hashrc = 1; foreach my $mapping (@{$config->{header}->{$head}->{act}}) { print "\$mapping = $mapping\n"; } last; } } unless ($hashrc) { print "$head is not currently mapped\n"; }

Many thanks. -----
Of all the things I've lost in my life, its my mind I miss the most.

Replies are listed 'Best First'.
Re: XML::Simple parsing
by mirod (Canon) on Oct 23, 2002 at 14:59 UTC

    Did you think you could avoid the inevitable? Here is how someone from New Hampshire would solve this using XML::Twig! I guess grantm will have more on the way you use XML::Simple ;--)

    XML::Twig is quite appropriate here because it lets you specify quite easily that you only want to process header elements with a specific attribute name, and it will only load the relevant part of the document, instead of the entire thing.

    #!/usr/bin/perl -w -l use strict; use XML::Twig; my $name= live_free() or die; my $t= XML::Twig->new( twig_roots => { qq{header[\@name="$name"]} => \&print_content + }, ); $t->parse( \*DATA); sub print_content { my( $t, $header)= @_; print join( "\n", map { $_->text } $header->children); } sub live_free { return shift @ARGV; } __DATA__ <?xml version="1.0"?> <!-- This is XML for the Header Mapping --> <map> <header name="ABCstatus"> <act>WTO</act> </header> <header name="ABCevent"> <act>WTO</act> <act>Call</act> </header> <header name="DEF-Call"> <act>Call</act> </header> <header name="Unknown"> <act>WTO</act> <act>SMS</act> <act>NetSend</act> </header> </map>

      hmmm..I like the look of this. Unfortunately, I am running Windows and ActiveState and the XML::Twig module I could find is for Linux and Solaris OSes.

      Now if I could find this for W2k..;)

      -----
      Of all the things I've lost in my life, its my mind I miss the most.

        There are many ways to install a module beyond ppm:

        • If you have CPAN (or even better CPANPlus) installed you can use it
        • if you have nmake installed you can follow this link to XML::Twig and download it from CPAN, do the magic incantation :
          tar zxvf XML-Twig-3.08.tar.gz cd XML::Twig-3.08 perl Makefile.PL make make test su (is it called su on windows?) make install
        • as XML::Twig is pure Perl you can also just download it, un-tar it and copy Twig.pm in your Perl tree, in the same directory as XML::Parser (something like C:\Perl\site\lib\xml)
Re: XML::Simple parsing
by grantm (Parson) on Oct 24, 2002 at 01:02 UTC

    In the event that mirod has not managed to convert you to XML::Twig :-) then here's one code snippet to do what (I think) you want:

    #!/usr/bin/perl use strict; use warnings; use XML::Simple; my $file = './Test.xml'; my $head = $ARGV[0] || 'Unknown'; my $map = XMLin($file, forcearray => ['header', 'act'], keyattr => { h +eader => 'name'}); my $header = $map->{header}->{$head}; if($header) { foreach my $act ( @{$header->{act}} ) { print "act: $act\n"; } } else { print "No match for '$head'\n"; }

    Alternatively, you could iterate through the <header> items by using the keyattr option to turn off array folding:

    my $map = XMLin($file, forcearray => ['header', 'act'], keyattr => {}) +; my $headers = $map->{header}; foreach my $header (@$headers) { if($header->{name} eq $head) { foreach my $act ( @{$header->{act}} ) { print "act: $act\n"; } } }

    By the way, your original code included this logic:

    unless ($config = $xs1->XMLin($file, forcearray => 1)) { print "Could NOT read $file IN:$!\n"; exit(); }

    That won't actually work, since if XML::Simple can't parse the XML it will call 'die' rather than returning a false value. If you want to trap errors, you'd need to wrap the call to XMLin() in an eval block and check $@ afterwards.