How to expand a hash of arrays to XML using XML::Simple - arrays

I have a Perl class that is storing node and arc information for a tree data structure.
When I try to output this as XML using XML::Simple I do not get the full expansion of the arrays.
I have
$table->{arcs} = #arcs;
$table->{nodes} = #nodes;
And when I try to output this as XML I get the following output
<?xml version='1.0'?>
<Root>
<arcs>0</arcs>
<nodes>0</nodes>
</Root>
But the information is stored into the arrays correctly.
Here is the code I am working with
my $xml = new XML::Simple(NoAttr => 1, RootName => 'Root', ForceArray => 1);
$xml->XMLout(
$table,
KeepRoot => 1,
OutputFile => $xml_directory . $out_file . ".xml",
XMLDecl => "<?xml version='1.0'?>",
NSExpand => 0,
ValueAttr => { \#node_values => 'node' }
);
Any ideas on how to expand out the arrays without having to hard code what you want your tags to be?
I would like to be able to go on the fly from data structure to XML for generation.

The statements
$table->{arcs} = #arcs;
$table->{nodes} = #nodes;
are scalar assignments, with the result that the hash elements are set to the number of elements in the respective arrays.
You should change the assignment to assign references to the arrays instead, like this:
$table->{arcs} = \#arcs;
$table->{nodes} = \#nodes;
However this XML result
<Root>
<arcs>0</arcs>
<nodes>0</nodes>
</Root>
shows that the sizes you are getting are zeroes, so the arrays are actually empty and this is only part of the story.
Please show your complete code so that we can see where you are going wrong.

Related

perl -> Parsing json string of hash

so I took a curl output of a json formatted output and assigned it to a variable.
my $catcherJSON = decode_json $response->content;
print Dumper $catcherJSON;
When I view the $catcherJSON, I want to see an array of hashes, but I just get a massive string of json.
$VAR1 = [
{
'priority' => 5,
'ingestPath' => '/vg/24CHVOD_en',
...
...
...
},
{
'priority' => 5,
'ingestPath' =>
...
...
},
{
'priority' => 5,
'ingestPath' =>
...
...
},
{
'priority' => 5,
'ingestPath' =>
...
...},
{
'priority' => 5,
'ingestPath' =>
....
...
}
];
The hashes repeat (There are about 300 unique results) and I'm trying to figure out in perl how I can split this 1 string into my array of hashes simply.
Any way that I can easily convert this into a proper array of hashes so I can iterate through it?
Thanks in advance.
Any way that I can easily convert this into a proper array of hashes so I can iterate through it?
You've already done it.
so I took a curl output of a json formatted output and assigned it to a variable.
my $catcherJSON = decode_json $response->content;`
That's not correct.
You took the JSON formatted string from $response->content, decoded the JSON into a Perl data structure, and stored it in $catcherJSON.
When I view the $catcherJSON, I want to see an array of hashes, but I just get a massive string of json.
$catcherJSON is not a string of JSON. $catcherJSON is a Perl array of hashes. You've already decoded the JSON into a Perl data structure with decode_json. It's been formatted by Dumper() back into Perl code so you can read it.
Your code is fine, you have what you want. For example, $catcherJSON->[0]{ingestPath} will be '/vg/24CHVOD_en'.
my $catcherJSON = $response->content;
$catcherJSON =~ s/\n//g;
$catcherJSON = decode_json($catcherJSON);
This was my eventual solution.
When the curl response comes in, all json has line breaks. As soon as line breaks are removed, my decode_json will now refence all as $catcherJSON->[0]{id}, etc...
Thanks everyone for the help here.
Much appreciated.

php calling a string in an array creates empty array?

Currently I have this:
<?php
$fn = "file.txt";
$file = file_get_contents("./$fn");
$array = array($file);
?>
An example of whats in the text file:
array(1,4,3,2),array(3,2,1,2),array(5,6,7,8)
However when I print the array or even sizeof($array) it is empty. Whats the deal?
The content of your file is plain text not array data structures. You'll have to parse the content yourself. Here is one way of doing it:
$file_content = "array(1,4,3,2),array(3,2,1,2),array(5,6,7,8)";
$matches = [];
$arrays = [];
if (preg_match_all('~array\(((?:(?:\d+),?)*)\),?~', $file_content, $matches)) {
$arrays = array_map(function ($array) { return explode(',', $array); }, $matches[1]);
}
$arrays now contains arrays as you would expect and you can use count() to check their size.
If you know that your file always contain arrays another way to do it is by using eval.
Another approach
eval('$arrays2 = [' . $file_content . '];');
Now $arrays2 contains the same arrays. There is one subtle difference. The former approach does not cast the values to integers whereas eval does.
Warning: Only use eval() if are 100% sure that you know what goes into the function. Also note that this approach will fail on bad data, whereas the former is a little more resilient.

Accessing returned values as an array

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?
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.
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}
;
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];

Checking if XML values are in an AS3 array

I have some XML in the below format.
<user uid="0001">
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<ImagePath>images/0001.jpg</ImagePath>
<flightno>GS1234</flightno>
</user>
<user uid="0002">
<FirstName>Luke</FirstName>
<LastName>Dixon</LastName>
<ImagePath>images/0002.jpg</ImagePath>
<flightno>TD1234</flightno>
</user>
<user uid="0003">
<FirstName>Paul</FirstName>
<LastName>Kerr</LastName>
<ImagePath>images/0003.jpg</ImagePath>
<flightno>GS1234</flightno>
</user>
This is a small sample, there are a couple 100 of these.
I have used E4x filtering to filter down another set of XML data that produces an as3 array. The array contains some flight numbers (eg : [GS1234,PB7367,TD1234].
I'm wondering how I can filter my XML (as shown above) to only show the users whose 'flightno' EXISTS in the AS3 array.
I'm guessing its some sort of E4X query but I can't seem to get it right!
Thanks!
// Don't mind me using this trick with inline XML, it's not the point.
// It's here just to make it possible to copy and paste code
// with multiline XML sample.The actual solution is a one-liner below.
var usersXML:XML = new XML(<x><![CDATA[
<data>
<user uid="0001">
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<ImagePath>images/0001.jpg</ImagePath>
<flightno>GS1234567</flightno>
</user>
<user uid="0002">
<FirstName>Luke</FirstName>
<LastName>Dixon</LastName>
<ImagePath>images/0002.jpg</ImagePath>
<flightno>TD1234</flightno>
</user>
<user uid="0003">
<FirstName>Paul</FirstName>
<LastName>Kerr</LastName>
<ImagePath>images/0003.jpg</ImagePath>
<flightno>GS1234</flightno>
</user>
</data>
]]></x>.toString());
// once again, the way I create sample departingXML
// is not important, it's just for copying and pasting into IDE.
var departingXML:XML = new XML(<x><![CDATA[
<flights>
<flight>
<number>GS1234</number>
<date>10/11/2015</date>
<time>1440</time>
</flight>
<flight>
<number>TD1234</number>
<date>10/11/2015</date>
<time>1450</time>
</flight>
</flights>
]]></x>.toString());
// 1. create filter array
var flightNoArray:Array = [];
departingXML.flight.number.(flightNoArray.push(toString()));
trace(flightNoArray); // GS1234,TD1234
trace(typeof(flightNoArray[0])); // string
// 2. filter users:
var list:XMLList = usersXML.user.(flightNoArray.indexOf(flightno.toString()) >= 0);
trace(list); // traces users 0002 and 0003
I wouldn't call it efficient or at least readable though.
// Note: this line is somewhat queer and I don't like it,
departingXML.flight.number.(flightNoArray.push(toString()));
// but this is the only way I can now think of to get and array
// of strings from an XMLList nodes without a loop.
// I would advise to use a readable and simple loop instead.
usersXML.user -- this gets you an XMLList with all nodes named "user"
usersXML.user.(some condition) -- this filters the XMLList of user nodes given a condition
flightNoArray.indexOf(flightno.toString()) >= 0 -- and this is a filter condition
flightno.toString() -- gets you a string inside flightno child
REFERENCE: Traversing XML structures.
Explanation of the search trick in the note above.
UPDATE: it turned out in comments that it was also the way the filter array was populated that was causing trouble. The code below demonstrates some more E4X.
This is how the filter list was created and what was actually happening:
// once again, the way I create sample departingXML
// is just for the sake of copying and pasting, it's not related to solution.
var departingXML:XML = new XML(<x><![CDATA[
<flights>
<flight>
<number>GS1234</number>
<date>10/11/2015</date>
<time>1440</time>
</flight>
<flight>
<number>TD1234</number>
<date>10/11/2015</date>
<time>1450</time>
</flight>
</flights>
]]></x>.toString());
// the way it was done before
var flightNoArray: Array = [];
for each(var num: XML in departingXML.flight) {
flightNoArray.push(num.number);
// WARNING! num.number is an XMLList! It s NOT a string.
// Addressing nodes by name ALWAYS gets you an XMLList,
// even if there's only one node with that name
// Hence, `flightNoArray.indexOf("string")` could not possibly work,
// as there are lists inside of the array, not strings.
// We can check if this is a fact:
trace(flash.utils.getQualifiedClassName(flightNoArray[flightNoArray.length-1]));
// (traces XMLList)
// Or this way (note toXMLString() method)
trace(flightNoArray[flightNoArray.length-1].toXMLString());
// (traces <number>GS1234</number>)
}
trace(flightNoArray);
trace(flightNoArray); traces GS1234,TD1234 because this is the way toString() method works for xml nodes -- it gets you text, that is inside. This is why there is a special method toXMLString(), that gets you a string representation of a node.

Referenced array dropped in size to one element

Dear fellow perl programmers,
I wanted to access to this array
my #vsrvAttribs = qw(
Code
Description
vsrv_id
vsrv_name
vsrv_vcpu_no
vsrv_vmem_size
vsrv_vdspace_alloc
vsrv_mgmt_ip
vsrv_os
vsrv_virt_platf
vsrv_owner
vsrv_contact
vsrv_state
);
through a variable composed of a variable and a string suffix, which of course led to the error message like this
Can't use string ("#vsrvAttribs") as an ARRAY ref while "strict refs" in use at cmdbuild.pl line 262.`
Therefore I decided to get the reference to the array through a hash
my %attribs = ( vsrv => #vsrvAttribs );
And this is the code where I need to get the content of aforementioned array
foreach my $classTypeKey (keys %classTypes) {
my #attribs = $attribs{$classTypeKey};
print Dumper(\#attribs);
}
It seems I can get the reference to the array #vsrvAttribs, but when I checked the content of the array with Dumper , the array have got only one element
$VAR1 = [
'Code'
];
Do you have any idea where could be the problem?
How do you store the array in a hash and access it later?
You need to store your array by reference like this:
my %attribs = ( vsrv => \#vsrvAttribs );
Note the backslash before the # sigil. This tells perl that you want a reference to the array.
Then when access the array stored in $attribs{vsrv} you need to treat it as a reference instead of as an array. You'll do something like this:
foreach my $classTypeKey (keys %classTypes) {
# make a copy of the array by dereferencing
my #attribs = #{ $attribs{$classTypeKey} };
# OR just use the array reference if profiling shows performance issues:
my $attribs = $attribs{$classTypeKey}
# these will show the same thing if you haven't done anything to #attribs
# in the interim
print Dumper(\#attribs);
print Dumper($attribs);
}
Why did you only get one value and where did the rest of the array go?
Your missing values from #vsrvAttribs weren't lost they were assigned as keys and values to %attribs itself. Try adding the following just after you made your assignment and you'll see it for yourself:
my %attribs = ( vsrv => #vsrvAttribs );
print Dumper(\%attribs);
You'll see output like this:
$VAR1 = {
'vsrv_contact' => 'vsrv_state',
'vsrv_virt_platf' => 'vsrv_owner',
'vsrv' => 'Code',
'vsrv_name' => 'vsrv_vcpu_no',
'vsrv_mgmt_ip' => 'vsrv_os',
'Description' => 'vsrv_id',
'vsrv_vmem_size' => 'vsrv_vdspace_alloc'
};
This is because perl interpreted your assignment by expanding the contents #vsrvAttribs as multiple arguments to the list literal ():
my %attribs = (
# your key => first value from array
vsrv => 'Code',
# subsequent values of the array
Description => 'vsrv_id',
vsrv_name => 'vsrv_vcpu_no',
vsrv_vmem_size => 'vsrv_vdspace_alloc',
vsrv_mgmt_ip => 'vsrv_os',
vsrv_virt_platf => 'vsrv_owner',
vsrv_contact => 'vsrv_state',
);
This is legal in perl and there are reasons where you might want to do this but in your case it wasn't what you wanted.
Incidentally, you would have been warned that perl was doing something that you might not want if you had an even number of elements in your array. Your 13 elements plush the hash key "vsrv" makes 14 which is even. Perl will take any list with an even number of elements and happily make it into a hash. If your array had another element for 15 elements total with the hash key you would get a warning: Odd number of elements in hash assignment at foo.pl line 28.
See "Making References" and "Using References" in perldoc perlreftut for more information.
If you use a bare array in a hash definition like
my %attribs = ( vsrv => #vsrv_attribs )
the array is expanded and used as key/value pairs, so you will get
my %attribs = (
vsrv => 'Code',
Description => 'vsrv_id',
vsrv_name => 'vsrv_vcpu_no',
vsrv_vmem_size => 'vsrv_vdspace_alloc',
...
)
The value of a Perl hash element can only be a scalar value, so if you want an array of values there you have to take a reference, as shown below
It is also a bad idea to use capitals in Perl identifiers for anything except globals, such as package names. Local names are conventional lower-case alphanumeric plus underscore, so $class_type_key instead of $classTypeKey
use strict;
use warnings;
use Data::Dumper;
my #vsrv_attribs = qw(
Code
Description
vsrv_id
vsrv_name
vsrv_vcpu_no
vsrv_vmem_size
vsrv_vdspace_alloc
vsrv_mgmt_ip
vsrv_os
vsrv_virt_platf
vsrv_owner
vsrv_contact
vsrv_state
);
my %attribs = (
vsrc => \#vsrv_attribs,
);
for my $class_type_key (keys %attribs) {
my $attribs = $attribs{$class_type_key};
print Dumper $attribs;
}
output
$VAR1 = [
'Code',
'Description',
'vsrv_id',
'vsrv_name',
'vsrv_vcpu_no',
'vsrv_vmem_size',
'vsrv_vdspace_alloc',
'vsrv_mgmt_ip',
'vsrv_os',
'vsrv_virt_platf',
'vsrv_owner',
'vsrv_contact',
'vsrv_state'
];

Resources