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

Dear community, I have an xml file which was converted into a hash variable via XML::Simple.
use XML::Simple qw(:strict); $config = XMLin('config.xml', KeyAttr => {item=>'filename'}, ForceArra +y => ['item']);
My xml file looks like:
<?xml version="1.0" encoding="UTF-8"?> <config> <item> <id>0</id> <filename>AMEX</filename> <destination_dir>test</destination_dir> </item> <item> <id>1</id> <filename>bla</filename> <destination_dir>test1</destination_dir> </item> <item> <id>2</id> <filename>alb</filename> <destination_dir>test2</destination_dir> </item> </config>

the problem is, that I must have 2 possibilities to access the information in the <item>

The first case is to access a single item by its filename like: print $config->{item}{"bla"}->{destination_dir};
It works fine!
The second case ist, to go through the whole config (item after item) and here I need to access each single item by an index like:
for (my $i = 0; $i < $theNumberOfItems; $i++) { print $config->{item}[$i]->{destination_dir}; }

But if I try this, I get an error: Not an ARRAY reference at test.pl line 18.

I try to make the <id> to another key: $config = XMLin('config.xml', KeyAttr => {item=>'filename', item=>'id'}, ForceArray => [ 'item']); and then to access like this:
for (my $i = 0; $i < $theNumberOfItems; $i++) { print $config->{item}{$i}->{destination_dir}; }

It workes fine, but now the access via filename is impossible. I suspect, that it is not allowed to have two of KeyAttr and the last one overwrites the filename...

Is there any possibility to access nevertheless on both ways?

Thanks a lot!
Dimitri

Replies are listed 'Best First'.
Re: How do I access values in a hash by two different keys?
by hdb (Monsignor) on Jul 19, 2013 at 08:51 UTC

    If you keep filename as your key, you could write your loop as

    for my $i ( keys %{ $config->{item} } ) { print $config->{item}->{$i}->{id}, ": "; print $config->{item}->{$i}->{destination_dir}, "\n"; }

    If you need it sorted by id, it goes like this:

    for my $i (sort { $config->{item}->{$a}->{id} <=> $config->{item}->{$b +}->{id} } keys %{ $config->{item} } ) { print $config->{item}->{$i}->{id}, ": "; print $config->{item}->{$i}->{destination_dir}, "\n"; }
      It is a very good solution, thank you!
      But I have a question: Why this code works
      for my $i ( keys %{ $config->{item} } ) { print $config->{item}->{$i}->{destination_dir}; }
      and this one not?
      for (my $i=0; $i < 4; $i++ ) { print $config->{item}->{$i}->{destination_dir}; }
      I mean, it is nearly the same... I just say how often the loop should go and then I access via the index {$i}. Or not?
      Even print $config->{item}->{1}->{destination_dir}; this one doesn't work...
      I just want to understand. Thank you again!

        Have a look at your data structure using Data::Dumper:

        use Data::Dumper; print Dumper $config; gives you $VAR1 = { 'item' => { 'AMEX' => { 'destination_dir' => 'test', 'id' => 0 }, 'bla' => { 'destination_dir' => 'test1', 'id' => 1 }, 'alb' => { 'destination_dir' => 'test2', 'id' => 2 } } };

        So $config->{item} represents a reference to a hash with keys "AMEX", "bla", and "alb". If you try $config->{item}->{1}->{destination_dir}; you are using a key that does not exist, the string "1".

        UPDATE: I probably should state explicitely that there is no numeric index in a hash and that there is no defined order of items in a hash that you can rely on.

Re: How do I access values in a hash by two different keys?
by kcott (Archbishop) on Jul 19, 2013 at 10:01 UTC

    G'day Dimitri,

    The fact that accessing $config->{item} with a hash key worked (i.e. $config->{item}{"bla"}), while accessing it with an array index gave an error (i.e. $config->{item}[$i]), should have provided a huge clue as to what was going on. Regardless, in situations where you are encountering these sorts of difficulties, it's quite useful to look at the data structure that XML::Simple is generating (e.g. with Data::Dumper):

    $ perl -Mstrict -Mwarnings -E ' use Data::Dumper; use XML::Simple qw{:strict}; my $xml = <<EOX; <?xml version="1.0" encoding="UTF-8"?> <config> <item> <id>0</id> <filename>AMEX</filename> <destination_dir>test</destination_dir> </item> <item> <id>1</id> <filename>bla</filename> <destination_dir>test1</destination_dir> </item> <item> <id>2</id> <filename>alb</filename> <destination_dir>test2</destination_dir> </item> </config> EOX my $config = XMLin($xml => KeyAttr => {item=>"filename"}, ForceArr +ay => ["item"]); print Dumper $config; ' $VAR1 = { 'item' => { 'alb' => { 'id' => '2', 'destination_dir' => 'test2' }, 'AMEX' => { 'destination_dir' => 'test', 'id' => '0' }, 'bla' => { 'id' => '1', 'destination_dir' => 'test1' } } };

    Armed with that information, it should now be easy to extract the data you want:

    $ perl -Mstrict -Mwarnings -E ' use XML::Simple qw{:strict}; my $xml = <<EOX; <?xml version="1.0" encoding="UTF-8"?> <config> <item> <id>0</id> <filename>AMEX</filename> <destination_dir>test</destination_dir> </item> <item> <id>1</id> <filename>bla</filename> <destination_dir>test1</destination_dir> </item> <item> <id>2</id> <filename>alb</filename> <destination_dir>test2</destination_dir> </item> </config> EOX my $config = XMLin($xml => KeyAttr => {item=>"filename"}, ForceArr +ay => ["item"]); say "*** bla destination ***"; say $config->{item}{bla}{destination_dir}; say "*** all destinations ***"; for (keys %{$config->{item}}) { say "$_: ", $config->{item}{$_}{destination_dir}; } ' *** bla destination *** test1 *** all destinations *** alb: test2 AMEX: test bla: test1

    If you need to change the options to XMLin, continue to use Data::Dumper (or your preferred equivalent) to see the effects of those option changes.

    -- Ken

Re: How do I access values in a hash by two different keys?
by Loops (Curate) on Jul 19, 2013 at 08:50 UTC

    If you don't require random access, you can iterate over all the items without needing to use an index.

    foreach my $item (values $config->{item}) { print $item->{destination_dir}; }
Re: How do I access values in a hash by two different keys? (XML DOM)
by Anonymous Monk on Jul 19, 2013 at 09:32 UTC

    Is there any possibility to access nevertheless on both ways?

    Yeah, forget XML::Simple, just stick with the DOM, and use xpath

    See all the links in Re: Retrieve select information from HTML, the links they link , the parent nodes (for other details ), and you can use ->F or ->findnodes with these paths

    ## for each item $ xmllint.exe --xpath " /config/item/destination_dir " config.xml <destination_dir>test</destination_dir><destination_dir>test1</destina +tion_dir><destination_dir>test2</destination_dir> ## first item only $ xmllint.exe --xpath " /config/item[1]/destination_dir " config.xml <destination_dir>test</destination_dir> ## item with filename 'bla' $ xmllint.exe --xpath " /config/item[ filename='bla']/destination_dir +" config.xml <destination_dir>test1</destination_dir>