0

I'm reading in an xml file,

$myxml = XMLin("$configfile");

And when I print it using Dumper (print Dumper($myxml);) I get this...

$VAR1 = {
          'Timeout' => 5,
          'Roots' => {
                        'Root' => [
                                     {
                                       'Name' => 'Sales',
                                       'Level' => 'Indeterminate',
                                       'Profiles' => {
                                                    'Profile' => [
                                                                {
                                                                  'Name' => 'Bill',
                                                                  'Age' => '50',
                                                                  'Status' => Active
                                                                },
                                                                {
                                                                  'Name' => 'Bob',
                                                                  'Age' => '24',
                                                                  'Status' => Inactive
                                                                }
                                                              ]
                                                  },
                                       'Interval' => 'Order',
                                       'Action' => 'Reject'
                                     },
                                     {
                                      'Name' => 'User',
                                      'Level' => 'Indeterminate',
                                      'Profiles' => {
                                                   'Profile' => [
                                                            {
                                                              'Name' => 'User',
                                                              'Action' => 'Reject',
                                                              'User' => 'acount'
                                                             }, 
                                                            {
                                                              'Name' => 'Admin',
                                                              'Action' => 'Accept',
                                                              'User' => 'acount'
                                                             },                                                                   
                                   ]
                      }
        };    

I'd like to read this hash and get the value of all inactive 'Status' or or get 'Bob's Status..

{
'Name' => 'Bob',
'Age' => '24',
'Status' => Inactive
}

Start Edit:

So to get profile information for one person..

Dumper($myxml->{'Roots'}->{'Root'}[0]{'Profiles'}{'Profile'}[2]); 

For example to get the Status for Bob

if ($myxml->{'Roots'}->{'Root'}[0]{'Profiles'}{'Profile'}[1]{'Name'} eq "Bob") {
$status = $myxml->{'Roots'}->{'Root'}[0]{'Profiles'}{'Profile'}[1]{'Status'};
}

However, how do I loop through this xml so it will keep checking {'Roots'}->{'Root'} and {'Profiles'}{'Profile'} in case Bob is not in location [0] and [1]. A double foreach loop?

End Edit

I've included an example of the xml..

<Root Name="Sales" Level="Indeterminate" Profile="Order" Interval="Order" Action="Reject">
  <Profiles>
    <Profile Name="Bill" Age="50" Status=Active />
    <Profile Name="Bob" Age="24" Status=InActive />
    <Profile Name="Ben" Age="45" Status=Active />
  </Profiles>
</Root>

Which produces this:

$VAR1 = {
      'Name' => 'Sales',
      'Type' => 'Indeterminate',
      'Profiles' => {
                   'Profile' => [

                               {
                                 'Name' => 'Bill',
                                 'Age' => '50',
                                 'Status' => Active
                               },
                               {
                                 'Name' => 'Bob',
                                 'Age' => '24',
                                 'Status' => InActive
                               },
                               {
                                 'Name' => 'Ben',
                                 'Age' => '45',
                                 'Status' => Active
                               }
                             ]
                 },
      'Interval' => 'Order',
      'Action' => 'Reject'
    };

Thanks,

John.

John
  • 787
  • 4
  • 11
  • 28
  • 1
    If you give us some sample XML, we can show you a MUCH simpler solution. I've made a guess at what your source XML looks like to illustrate the concept. – Sobrique Apr 08 '16 at 14:50

3 Answers3

2

You've an answer how to do this with XML::Simple already. But I'd suggest not, and use XML::Twig instead, which is MUCH less nasty.

Why is XML::Simple "Discouraged"?

I'm going to assume that your XML looks a bit like this:

<opt Timeout="5">
  <Roots>
    <Root Action="Reject" Interval="Order" Level="Indeterminate" Name="Sales">
      <Profiles>
        <Profile Age="50" Name="Bill" Status="Active" />
        <Profile Age="24" Name="Bob" Status="Inactive" />
      </Profiles>
    </Root>
  </Roots>
</opt>

I can't tell for sure, because that's the joy of XML::Simple. But:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig -> new -> parsefile ( $configfile );

print $twig -> get_xpath ( '//Profile[@Name="Bob"]',0 ) -> att('Status')

This uses xpath to locate the attribute you desire - // denotes a 'anywhere in tree' search.

But you could instead:

print $twig -> get_xpath ( '/opt/Roots/Root/Profiles/Profile[@Name="Bob"]',0 ) -> att('Status')

Much simpler wouldn't you agree?

Or iterate all the 'Profiles':

foreach my $profile ( $twig -> get_xpath ('//Profile' ) ) {
    print $profile -> att('Name'), " => ", $profile -> att('Status'),"\n";
}
Community
  • 1
  • 1
Sobrique
  • 52,974
  • 7
  • 60
  • 101
0

The value of Root is an ArrayRef. You are ignoring the array and treating it as if it was one of the HashRefs that are inside it. You need to loop over the array or access it with a specific index.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
0

This is one of the reasons that XML::Simple is strongly deprecated - occasionally it will throw in an array reference when you expect a hash reference.

You can see that the value of $rulesxml->{'Roots'}->{'Root'} is an array ref, not a hash ref as it starts with a [ not a {.

If the data is exactly what you show here, then all you need to do is to insert an array look-up in your code.

Dumper($myxml->{'Roots'}->{'Root'}->[0]->{'Profiles'}->{'Profile'}); 

I've used 0 as there is (currently?) only one element in that array. If your data is more complicated and you have multiple elements in the array, then you will need to use a loop.

For work like this, I'd strongly recommend taking the time to learn XPath and using a module which supports that (I like XML::LibXML).

A couple of other ways to simplify your code:

  1. You quote the variable in the XMLin() call unnecessarily.

    $myxml = XMLin($configfile); # Works fine without quotes

  2. You can omit intermediate arrows in multi-level look-ups.

    $myxml->{'Roots'}{'Root'}[0]{'Profiles'}{'Profile'}

  3. You can also omit most quotes around hash key names.

    $myxml->{Roots}{Root}[0]{Profiles}{Profile}

Update: I'm loathe to give you the solution to your added question because (as we never tire of pointing out here) XML::Simple is a terrible solution to most XML problems.

But, given your data structure (or rather, a version of your data structure that I have cleaned up so it actually compiles!) this is how you would traverse it (yes, it's a nested loop).

for my $root (@{ $myxml->{Roots}{Root} }) {
  for my $profile (@{ $root->{Profiles}{Profile} }) {
    if ($profile->{Name} eq 'Bob') {
      say $profile->{Status};
    }
  }
}

But please don't use this approach. The XML::Twig approach suggested by Sobrique is a much better answer. And an XPath approach based on XML::LibXML would be similar.

Whenever you are extracting data from an XML document, XPath is likely to be the best solution. If you're dealing with XML, then you really need to have XPath in your toolkit.

Community
  • 1
  • 1
Dave Cross
  • 68,119
  • 3
  • 51
  • 97