Perl: How do I print the result of an Array of Hashes onto the body of an e-mail - perl

I have some data stored in an array of hashes, and I am trying to print the results onto the body of the email being sent. Results will print on the command line when the script is ran, however the body of the email will remain blank.
sub DailyInflow {
my #inflow = SQLTableHash("select count(FOO), count(BAR), BAZ from table1 where days in (0,1) group by BAZ", $reportdbh);
foreach my $inflow (#inflow) {
print $inflow->{"BAZ"} . ": " . $inflow->{"COUNT(FOO)"}."\n";
print "Total: " . $inflow->{"COUNT(BAR)"} . "\n";
}
}
################### Send E-mail with CSV Attachment ##################
print "Sending Email ... \n" if $ENV{DEBUG};
my $subject = "Snapshot $reportdate";
SendEmail({
FROM => 'user#email.com',
TO => $args{EMAIL},
SUBJECT => $subject,
BODY =>DailyInflow()
ATTACHFILES => [{
Type => 'BINARY',
Path => "$fullzipname",
Disposition => 'attachment',
Filename => "$zipname",
}],
});

Your DailyInflow() subroutine should look like this:
sub DailyInflow {
my #inflow = SQLTableHash("select count(FOO), count(BAR), BAZ from table1 where days in (0,1) group by BAZ", $reportdbh);
my $string;
foreach my $inflow (#inflow) {
$string .= $inflow->{"BAZ"} . ": " . $inflow->{"COUNT(FOO)"}."\n";
$string .= print "Total: " . $inflow->{"COUNT(BAR)"} . "\n";
}
return $string;
}
But you can simplify it by interpolating variables within double-quoted strings.
sub DailyInflow {
my #inflow = SQLTableHash("select count(FOO), count(BAR), BAZ from table1 where days in (0,1) group by BAZ", $reportdbh);
my $string;
foreach my $inflow (#inflow) {
$string .= "$inflow->{'BAZ}: $inflow->{'COUNT(FOO)'}\n";
$string .= "Total: $inflow->{'COUNT(BAR)'}\n";
}
return $string;
}

Related

PDF::FromHTML No Anchors and Early Termination

Further top this question: PDF::FromHTML - Corrupt file and no output
The code in question is 'working' in that it produces a PDF document just fine, just NONE of the HTML anchors are being translated, and on larger documents the processing ceases at Page 11 of the PDF - with no error, it closes the document just fine!
Edit: To save looking at the Question Link:
# print "<p>".$textblob."</p>";
$textblob='<html><head></head><body>'.$textblob.'</body></html>';
# $textblob = decode('UTF-8', $textblob);
my $output;
if(defined($query->param('PDF'))){
my $pdf = PDF::FromHTML->new( encoding => 'utf-8' );
$pdf->load_file(\$textblob);
$pdf->convert(
# With PDF::API2, font names such as 'traditional' also works
Font => 'Arial',
LineHeight => 10,
Landscape => 0,
);
$pdf->write_file(\$output);
print $output;
}
$textblob when uncommented to print and commenting out the PDF section displays the full 400 reference adventure with links in html just fine...
Update:In desperation here is the entire script (it's not TOO long...)
#!/usr/bin/perl
use cPanelUserConfig;
use CGI::Carp qw(fatalsToBrowser);
use CGI;
use List::Util qw(shuffle);
use PDF::FromHTML;
require "authenticate.pl";
$query = new CGI;
if(defined($query->param('PDF'))){
print $query->header(-type=>'application/pdf');
}
else{
print $query->header(-charset=>'utf-8');
&html_header;
print "\n\n\n\n<!-- -------------------------- BEGIN: ff.net Script generated text ------------------------------------------- -->";
print "Randomise working? Let me know if you find a bug.<br />";
}
if(defined($query->param('doc'))){
$doc=$query->param('doc')."\nEOF";
%refhash = $doc =~ /^[\n\s\t\.\#]*(\d+)[\s\t\.\#\n]+(?!\n*^[\n\s\t\.\#]*\d+[\s\t\.\#\n]+)(.+?)(?=^[\s\t\.\#\n]*\d+[\s\t\.\#\n]+|EOF)/smcgi; # refhash{key}=content, where key==refnumber and content==well, ref content
&display_refhash(\%refhash);
}
elsif(defined($query->param('references'))){
my %anchors;
my $refhashref=&recreate_refhash($query->param('references'),\%anchors);
if(defined($query->param('Randomise'))){
$refhashref=&randomise($refhashref,\%anchors);
print "Your adventure looks like this: <br /><br />";
&display_refhash($refhashref);
}
elsif(defined($query->param('Save'))){
&save($refhashref);
}
elsif(defined($query->param('Auto-HTML Tag'))){
print "Your adventure looks like this: <br /><br />";
&display_refhash($refhashref);
}
elsif(defined($query->param('Auto-ABML Tag'))){
&autoABML($refhashref);
print "Your adventure looks like this: <br /><br />";
&display_refhash($refhashref);
}
elsif(defined($query->param('PDF'))){
&output_pdf($refhashref);
}
else{
print "undefined function call";
}
}
else{ # output form to input doc content
print "Please input your document text into the textarea below (copy and paste should do it):";
print '<form method="post" action="doc_to_refs.cgi" enctype="multipart/form-data" name="doc_to_refs_form">';
print $query->textarea(-name=>'doc',-rows=>20,-cols=>100, -style=>"font-family:arial;width:98%");
print $query->submit('Go!');
print '</form>';
}
&html_footer;
# print "<!-- -------------------------- END: ff.net Script generated text ------------------------------------------- -->";
sub recreate_refhash{
my %refhash;
my $references=shift;
my $anchors_ref=shift;
for(my $x=0;$x<$references;$x++){
my $referencekey="reference"."$x";
my $referencecontent="reftext"."$x";
my $anchorname="anchor"."$x";
my $deletename="delete"."$x";
if(!defined($query->param($deletename))){
$refhash{$query->param($referencekey)}=$query->param($referencecontent);
if(defined($query->param($anchorname))){
$$anchors_ref{$query->param($anchorname)}=$x;
}
}
}
return \%refhash;
}
sub randomise{
my $refhashref=shift;
my $anchor_ref=shift;
my %refhash=%$refhashref;
my %randomisedrefhash, %Xrefhash, #refstack, $ref;
my %anchors=%$anchor_ref;
# randomise the list
#refstack=shuffle sort {$a <=> $b} keys %refhash; # inflict an order on the pre-shuffle (therefore we can xref predicatably?) not sure this makes ANY sense i'm melting....
## transpose anchors back to their required location
for($x=0;$x<#refstack;$x++){
if(defined($anchors{$refstack[$x]})){
my $anchor=\$refstack[$anchors{$refstack[$x]}];
my $temp=$refstack[$x];
$refstack[$x]=$$anchor;
print "---Swapping $temp with ".$$anchor;
$$anchor=$temp;
if(defined($anchors{$refstack[$x]})){
if($refstack[$anchors{$refstack[$x]}] ne $$anchor){
$x--;
}
}
}
}
## randomise the refs and the content associations, and create the cross-ref hash
foreach $ref(sort {$a <=> $b} keys %refhash){
$key=shift #refstack;
$randomisedrefhash{$ref}=$refhash{$key};
$Xrefhash{$key}=$ref;
}
## now do the content link substitutions
foreach $ref(keys %randomisedrefhash){
$randomisedrefhash{$ref}=~s/(return\sto|go\sto|turn\sto)(\s+)(page|paragraph|reference|section)*(\s)*(\d+)/&substitute_xref($1,$2,$3,$4,$5,\%Xrefhash)/egi;
}
print "You asked for the following anchors:";
foreach $key(keys %anchors){
print $anchors{$key};
}
return \%randomisedrefhash;
}
sub substitute_xref{ ## not sure that this is necessary but the verboseness was easier to work out
my $pretext1=shift;
my $pretext2=shift;
my $pretext3=shift;
my $pretext4=shift;
my $link=shift;
my $Xrefhashref=shift;
my %Xrefhash=%$Xrefhashref;
my $newlink=$Xrefhash{$link};
return "$pretext1$pretext2$pretext3$pretext4$newlink";
}
sub save{
print "Will Save soon";
}
sub display_refhash{
my $refhashref=shift;
my %refhash=%$refhashref;
print '<form method="post" action="doc_to_refs.cgi" enctype="multipart/form-data" name="doc_to_refs_form">';
my $x=0;
my $ref,$textblob;
foreach $ref (sort {$a <=> $b} keys %refhash){
my $reference="reference"."$x";
my $reftext="reftext"."$x";
my $anchor="anchor"."$x";
my $delete="delete"."$x";
my $default=$refhash{$ref};
print "Reference is: ".$query->textfield(-name=>$reference,-value=>$ref, -override=>1)."<br />";
print $query->checkbox_group(-name=>$delete,-values=>$ref,-labels=>{$ref=>'Delete Me'})."<br />";
print $query->checkbox_group(-name=>$anchor,-values=>$ref, -labels=>{$ref=>'Anchor Me (Will NOT get Randomised)'})."<br />";
print "Content is: ".$query->textarea(-name=>$reftext, -default=>$default, -rows=>5, -override=>1, -cols=>100, -style=>"font-family:arial;width:98%")."<br />";
print "<br /><br />";
if((defined($query->param('Auto-HTML Tag'))) or (defined($query->param('PDF')))){
$ref=~s/(\d+)/\<a id\=\"$1\"\>$1\<\/a\>/gi;
$default=~s/(return\sto|go\sto|turn\sto)(\s+)(page|paragraph|reference|section)*(\s*)(\d+)/\<a href\=\"\#$5\"\>$1 $2 $3 $4 $5\<\/a\>/gi;
}
if(defined($query->param('Auto-ABML Tag'))){
# $ref=~s/(\d+)/\<a id\=\"$1\"\>$1\<\/a\>/gi;
$default=~s/(return\sto|go\sto|turn\sto)(\s+)(page|paragraph|reference|section)*(\s*)(\d+)/\&lt\;tt ref\=\"$5\"\&gt\;$1 $2 $3 $4 $5\&lt\;\/tt\&gt\;/gi;
}
$textblob.=$ref." ".$default."<br /><br />";
$x++;
}
print $query->hidden(-name=>'references',-value=>$x,override=>1);
# print $query->submit(-name=>'Save');
print $query->submit('Randomise');
print $query->submit('Auto-ABML Tag');
print $query->submit('Auto-HTML Tag');
print $query->submit('PDF');
print "</form><br /><br /><br />";
$textblob=~s/\n/\<br \/\>/gi;
print "<p>".$textblob."</p>";
}
sub output_pdf{
my $refhashref=shift;
my %refhash=%$refhashref;
my $x=0;
my $ref,$textblob;
foreach $ref (sort {$a <=> $b} keys %refhash){
my $reference="reference"."$x";
my $reftext="reftext"."$x";
my $anchor="anchor"."$x";
my $delete="delete"."$x";
my $default=$refhash{$ref};
if((defined($query->param('Auto-HTML Tag'))) or (defined($query->param('PDF')))){
$ref=~s/(\d+)/\<a id\=\"$1\"\>$1\<\/a\>/gi;
$default=~s/(return\sto|go\sto|turn\sto)(\s+)(page|paragraph|reference|section)*(\s*)(\d+)/\<a href\=\"\#$5\"\>$1 $2 $3 $4 $5\<\/a\>/gi;
}
if(defined($query->param('Auto-ABML Tag'))){
# $ref=~s/(\d+)/\<a id\=\"$1\"\>$1\<\/a\>/gi;
$default=~s/(return\sto|go\sto|turn\sto)(\s+)(page|paragraph|reference|section)*(\s*)(\d+)/\&lt\;tt ref\=\"$5\"\&gt\;$1 $2 $3 $4 $5\&lt\;\/tt\&gt\;/gi;
}
$textblob.=$ref." ".$default."<br /><br />";
$x++;
}
$textblob=~s/\n/\<br \/\>/gi;
# print "<p>".$textblob."</p>";
$textblob='<html><head></head><body>'.$textblob.'</body></html>';
my $output;
if(defined($query->param('PDF'))){
my $pdf = PDF::FromHTML->new( encoding => 'utf-8' );
$pdf->load_file(\$textblob);
$pdf->convert(
# With PDF::API2, font names such as 'traditional' also works
Font => 'Arial',
LineHeight => 10,
Landscape => 0,
);
$pdf->write_file(\$output);
print $output;
}
}
sub html_header{
}
sub html_footer{
}
If you want sample data let me know I'll upload it somewhere
"... and on larger documents the processing ceases at Page 11 of the PDF..."
This seems to be due to a bug in PDF::FromHTML::Template::Container::PageDef. Notice the line:
last if $::x++ > 10;
It means it will never create more than 11 pages. I have filed a bug report

Printing content of ARRAY inside hash

how do I print a content of an array inside the hash? I am using Dumper so you can see the data that I am parsing.
print Dumper \%loginusers;
for my $key ( keys %loginusers ) {
my $value = $loginusers{$key};
print "$key => $value\n";
}
printf "%s %-32s %-18s\n","User","Hostname","Since";
The output is
$VAR1 = {
'server1.localdomain.com:8080' => [
', 'user=user1
' 'since=2017-03-10 13:53:27
]
};
server1.localdomain.com:8080 => ARRAY(0x1584748)
User Hostname Since
As you can see there is an ARRAY(0x1584748) and I don't know how to get that value inside from the hash.
What I would like to see is something like:
User Hostname Since
user1 server1.localdomain.com:8080 2017-03-10 13:53:27
Thank you very much for someone that can help.
Update:
So after trying this to see the data how it looks:
foreach my $key (keys %loginusers)
{
print "For $key:\n";
print "\t|$_|\n" for #{$loginusers{$key}};
}
The output looks like this:
For server1.localdomain.com:8080:
| |user=user1
| |since=2017-03-10 13:53:27
Update:
tried the add these on the code:
foreach my $key (keys %loginusers)
{
my #fields =
map { s/^\s*//; s/\s*\Z//; s/\s*\n\s*/ /g; $_ }
grep { /\S/ }
#{$loginusers{$key}};
print "For $key:\n";
print "$_\n" for #fields;
}
And using the both sample code:
printf "%-8s %-32s %s\n", qw(User Hostname Since);
foreach my $key (keys %loginusers)
{
my %field = map { /\s*(.*?)=\s*(.*)/ } #{$loginusers{$key}};
my ($host, $rgsender, $port) = split /:/, $key;
printf "%-8s %-32s %s\n", $field{user}, $host, $field{since};
}
my $newusers;
for my $host ( keys %loginusers ) {
local $/ = "\r\n"; #localised "input record separator" for the "chomp"
%{$newusers->{$host}} = map { chomp; split /=/,$_,2 } #{$loginusers{$host}};
}
undef %loginusers; #not needed anymore
#print "NEW STRUCTURE: ", Dumper $newusers;
printline( qw(User Hostname Since) );
printline($newusers->{$_}{user}, $_, $newusers->{$_}{since}) for (keys %$newusers);
sub printline { printf "%-8s %-32s %-18s\n", #_; }
and here is the results:
User Hostname Since
user1 server1.localdomain.com:8080 2017-03-10 13:53:27
User Hostname Since
user1 server1.localdomain.com:8080 2017-03-10 13:53:27
A hash value is a scalar, and it can take a reference. This is how we build complex data structures. Yours apparently have arrayrefs, so you need to dereference them. Something like
foreach my $key (keys %hash) {
say "$key => #{$hash{key}}";
}
See the tutorial perlretut and the cookbook on data structures perldsc.
The strange output from Dumper indicates that there may be leading/trailing spaces around values (or worse), which need be cleaned out. Until this is clarified I'll assume data like
'server1.localdomain.com:8080' => ['user=user1', 'since=2017-03-10 13:53:27']
In order to get the desired output you need to split each element
printf "%-8s %-32s %s\n", qw(User Hostname Since);
foreach my $key (keys %hash)
{
my ($user, $since) = map { /=\s*(.*)/ } #{$hash{$key}};
printf "%-8s %-32s %s\n", $user, $key, $since;
}
For each value, we dereference it and pass that through map. The code in maps block, that is applied to each element, pulls what is after =. Given the data, the first one is the user and the second one is timestamp. Since this is an array (and not a hash) I assume that the order is fixed. If not, get strings from both sides of = and analyze them to see which one goes where. Or better use a hash
my %field = map { /\s*(.*?)=\s*(.*)/ } #{$hash{$key}};
where .*? is the non-greedy version of .*, capturing until the first =. Then print as
printf "%-8s %-32s %s\n", $field{user}, $key, $field{since};
and you don't rely on the order in the arrayref. See the answer by jm666 for a nice and consistent approach building this from the beginning.
With the hash shown above this prints
User Hostname Since
user1 server1.localdomain.com:8080 2017-03-10 13:53:27
I've used 8 and 32 widths based on shown data. For more precision, there are modules for tabular output. If you do it by hand you need to pre-process and find the longest word for each column among keys and/or values, and then use those lengths in the second pass with printf.
It appears that Dumper is getting confused by strange data. To see what we have do
foreach my $key (keys %loginusers)
{
print "For $key:\n";
print "\t|$_|\n" for #{$loginusers{$key}};
}
To clean up the data you can try
foreach my $key (keys %loginusers)
{
my #fields =
map { s/^\s*//; s/\s*$//; s/\s*\R\s*/ /g; $_ }
grep { /\S/ }
#{$loginusers{$key}};
print "For $key:\n";
print "$_\n" for #fields;
}
The grep takes an input list and filters out those elements for which the code inside its block evaluates false. Here we require at least one non-space character. Then its output goes into map, which removes all leading and trailing whitespace, and replaces all newlines with spaces.
The your data-structure isn't very nice. I would convert it to some better, using:
#convert to better structure
my $newusers;
for my $host ( keys %loginusers ) {
%{$newusers->{$host}} = map { chomp; split /=/,$_,2 } #{$loginusers{$host}};
}
undef %loginusers; #the old not needed anymore
print "NEW STRUCTURE: ", Dumper $newusers;
The dump now looks like:
NEW STRUCTURE: $VAR1 = {
'server1.localdomain.com:8080' => {
'user' => 'user1',
'since' => '2017-03-10 13:53:27'
}
};
after the above the printing is simple:
printline( qw(User Hostname Since) );
printline($newusers->{$_}{user}, $_, $newusers->{$_}{since}) for (keys %$newusers);
sub printline { printf "%-8s %-32s %-18s\n", #_; }
For the explanation read #zdim's excellent answer (and accept his answer :))
full code
use 5.014;
use warnings;
use Data::Dumper;
my %loginusers = (
'server1.localdomain.com:8080' => [
"user=user1\r\n", # you probably have the \r too
"since=2017-03-10 13:53:27\r\n",
]
);
say "OLD STRUCTURE: ", Dumper \%loginusers;
#convert to better structure
my $newusers;
for my $host ( keys %loginusers ) {
%{$newusers->{$host}} = map { s/[\r\n]//g; split /=/, $_, 2 } #{$loginusers{$host}}; #removes all \r and \n
}
undef %loginusers; #not needed anymore
say "NEW STRUCTURE: ", Dumper $newusers;
printline( qw(User Hostname Since) );
printline($newusers->{$_}{user}, $_, $newusers->{$_}{since}) for (keys %$newusers);
sub printline { printf "%-8s %-32s %-18s\n", #_; }
result:
OLD STRUCTURE: $VAR1 = {
'server1.localdomain.com:8080' => [
'user=user1
',
'since=2017-03-10 13:53:27
'
]
};
NEW STRUCTURE: $VAR1 = {
'server1.localdomain.com:8080' => {
'user' => 'user1',
'since' => '2017-03-10 13:53:27'
}
};
User Hostname Since
user1 server1.localdomain.com:8080 2017-03-10 13:53:27
EDIT
You probably have the \r in your data too. See the updated code.

Perl save print as variable

I have a command
print $_->{href} . "\n" for $mech->find_link_dom(text_regex => qr/pdf/i);
that prints out the exact link I would like to save as a variable. Although when I try to do
my $link = $_->{href} . "\n" for $mech->find_link_dom(text_regex => qr/pdf/i);
it does not work.
Any thoughts?
If you know that $mech->find_link_dom(text_regex => qr/pdf/i) returns exactly one element, then you can write:
my $link = [$mech->find_link_dom(text_regex => qr/pdf/i)]->[0]->{href} . "\n";
If it can return multiple elements — or zero elements — then maybe you mean this:
my $link;
$link .= $_->{href} . "\n" for $mech->find_link_dom(text_regex => qr/pdf/i);
Your first code snippet is equivalent to:
for $_ ($mech->find_link_dom(text_regex => qr/pdf/i)) {
print $_->{href} . "\n"
}
The second is equivalent to:
for $_ ($mech->find_link_dom(text_regex => qr/pdf/i)) {
my $link = $_->{href} . "\n";
}
So, the $link variable is local to the for block, and is not visible outside of that block. It will work if you first declare the variable outside of the block:
my $link;
for $_ ($mech->find_link_dom(text_regex => qr/pdf/i)) {
$link = $_->{href} . "\n";
}
Or, using the short form as in your code:
my $link;
$link = $_->{href} . "\n" for $mech->find_link_dom(text_regex => qr/pdf/i);

How to properly call a sub by referencing in Perl

I'm working on a dispatching script. It takes a string with a command, does some cooking to it, and then parses it. But I can't grab a hold into the referencing:
Use::strict;
Use:warnings;
my($contexto, $cmd, $target, $ultpos, #params);
my $do = "echo5 sample string that says stuff ";
$target = "";
$cmd = "";
$_ = "";
# I do some cumbersome string parsing to get the array with
# the exploded string and then call parsear(#command)
sub parsear {
my %operations = (
'echo' => \&echo,
'status' => \&status,
'echo5' => \&echo5,
);
my $op = $_[0];
if ($operations{$op}){
$operations{$op}->(#_);
print "it exists\n";
}
else{
print "incorrect command.\n";
}
}
sub status {
print "correct status.\n";
}
sub echo {
shift(#_);
print join(' ',#_) . "\n";
}
sub echo5 {
shift(#_);
print join(' ',#_) . "\n" x 5;
}
I don't really know what the problem is. If the sub does not exist, it never says "incorrect command", and if I call for example "echo5 hello" it should print out:
hello
hello
hello
hello
hello
But it does nothing.
And when I call echo, it works as expected. What is the explanation?
Note: I'm on the latest version of Strawberry Perl
use strict; # 'use' is a keyword
use warnings;
# All these variables are not needed
sub parsear { # Learn to indent correctly
my %operations = (
'echo' => \&echo,
'status' => \&status,
'echo5' => \&echo5,
);
my $op = shift; # take first element off #_
if ($operations{$op}) {
print "$op exists\n"; # Make your status message useful
$operations{$op}->(#_);
} else {
print "incorrect command: $op\n"; # And your error message
}
}
sub status {
print "correct status.\n";
}
sub echo {
# shift(#_); # This is no longer needed, and now echo can be used as a
# normal subroutine as well as a dispatch target
print join(' ',#_) . "\n";
}
sub echo5 {
# shift(#_); # This is no longer needed
print +(join(' ',#_) . "\n") x 5; # Parentheses are needed since x binds tightly
}
Then running:
parsear 'status';
parsear 'echo', 'hello';
parsear 'echo5', 'hello';
parsear 'an error';
results in:
status exists
correct status.
echo exists
hello
echo5 exists
hello
hello
hello
hello
hello
incorrect command: an error
I am not sure what "cumbersome string parsing" you are doing since you did not include it, but if you are parsing a string like
my $do = "echo5 sample string that says stuff ";
where the command is the first word, and the arguments are the rest, you can either split everything:
parsear split /\s+/, $do;
Or use a regex to cut the first word off:
my ($cmd, $arg) = $do =~ /^(\w+)\s*(.*)/;
parsear $cmd => $arg;
You don’t even need the variables:
parsear $do =~ /^(\w+)\s*(.*)/;
Finally, the echo5 subroutine is a bit more complicated than it needs to be. It could be written as:
sub echo5 {
print "#_\n" x 5; # "#_" means join($", #_) and $" defaults to ' '
}
The x command binds differently from how you were expecting; you probably wanted:
print ((join(' ', #_) . "\n") x 5);
Both extra sets of parentheses seemed to be necessary.

Recursively printing data structures in Perl

I am currently learning Perl. I have Perl hash that contains references to hashes and arrays. The hashes and arrays may in turn contain references to other hashes/arrays.
I wrote a subroutine to parse the hash recursively and print them with proper indentation. Though the routine works as expected, my instructor was not convinced about the readability and elegance of the below code.
I would really appreciate to get the views of Perl experts here on possible optimization of the below code.
Here is my complete code snippet..
# Array of Arrays
$ref_to_AoA = [
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
];
#Array of Hashes
$ref_to_AoH = [
{
husband => "barney",
wife => "betty",
son => "bamm bamm",
},
{
husband => "george",
wife => "jane",
son => "elroy",
},
];
# Hash of Hashes
$ref_to_HoH = {
flintstones => {
husband => "fred",
pal => "barney",
},
jetsons => {
husband => "george",
wife => "jane",
"his boy" => "elroy", # Key quotes needed.
},
simpsons => {
husband => "homer",
wife => "marge",
kid => "bart",
},
};
# Hash which contains references to arrays and hashes
$finalHash = {
'arrayofArrays' => $ref_to_AoA,
'arrayofHash' => $ref_to_AoH,
'hashofHash' => $ref_to_HoH,
};
$string = str($finalHash);
print "$string\n";
#------------------------------------------------------------------
sub str {
my $hash = shift;
my ($space, $newline, $delimiter) = #_;
$space = "" unless (defined $space);
$newline = "\n\n\n" unless (defined $newline);
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
my $str = "";
for (sort keys %{$hash}) {
my $value = $hash->{$_};
$str .= "$newline$space$_ == $value$delimiter";
$str .= recurseErrors($value,$space);
}
$str;
}
#------------------------------------------------------------------
sub recurseErrors {
my $str;
my ($value,$space) = #_;
my $ref = ref $value;
if ($ref eq 'ARRAY') {
my $i = 0;
my $isEmpty = 1;
my #array = #$value;
$space .= "\t";
for my $a (#array) {
if (defined $a) {
$isEmpty = 0;
$str .= "\n$space$_\[$i\] :";
$str .= recurseErrors($a,$space);
}
$i++;
}
$str .= "= { }" if ($isEmpty);
} elsif ($ref eq 'HASH') {
$space .= "\t";
for my $k (sort keys %$value) {
if ( ( ref($value->{$k}) eq 'HASH') || (ref $value->{$k} eq 'ARRAY') ) {
my $val = $value->{$k};
$str .= "\n\n$space$k == ";
$str .= "$val";
}
else {
$str .= "\n$space$k == ";
}
$str .= recurseErrors($value->{$k},$space);
}
# we have reached a scalar (leaf)
} elsif ($ref eq '') {
$str .= "$value";
}
$str
}
#------------------------------------------------------------------
Output:
arrayofArrays == ARRAY(0x9d9baf8)
--------------------------------------------
arrayofArrays[0] :
arrayofArrays[0] :fred
arrayofArrays[1] :barney
arrayofArrays[1] :
arrayofArrays[0] :george
arrayofArrays[1] :jane
arrayofArrays[2] :elroy
arrayofArrays[2] :
arrayofArrays[0] :homer
arrayofArrays[1] :marge
arrayofArrays[2] :bart
arrayofHash == ARRAY(0x9d9bba8)
--------------------------------------------
arrayofHash[0] :
husband == barney
son == bamm bamm
wife == betty
arrayofHash[1] :
husband == george
son == elroy
wife == jane
hashofHash == HASH(0x9da45f8)
--------------------------------------------
flintstones == HASH(0x9d9bb48)
husband == fred
pal == barney
jetsons == HASH(0x9d9bbf8)
his boy == elroy
husband == george
wife == jane
simpsons == HASH(0x9d9bc48)
husband == homer
kid == bart
wife == marge
Always use use strict;
To be a good boy, use use warnings as well.
The names you use for subroutines should make it obvious what the subroutine does. "recurseErrors" kind of violates that principle. Yes, it does recurse. But what errors?
On the first line of each subroutine you should declare and initialize any parameters. recurseErrors first declares $str and then declares its parameters.
Don't mix shift and = #_ like you do in str()
You might consider breaking up what is now called recurseErrors into specialized routines for handling arrays and hashes.
There's no need to quote variables like you do on lines 99 and 109.
Apart from that I think your instructor had a bad day that day.
maybe Data::Dumper is what you want:
use Data::Dumper;
$str = Dumper($foo);
print($str);
If you are new to perl, I'd recommend running your code through perl-critic (there is also a script you can install from CPAN, normally I use it as a test so it gets run from the command line whenever I do "make test"). In addition to its output, you might want to break up your functions a bit more. recurseErrors has three cases that could be split into sub functions (or even put into a hash of ref-type to sub-function ref).
If this were a production job, I'd use Data::Dumper, but it sounds like this is homework, so your teacher might not be too pleased.
Here is one simple example why your code is not easily readable:
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
You could use the defined or operator:
$delimiter //= "\n" . '-' x 44;
If you are worried about earlier Perls:
defined $delimeter or $delimeter = "\n" . '-' x 44;
Conditionals going off the right margin are enough of a turn-off for me not to read the rest of the code.
My guess is that he doesn't like that you
expect a hash in the str function.
call the same function to print arrays as hashes, despite that there appears to be no common function between them.
allow various ways to call str, but it never figures into the final result.
allow configurable space to be passed in to the root function, but have a tab hardcoded in the recursive function.
omit undefined values that actually hold a place in the arrays
Those are issues that I can see, pretty quickly.
You could have separated out the code blocks that dealt with arrays, and hashes.
sub recurse{
...
recurse_A(#_) if $ref eq 'ARRAY';
recurse_H(#_) if $ref eq 'HASH';
...
}
sub recurse_A{ ... }
sub recurse_H{ ... }
I would recommend starting out your subroutines like this, unless you have a real good reason for doing otherwise.
sub example{
my( $one, $two, $three, $optional_four ) = #_;
( If you do it like this then Komodo, at least, will be able to figure out what the arguments are to your subroutine )
There is rarely any reason to put a variable into a string containing only the variable.
"$var" eq $var;
The only time I can think I would ever do that is when I am using an object that has an overloaded "" function, and I want to get the string, without also getting the object.
package My_Class;
use overload
'""' => 'Stringify',
;
sub new{
my( $class, $name ) = #_;
my $self = bless { name => $name }, $class;
return $self;
}
sub Stringify{
my( $self ) = #_;
return $self->{name};
}
my $object = My_Class->new;
my $string = "$object";
I've struggled with this same problem before, and found my way here. I almost used a solution posted here, but found a more suitable one (for me anyway). Read about Depth First Recursion here.
The sub in the above article works perfectly with a reference containing other Hashes, Arrays, or Scalars. It did not print Hash key names, though, so I slightly modified it:
#!/usr/bin/perl
#
# See:
#
# http://perldesignpatterns.com/?DepthFirstRecursion
#
use strict;
use warnings;
my %hash = (
'a' => {
'one' => 1111,
'two' => 222,
},
'b' => [ 'foo', 'bar' ],
'c' => 'test',
'd' => {
'states' => {
'virginia' => 'richmond',
'texas' => 'austin',
},
'planets' => [ 'venus','earth','mars' ],
'constellations' => ['orion','ursa major' ],
'galaxies' => {
'milky way' => 'barred spiral',
'm87' => 'elliptical',
},
},
);
&expand_references2(\%hash);
sub expand_references2 {
my $indenting = -1;
my $inner; $inner = sub {
my $ref = $_[0];
my $key = $_[1];
$indenting++;
if(ref $ref eq 'ARRAY'){
print ' ' x $indenting,'ARRAY:';
printf("%s\n",($key) ? $key : '');
$inner->($_) for #{$ref};
}elsif(ref $ref eq 'HASH'){
print ' ' x $indenting,'HASH:';
printf("%s\n",($key) ? $key : '');
for my $k(sort keys %{$ref}){
$inner->($ref->{$k},$k);
}
}else{
if($key){
print ' ' x $indenting,$key,' => ',$ref,"\n";
}else{
print ' ' x $indenting,$ref,"\n";
}
}
$indenting--;
};
$inner->($_) for #_;
}
#use strict ;
use warnings ;
# use module
use XML::Simple;
use Data::Dumper;
#debug print "START SCRIPT " ;
my $fileToParse = 'C:/Temp/CDIP/scripts/perl/nps_all_workflows.xml' ;
# create object
my $objXml= new XML::Simple;
# read XML file
my $data = $objXml->XMLin("$fileToParse");
# #debug print "\n FirstLevel is " . $objXml->{'POWERMART'} ;
my $level = 1 ;
#
printHashKeyValues ($data ) ;
sub printHashKeyValues
{
$level ++ ;
my $refHash = shift ;
my $parentKey = shift ;
my $parentValue = shift ;
while( my ($key, $value) = each %$refHash)
{
if ( defined ( $key ) )
{
if ( ref ($refHash->{"$key"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$key"} ;
#debug print " \n The key is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$key"}) eq 'ARRAY' )
{
#debug print " \n the key is an ARRAY " ;
printArrayValues ( $refHash->{"$key"} ) ;
}
} #eof if ( defined ( $key ))
if ( defined ( $value) )
{
if ( ref ($refHash->{"$value"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$value"} ;
#debug print " \n The value is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$value"}) eq 'ARRAY' )
{
#debug print " \n the value is an ARRAY " ;
printArrayValues ( $refHash->{"$value"} ) ;
}
} #eof if defined ( $value )
#debug print "\n key: $key, value: $value.\n";
} #eof while
} #eof sub
sub printArrayValues
{
my $arrRef = shift ;
my #array = #$arrRef;
my $parrentArrayElement = shift ;
#debug print "printArrayValues CALLED " ;
foreach my $arrayElement ( #array )
{
if (defined ( $arrayElement ) )
{
if ( ref ($arrayElement) eq 'HASH' )
{
#debug print " \n The \$arrayElement is a hash FROM THE ARRAY " ;
printHashKeyValues ($arrayElement ) ;
} #eof if
if ( ref ($arrayElement) eq 'ARRAY' )
{
#debug print " \n The \$arrayElement is a ARRAY FROM THE ARRAY " ;
printArrayValues ($arrayElement ) ;
} #eof if
#debug print "\n \$arrayElement is $arrayElement " ;
} #eof if ( defined ( $arrayElement ) )
} #eof foreach
} #eof sub
# #debug print output
##debug print Dumper($data);
1 ;