With XML :: Twig, is there a way to find "first_child" with a specific attribute?

I have XML that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<DataSet>
<Category>
   <Name mode="source">somename</Name>
   <Name mode="destination">someothername</Name>
   <Content>Some text here</Content>
</Category>
</DataSet>

      

What I am trying to do is process the "Category" and extract a different name based on the context.

I tried iterating with children

- this works:

use strict;
use warnings;
use XML::Twig;

sub process_category {
    my ( $twig, $category ) = @_;
    my $cat_name;
    foreach my $name ( $category->children('Name') ) {
        if ( $name->att('mode') eq 'source' ) {
            $cat_name = $name->text;
        }
    }

    print "$cat_name ", $category->first_child_text('Content'), "\n";
}

my $twig =
    XML::Twig->new( twig_handlers => { 'Category' => \&process_category } )
    ->parse( \*DATA );


__DATA__
<?xml version="1.0" encoding="UTF-8"?>
<DataSet>
<Category>
   <Name mode="source">somename</Name>
   <Name mode="destination">someothername</Name>
   <Content>Some Text</Content>
</Category>
</DataSet>

      

However, I'm wondering - is there a better way than iterating over elements? I can't figure out if first_child

attribute lookup supports or if there is another method that does the same.

+3


source to share


2 answers


Use the XML :: Twig method get_xpath

to find matching values ​​in attributes. For example:

my $cat_name = $category->get_xpath('./Name[@mode="source"]', 0)->text;

      



By default, get_xpath returns an array. By passing "0", only the first element of the array is passed (this is what you want, and most likely there will only be one match). Then the text is stretched with ->text

. Use this and you can remove the for loop.

+4


source


You can pass code_ref to first_child

. This sub is passed to each element in turn, and if it returns "true" then the method first_child

matches. (And then doesn't keep looking.)

So this should do the trick:



use strict;
use warnings;
use XML::Twig;

sub is_name_source {
    my ($element) = @_;

    print $element ->tag, "\n";
    if (    $element->tag eq 'Name'
        and $element->att('mode') eq 'source' )
    {
        return 1;
    }
}

sub process_category {
    my ( $twig, $category ) = @_;
    my $cat_name = $category->first_child( \&is_name_source )->text;
    print "$cat_name ", $category->first_child_text('Content'), "\n";
}

my $twig =
    XML::Twig->new( twig_handlers => { 'Category' => \&process_category } )
    ->parse( \*DATA );


__DATA__
<?xml version="1.0" encoding="UTF-8"?>
<DataSet>
<Category>
   <Name mode="source">somename</Name>
   <Name mode="destination">someothername</Name>
   <Content>Some Text</Content>
</Category>
</DataSet>

      

You could of course include inline is_name_source

with an anonymous sub. It's a matter of taste.

0


source







All Articles