2

I am creating an xml to pass to an API and the API returns this (data dumped):

    (
    "Data::Dump",
    {
      SiteDevices => {
        "device"  => {
                       1102   => { address => "1.2.3.4", riskfactor => "1.0", riskscore => "0.0" },
                       1136   => { address => "1.2.3.5", riskfactor => "1.0", riskscore => "0.0" },
                       20491  => { address => "1.2.3.6", riskfactor => "1.0", riskscore => "0.0" },
                       129644 => { address => "1.2.3.7", riskfactor => "1.0", riskscore => "0.0" },
                       129645 => { address => "1.2.3.8", riskfactor => "1.0", riskscore => "0.0" },
                       130408 => { address => "1.2.3.9", riskfactor => "1.0", riskscore => "0.0" },
                       135975 => { address => "1.2.3.10", riskfactor => "1.0", riskscore => "0.0" },
                       137642 => { address => "1.2.3.11", riskfactor => "1.0", riskscore => "0.0" },
                     },
        "site-id" => 27,
      },
      success => 1,
    },
)

I want to loop through and print the devices and IPs associated with them and I for the life of me can’t come up with any code to do it. What the heck am I missing?! I tried to loop through a hash, a hash of hashes, etc. Can never get it to work. If any of you have a second and can provide an answer so I can shake my head in shame that’d be awesome.

I have tried:

foreach my $key (keys %{ $output->{‘SiteDevices’}->{‘device’} }) {
         print $key 
         print $key->{‘address’} 
}

and

foreach my $key (keys %{ $output{‘SiteDevices’}{‘device’} }) {
         print $key 
         print $key{‘address’} 
}

but neither works.

  • How many `SiteDevices` entries are in the output? Just one? – stevieb Dec 19 '16 at 19:36
  • This looks suspiciously like parsed XML. Is it by any chance? If so, I'd suggest you'd be _far_ better off working upstream and using a better parser. Add an example of the XML, and it'll be much easier to give you a better example. – Sobrique Dec 19 '16 at 19:43

2 Answers2

6

You mention this is parsed XML. It looks like you've parsed it with XML::Simple and that's just not a good idea.

Why not instead:

#!/usr/bin/env perl
use strict;
use warnings;

use XML::Twig; 

## get xml somehow here. parsefile if it's a file already. 
my $twig = XML::Twig -> new -> parse ( $your_xml );

foreach my $device ( $twig -> get_xpath('//devices' ) ) {
   print $device -> att('name'), " => "; 
   print $device -> att('address'),"\n";
}

The useful thing here is xpath - it's not a regular expression, but it's sort of similar - and it applies to XML. In this case //device says 'find a <device> node anywhere in the tree. (And then we extract attributes).

That may work for your scenario, but you can be more specific by specifying a longer path:

  • ./device - directly below the current node.
  • .//device - anywhere below the current node.
  • /root/SiteDevices/device - nodes specifically matching this 'tree'.

You can also use xpath to search on attributes:

  • .//device[@name="1136"] will find something with the appropriate value and attribute.

See XML Twig quick reference for some guides on how to do this.

Community
  • 1
  • 1
Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • 1
    I want to Thank you for this! I have been hitting my head on a table for a couple days trying to figure this out. And you fixed it in a matter of seconds. I'm going to go read up a bit more how twig works because I did a dump of it and it has a crazy amount of info in it. But the loop prints the required info!!! – robamcclellan Dec 19 '16 at 19:56
  • 1
    @robamcclellan: Note that Sobrique has used the XPath `//devices`, which finds all `devices` nodes anywhere in the document. It's my guess he chose that because we haven't seen your XML data and so don't know the name of the root element. Ordinarily it would be best to use something like `/root/SiteDevices/device` to isolate the nodes more specifically, but we can't tell what `root` should be. – Borodin Dec 19 '16 at 20:17
  • 1
    @robamcclellan you probably don't want to dump the XML::Twig (or any XML::Twig::Elt) object. Think of it as a sausage ;--) You could use the `_dump` method to get tree representation, but really, pretty printing the XML will be just as informative. Look at http://xmltwig.org/xmltwig/quick_ref.html for a first list of useful methods – mirod Dec 20 '16 at 10:07
2

I believe the following loop will do what you are trying to accomplish. It fetches each device from the tree, then extracts out its address, and prints both:

for my $device (keys %{ $output->{SiteDevices}{device} }){
    print "$device: $output->{SiteDevices}{device}{$device}{address}\n";
}

In your tests, you're trying to use the key name as a hash/hash reference, which won't work. You need to put the extracted key back into the hash to perform further extractions.

Output, based on the single record I extracted out of your question:

129644: 1.2.3.7
129645: 1.2.3.8
130408: 1.2.3.9
137642: 1.2.3.11
1136: 1.2.3.5
135975: 1.2.3.10
1102: 1.2.3.4
20491: 1.2.3.6
stevieb
  • 9,065
  • 3
  • 26
  • 36