Reputation: 1798
Have some problem with Perl hashes. I have an XML:
<?xml version="1.0" encoding="utf-8"?>
<SvnRequestUsers>
<AccessCode>$TR0ngP@ssvv0rd!</AccessCode>
<SvnUsers>
<SvnUser>
<Username>svn.testname</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
<SvnUser>
<Username>svn.testname2</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
</SvnUsers>
</SvnRequestUsers>
I want to loop through SvnUser nodes. When I use
my $usersList = $ref->{'SvnUsers'};
foreach my $key ( @{$usersList->{'SvnUser'}} )
{ ..... }
That works when there is more than one node but doesn't work for One node.
When use
my @usersList = $ref->{'SvnUsers'}->{'SvnUser'};
foreach my $key ( @usersList )
{ ..... }
that works only when exactly one node is there. Where is the trick ???
Upvotes: 1
Views: 690
Reputation: 20280
As usual I will recommend Mojo::DOM which is part of the Mojolicious suite. Using selectors and transform/iterator methods the problem gets really simple:
#!/usr/bin/env perl
use strict;
use warnings;
use Mojo::DOM;
my $dom = Mojo::DOM->new(<<'END');
<?xml version="1.0" encoding="utf-8"?>
<SvnRequestUsers>
<AccessCode>$TR0ngP@ssvv0rd!</AccessCode>
<SvnUsers>
<SvnUser>
<Username>svn.testname</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
<SvnUser>
<Username>svn.testname2</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
</SvnUsers>
</SvnRequestUsers>
END
# get an array
my @users = $dom->find('SvnUser')
->pluck('Username')
->pluck('text')
->each;
# or do something with each element
$dom->find('SvnUser')
->pluck('Username')
->pluck('text')
->each(sub{ print "$_\n" });
Of course if you were actually just wanting the username you might just use the more tailored selector find('SvnUser Username')
, my above example was to show a method.
Upvotes: 0
Reputation: 126722
This looks like the work of the far-from-simple XML::Simple
. I am sure it has won its popularity through being the first to claim its namespace, and IMO it has a lot of shortcomings.
If my guess is correct then you can overcome some of the awkwardness by enabling the ForceArray
option. The POD for the module says
check out ForceArray because you'll almost certainly want to turn it on
In your case, this will make sure that $ref->{SvnUsers}{SvnUser}
is always an array reference, even if there is only a single <SvnUser>
element at that point in the XML. So $ref->{SvnUsers}{SvnUser}[0]
will consistently access the first or only user element.
My advice would be to to upgrade to a more consistent XML module. XML::Smart
allows the data to be accessed in a very similar way to the XML::Simple
structure, but it makes extensive use of tie
to provide a much better DWIM interface. For instance under XML::Smart
the first SvnUser
of the first SvnUsers
element can be accessed using either $ref->{SvnUsers}{SvnUser}
or $ref->{SvnUsers}[0]{SvnUser}[0]
.
Just switching to XML::Smart
will fix your immediate problem. For example the code below will list the usernames of all users, whether there is one or many of them (or indeed none, as the object will return an empty list in that case).
My own preference would be a "proper" XML module like XML::LibXML
or
XML::Twig
, but of course the choice is yours.
use strict;
use warnings;
use XML::Smart;
my $ref = XML::Smart->new(\*DATA);
my $users_list= $ref->{SvnRequestUsers}{SvnUsers};
foreach my $key ( @{ $users_list->{SvnUser} } ) {
print $key->{Username}, "\n";
}
__DATA__
<?xml version="1.0" encoding="utf-8"?>
<SvnRequestUsers>
<AccessCode>$TR0ngP@ssvv0rd!</AccessCode>
<SvnUsers>
<SvnUser>
<Username>svn.testname</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
<SvnUser>
<Username>svn.testname2</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
</SvnUsers>
</SvnRequestUsers>
output
svn.testname
svn.testname2
Upvotes: 1
Reputation: 98398
If you are using XML::Simple, you probably want to turn on ForceArray, either globally (which will probably require you to add more array dereferences in your code) or for the specific element that may have one or many nodes; see https://metacpan.org/module/XML::Simple#ForceArray-names-in---important.
Upvotes: 2
Reputation: 185161
Try doing this :
#!/usr/bin/perl
use strict;
use warnings;
use XML::XPath;
use XML::XPath::XMLParser;
my $xp = XML::XPath->new(filename => 'file.xml');
my $nodeset = $xp->find('/SvnRequestUsers/SvnUsers/SvnUser'); # find svn users
foreach my $node ($nodeset->get_nodelist) {
print "FOUND\n\n",
XML::XPath::XMLParser::as_string($node),
"\n\n";
}
Result :
FOUND
<SvnUser>
<Username>svn.testname</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
FOUND
<SvnUser>
<Username>svn.testname2</Username>
<Password>jA=B*+q%</Password>
<Create>true</Create>
</SvnUser>
Upvotes: 0
Reputation: 3222
Which module do you use to read the XML in the ahref? I'd guess that you'll need to access your data like this if there are more users:
my @usersList = $ref->{'SvnUsers'}->[0]->{'SvnUser'};
This would give you the first user, because there will be an arrayref as value under the 'SvnUsers'-key
An iteration could look like this:
my $maxusers = scalar(@{$ref->{'SvnUsers'}}); # get the number of elements in your ref
for my $i(0 .. $maxusers){
my $svnuser = $ref->{'SvnUsers'}->[$i]->{'SvnUser'}; # get current
....... # do your stuff
}
Upvotes: 0