1

I have simple XML that I want to read in Perl and make hash containing all read keys.

Consider this code:

my $content = $xml->XMLin("filesmap.xml")->{Item};
my %files = map { $_->{Path} => 1 } @$content;

This snippet works great when XML file contains many Item tags. Then $content is a reference to array. But when there is only one Item I get an error when dereferencing as array. My assumption is that $content is a reference to the scalar, not array.

What is the practice to make sure I get array of values read from XML?

Bartek Kobyłecki
  • 2,365
  • 14
  • 24
  • 2
    First: [Don't use XML::Simple](/http://stackoverflow.com/questions/33267765/why-is-xmlsimple-discouraged) and then it's all nice and easy. Give us an XML Sample, and we'll be able to give you a precise answer. – Sobrique Dec 08 '15 at 15:09
  • Appeared posts already answer my question. In general there is a practice to use `ref $content eq 'ARRAY'` where I need to make sure I get arrays, which is ugly by the way, but in my XML case I can use ForceArray parameter or use another xml lib. Thanks! – Bartek Kobyłecki Dec 08 '15 at 15:30

3 Answers3

8

What you need is to not use XML::Simple and then it's really trivial. My favourite for fairly straightforward XML samples is XML::Twig

use XML::Twig;
my $twig = XML::Twig -> new -> parsefile ( 'filesmap.xml' ); 

my @files = map { $_ -> trimmed_text } $twig -> get_xpath ( '//Path' ); 

With a more detailed XML sample (and desired result) I'll be able to give you a better answer.

Part of the problem with XML::Simple is it tries to turn an XML data structure into perl data structures, and - because hashes are key-values and unordered but arrays are ordered, it has to guess. And sometimes it does it wrong, and other times it's inconsistent.

If you want it to be consistent, you can set:

my $xml = XMLin( "filesmap.xml", ForceArray => 1, KeyAttr => [], ForceContent => 1 );

But really - XML::Simple is just a route to pain. Don't use it. If you don't like XML::Twig, try XML::LibXML instead.

Community
  • 1
  • 1
Sobrique
  • 52,974
  • 7
  • 60
  • 101
1

What I would say you need is a flatten-ing step.

my %files 
    = map { $_->{Path} => 1 } 
      # flatten...
      map { ref() eq 'ARRAY' ? @$_ : $_ } 
      $xml->XMLin("filesmap.xml")->{Item}
    ;
Axeman
  • 29,660
  • 2
  • 47
  • 102
1

You can do a check and force the return into an array reference if necessary:

my $content = $xml->XMLin("filesmap.xml")->{Item};

$content = ref $content eq 'ARRAY'
    ? $content
    : [$content];
stevieb
  • 9,065
  • 3
  • 26
  • 36