Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

How do i determine if an XML array element exists in a hash?

by fwbennett (Initiate)
on Dec 05, 2011 at 14:42 UTC ( #941864=perlquestion: print w/replies, xml ) Need Help??

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

Hello perl Monks, I need to check to see if an xml hash element exists so i can create an array to post to variables for "stuff".
It sounds complex, but really its not. I'm sure i'm over-looking something simple.
Here is the gist of it:

I have xml returned to me (using SOAP::Lite) that looks something like this:

<xml version, encoding & SOAP Envelope header stuff...> <account> <accountId>001</accountId> <accountName>Account 001</accountName> <asset> <assetId>abcd</assetId> <assetName>Asset abcd</assetName> </asset> <asset> <assetId>wxyz</assetId> <assetName>Asset wxyz</assetName> </asset> </account> <account> <accountId>001</accountId> <accountName>Account 001</accountName> </account> </SOAP Envelope stuff...>

As you can see from the xml response, I am being returned account information that may have many or no "assets".
I am trying to perform actions (do stuff) with each 'account' + 'asset' sets.

Ultimately, I need to create an array like this:

for each (asset that exists) { $array = (accountId, accountname, assetId, assetName); (do stuff with the array); }

note - the 'accountId' and 'accountName' need to be carried through to each relevant 'asset'.

my script seems to die when it hits the 'account' with no 'asset'.

my $data1 = $xml->XMLin($xmlResponse); foreach my $account (@{$data1->{'account'}}) { my $acctName = $account->{'accountName'}; my $acctId = $account->{'accountId'}; if (defined $account->{'ns1:asset'}) { our $assetID = $account->{'asset'}->{'assetId'}; our $assetName = $account->{'asset'}->{'assetName'}; } else { print "\nIt didn't process\n"; } }

The out put looks like this:

Bad index while coercing array into hash at c:/temp/parseit.pl line (e +quivelant of 7 in this case).
Is the 'if (defined...' not doing what i think it should be doing?

Thanks

Replies are listed 'Best First'.
Re: How do i determine if an XML array element exists in a hash?
by roboticus (Chancellor) on Dec 05, 2011 at 15:28 UTC

    fwbennett:

    The error message tells you that you've got an array and you're trying to use it as a hash. Try using Data::Dumper to print the data structure you have and that'll make it easier to see where your problem is, and how to access the data.

    Also, I don't know what library you're using to parse the XML, but you may need to review the documentation and configure it to return what you want/expect if needed.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: How do i determine if an XML array element exists in a hash?
by Jenda (Abbot) on Dec 05, 2011 at 21:34 UTC

    The thing is that there are (at least sometimes) several <asset>s in each <account> so the $account->{asset} is an array ref. The problem with XML::Simple is that it's not always an arrayref.

    You may use this instead:

    use strict; use XML::Rules; sub do_something_with { print join(', ', @_), "\n"; } my $parser = XML::Rules->new( rules => { _default => 'content', asset => 'as array no content', account => sub { my ($tag, $attr) = @_; foreach my $asset (@{$attr->{asset}}) { do_something_with($attr->{accountId}, $attr->{accountN +ame}, $asset->{assetId}, $asset->{assetName}); } }, } ); $parser->parse(\*DATA); __DATA__ <data> <account> <accountId>001</accountId> <accountName>Account 001</accountName> <asset> <assetId>abcd</assetId> <assetName>Asset abcd</assetName> </asset> ...

    Or if you'd prefer a data structure containing all the data:

    use strict; use XML::Rules; my $parser = XML::Rules->new( rules => { _default => 'content', 'asset,account' => 'as array no content', 'data' => 'pass no content' } ); use Data::Dumper; print Dumper($parser->parse(\*DATA));

    Jenda
    Enoch was right!
    Enjoy the last years of Rome.

      Or if you'd prefer a data structure containing all the data...
      Or even this might be preferable (using 'id' fields as hash keys):
      my $parser = XML::Rules->new( rules => { _default => 'content', 'asset' => 'no content by assetId', 'account' => 'no content by accountId', 'data' => 'pass no content' } );
      Update: Although looking at the OP, if there are really repeated accountId's, then maybe not...but I'm thinking that might be just due to cut n paste.
      Hello all, This is Fantastic! However, there is one minor issue. With the sample xml above, if I return just one 'account', I get the error: "Bad index while coercing array into hash..." Jenda was correct - it is not always an array ref, and what you have above, handles 1 level - at the "asset" or 'sub' level. Would I set up a 'Rules' sub-routine inside of a 'Rules' sub-routine? Would that even work? Thank you again, -Fred

        Depends on how do you "return just one account". Show me the exact code that gives you the error.

        Jenda
        Enoch was right!
        Enjoy the last years of Rome.

Re: How do i determine if an XML array element exists in a hash?
by grantm (Parson) on Dec 05, 2011 at 23:04 UTC
    If you're going to stick with XML::Simple (and I encourage you to look at other options) then this best practice article might help.
Re: How do i determine if an XML array element exists in a hash?
by TJPride (Pilgrim) on Dec 05, 2011 at 19:33 UTC
    Ta dah! I know that strictly speaking, you're not supposed to do complicated things like parsing XML yourself, but I couldn't resist:

    use Data::Dumper; use strict; my ($xml, $account, $asset, @record); $xml = parseXML(join '', <DATA>); print Dumper($xml); for $account (@{$xml->{'account'}}) { $account->{'asset'} = [$account->{'asset'}] if !ref $account->{'asset'}; for $asset (@{$account->{'asset'}}) { @record = ($account->{'accountId'}, $account->{'accountName'}, + $asset->{'assetId'}, $asset->{'assetName'}); print "@record\n"; } } sub parseXML { my $xml = $_[0]; my (%xml, $key, $val); while ($xml =~ m|<(.*?)>(.*?)</\1>|sg) { ($key, $val) = ($1, $2); if (!defined $xml{$key}) { $xml{$key} = parseXML($val); next; } $xml{$key} = [$xml{$key}] if ref $xml{$key} ne 'ARRAY'; push @{$xml{$key}}, parseXML($val); } return scalar keys %xml == 0 ? $xml : \%xml; } __DATA__ <xml version, encoding & SOAP Envelope header stuff...> <account> <accountId>001</accountId> <accountName>Account 001</accountName> <asset> <assetId>abcd</assetId> <assetName>Asset abcd</assetName> </asset> <asset> <assetId>wxyz</assetId> <assetName>Asset wxyz</assetName> </asset> </account> <account> <accountId>002</accountId> <accountName>Account 002</accountName> </account> </SOAP Envelope stuff...>

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://941864]
Approved by marto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others rifling through the Monastery: (6)
As of 2019-10-16 20:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?