Rearrange data in a PDF file - perl

I am using Perl to generate a PDF file with the
PDF::API2 module.
I am having difficulty writing data to the file in the required format.
Currently this is how it appears in the PDF file.
Here's my script
my %data = (
'1' => {
'SEQUENCE' => '1',
'NAME' => 'John',
'ADDR1' => 'Road 1',
'GRADE' => '5'
},
'2' => {
'SEQUENCE' => '2',
'NAME' => 'Smith',
'ADDR1' => 'Road 2',
'GRADE' => '6'
}
);
...
...
my #rows = qw( NAME ADDR1 GRADE );
for my $id (sort keys %data){
push #tbl, [ #{$data{$id}}{#rows} ];
($name, $addr, $grade) = ($data{$id}{'NAME'}, $data{$id}{'ADDR1'}, $data{$id}{'GRADE'});
}
...
...
my $pdftable = new PDF::Table;
$pdftable->table(
$pdf,
$page,
\#tbl,
x => 50,
w => 400,
start_y => 630,
start_h => 630,
next_y => 630,
next_h => 630,
padding => 5,
border => 1,
padding_right => 10,
);
$pdf->saveas();
But I want it to to appear like this:
I know I have to make a modification to #tbl data, but how?

The PDF::Table table function takes an "array or arrays" as its third argument. You currently have it strucutred like:
my #tbl = ( [$name,$addr,$grade], [...] )
Each nested array (what's inside the [] brackets) is a new row of the table, and the elements to that array are the cells of that row. So instead, you want it structured:
my #tbl = ( ['NAME',$name], ['ADDR1',$addr], ['GRADE',$grade], [...] )
To construct it using your %data structure:
for my $id (sort keys %data) {
push #tbl, [$_, $data{$id}->{$_}] for qw/NAME ADDR1 GRADE/;
push #tbl, [qw/- -/]; # to add a blank row as a separator
}

Related

Stringify Struct in Perl

In my program, I have a section of code that updates a list box depending on the value of another list box.
The code that does this looks a little like this.
$listBox1->bind('<<ListboxSelect>>' => sub {
$listBox2->delete(0, 'end');
for(#{$hashOfArraysOfStruct{$listBox1->get($listBox1->curselection)}}) {
$listBox2->insert('end', $_->name);
}
});
This works fine. However, I have found it easier to simply use a list and manipulate the list on the <<ListboxSelect>>. I have bound this list to the list box using -listvariable.
The code to do this looks a bit like
$listBox1->bind('<<ListboxSelect>>' => sub {
#updateableList = #{$hashOfArraysOfStruct{$listBox1->get($listBox1->curselection)}};
});
The problem with this approach is, as hashOfArraysOfStruct, contains structures, the list box contains values such as MyStruct=HASH(0x31d7e3c).
Is there any way to display the name variable of the struct MyStruct without looping through the whole array and individually inserting each result into the list box?
MCVE
use strict;
use warnings;
use Tk;
use Class::Struct;
struct MyStruct => {
name => '$',
group => '$'
};
my %hashOfArraysOfStruct = (
A => [
MyStruct->new(name => 'Phil', group => 'A'),
MyStruct->new(name => 'Ian', group => 'A'),
MyStruct->new(name => 'George', group => 'A')
],
B => [
MyStruct->new(name => 'Mac', group => 'B'),
MyStruct->new(name => 'Will', group => 'B')
],
C => [
MyStruct->new(name => 'Cath', group => 'C'),
MyStruct->new(name => 'Thom', group => 'C'),
MyStruct->new(name => 'Richard', group => 'C'),
MyStruct->new(name => 'Paul', group => 'C'),
MyStruct->new(name => 'Nick', group => 'C')
]
);
my $mainWindow = MainWindow->new();
my #listOne = sort(keys %hashOfArraysOfStruct);
############################################
#Route One
#Less efficient as has to loop through all the values
#But it displays the name variable of MyStruct
my $listBox1 = $mainWindow->Scrolled("Listbox", -scrollbars => "osoe", -selectmode => "single", -listvariable => \#listOne)->pack;
my $listBox2 = $mainWindow->Scrolled("Listbox", -scrollbars => "osoe", -selectmode => "single")->pack;
$listBox1->bind('<<ListboxSelect>>' => sub {
$listBox2->delete(0, 'end');
for(#{$hashOfArraysOfStruct{$listBox1->get($listBox1->curselection)}}) {
$listBox2->insert('end', $_->name);
}
});
############################################
############################################
#Route Two
#Works but displays in the form of MyStruct=HASH(0x31d7e3c)
#my #updateableList;
#my $listBox1 = $mainWindow->Scrolled("Listbox", -scrollbars => "osoe", -selectmode => "single", -listvariable => \#listOne)->pack;
#my $listBox2 = $mainWindow->Scrolled("Listbox", -scrollbars => "osoe", -selectmode => "single", -listvariable => \#updateableList)->pack;
#$listBox1->bind('<<ListboxSelect>>' => sub {
# #updateableList = #{$hashOfArraysOfStruct{$listBox1->get($listBox1->curselection)}};
#});
############################################
############################################
#What I would like to happen
#I would like to use route two but when the struct is displayed
#in the list box, instead of being in Route Twos format, it should
#display the name variable of MyStruct.
############################################
MainLoop;
Massive Edit
Change
#updateableList = #{$hashOfArraysOfStruct{$listBox1->get($listBox1->curselection)}};
to
#updateableList = map { $_->name() } #{$hashOfArraysOfStruct{$listBox1->get($listBox1->curselection)}};
To extract the list of names from the list of structs;

Perl extracting data from a hash

I am trying to extract data from a hash value.
my $triggers = $zabbix->raw('trigger','get', $options);
print Dumper($triggers);
foreach my $trigger (#{$triggers} )
{
push #triggerid,$trigger->{'triggerid'};
my #t=$trigger->{'hosts'};
my $lt = localtime($trigger->{'lastchange'});
print "$trigger->{'description'} $lt \n";
}
Output of Dumper is
[
{
'hosts' => [
{
'hostid' => '19914',
'host' => 'pc10bcf18.syd.sf.priv'
}
],
'priority' => '2',
'status' => '0',
'templateid' => '10652913',
'comments' => '',
'state' => '0',
'triggerid' => '10653191',
'expression' => '{15070357}#1',
'error' => '',
'url' => '',
'flags' => '0',
'value' => '1',
'name' => 'pc10_BizX_A_CF',
'description' => 'pc10bcf18.syd.sf.priv: Core Path not \'/dumps/java/core\' (Path=/export/home/jboss/j...)',
'value_flags' => '0',
'lastchange' => '1429181103',
'type' => '0'
},
]
From my above code, i was able to print 'description'. How do i access and print the value of 'host' value?
To maintain the for / push pattern that you have already coded, you can write this
my $triggers = $zabbix->raw('trigger', 'get', $options);
my #triggerid;
for my $trigger ( #$triggers ) {
push #triggerid, $trigger->{triggerid};
my #hosts;
my $hosts = $trigger->{hosts};
for my $host ( #$hosts ) {
push #hosts, $host->{host};
}
my $lt = localtime($trigger->{lastchange});
print "$trigger->{description} $lt\n";
}
Looks like there can be more than one host, so
my #hosts =
map { $_->{host} }
#{ $trigger->{hosts} };
To get the first one (assuming there will always be at least one),
my $first_host = $trigger->{hosts}[0]{host};
$triggers->{'hosts'}->[0]->{'host'}

accessing Data::Dumper output

I have this Perl subroutine:
sub ask_for_lease {
my $url = '/sp/api/v1/lease';
my $formdata = '{"classname":"lease",}';
my $c = REST::Client->new();
$c->setHost($wizhost);
$c->PUT (
$url
, $formdata
, $headers
);
my $r = from_json($c->responseContent());
#print Dumper($r);
#my #results = $r->{'results'};
my #items = %{#{$r->{'results'}}[0]}->{'items'};
print Dumper(#items);
for my $item (#items) {
print "=============\n";
print Dumper($item);
print "=============\n";
}
}
It produces this output:
$VAR1 = [
{
'owner' => undef,
'notes' => 'Simulation One',
'version' => undef,
'status' => 'Ready',
'name' => 'One',
'lease' => '7070',
'_oid' => '1'
},
{
'owner' => undef,
'notes' => 'Simulation Two',
'version' => undef,
'status' => 'Ready',
'name' => 'Two',
'lease' => '2',
'_oid' => '2'
},
{
'owner' => undef,
'notes' => 'Simulation Three',
'version' => undef,
'status' => 'Ready',
'name' => 'Three 2012',
'lease' => '3',
'_oid' => '3'
},
...
];
=============
$VAR1 = [
{
'owner' => undef,
'notes' => 'Simulation One',
'version' => undef,
'status' => 'Ready',
'name' => 'One',
'lease' => '7070',
'_oid' => '1'
},
{
'owner' => undef,
'notes' => 'Simulation Two',
'version' => undef,
'status' => 'Ready',
'name' => 'Two',
'lease' => '2',
'_oid' => '2'
},
{
'owner' => undef,
'notes' => 'Simulation Three',
'version' => undef,
'status' => 'Ready',
'name' => 'Three 2012',
'lease' => '3',
'_oid' => '3'
},
...
];
=============
What I want to be able to do is iterate over the items array and print out the status and the name, but I am not sure I am dereferencing $r correctly.
The line
my #items = %{#{$r->{'results'}}[0]}->{'items'}
is very suspicious. You are extracting the first element of the array referred to by $r->{results}, dereferencing that as a hash, and using that hash in reference syntax. You should have got
Using a hash as a reference is deprecated
if you have use strict and use warnings in place as you should.
It is best to extract complex nested data in layers. In this case you can get the reference to the items array into a scalar variable and use that.
my $items= $r->{results}[0]{items};
for my $item ( #$items ) {
printf "name: %s, $item->{name};
printf "status: %s, $item->{status};
print "--\n";
}
If you post your JSON data then we will be able to help much better

Using DBI::CSV module of Perl. Get undef for empty string while parsing the csv

I'm using DBI::CSV module in Perl to parse my csv and run queries on the data.
My data is something like this
1001|23|1|loading
1012|25||loading
I want the 3rd field in the second row to be undef, which I'm not able to achieve. I'm getting the field as an empty string instead of undef with this is the piece of code I tried.
use strict;
use warnings "all";
use Text::CSV_XS;
use DBI;
my $dbh = DBI->connect( "dbi:CSV:", undef, undef, {
csv_sep_char => "|",
f_dir => ".",
csv_eol => "\n",
csv_empty_is_undef => 1,
csv_blank_is_undef => 1,
csv_quote_char => undef,
csv_escape_char => undef,
csv_always_quote => undef,
f_ext => ".csv",
f_enc => "utf-8",
csv_class => "Text::CSV_XS",
RaiseError => 1,
PrintError => 1
}
);
my #cols = ("col1", "col2", "col3", "col4");
$dbh->{'csv_tables'}{'info'} = { 'file' => "file.csv", col_names => \#cols };
my $result =$dbh->selectall_hashref( "select col1,col2,col3,col4 from info where col1 = 1012", "col1")
#gives the following result
0 HASH(0x992573c)
1012 => HASH(0x9900e90)
'col1' => 1012
'col2' => '25'
'col3' => ''
'col4' => 'loading'
I'm expecting the value of col3 as undef here.
Any help here would me appreciated. Thanks
You declare #cols as an array, but then you assign an array reference to it, i.e. you only initialize its first element. Do not use square brackets for arrays, only for array references:
my #cols = ( "col1", "col2", "col3", "col4" );
Replace:
$dbh->{'csv_tables'}{'info'} = { 'file' => "file.csv", col_names => \#cols };
With:
$dbh->{'csv_tables'}{'info'} = { 'file' => "file.csv", col_names => #cols };
Results in:
$VAR1 = {
'1012' => {
'col3' => undef,
'col1' => '1012',
'col2' => '25',
'col4' => 'loading'
}
};

strange bug accessing a variable in template toolkit

I'm struggling with a bug, that I can't nail down.
I have a function that takes a postcode, does a lookup, and returns a latitude, longitude and area name.
for example, pass it AD300 it returns (something like) 42.6, 1.55, ordino - it works very well.
The function is called like this:
my ($lat, $lng, $area) = $object->release();
The return values are fine, and I can print them in perl with a warn
warn "Area $area, $rellat, $rellng";
This works fine. "Area Ordino, 42.6, 1.55"
I then take one of these values, say $area, add it to a hash of data, and pass it to a web page where it is preprocessed via TT (as I do successfully with a load of other variables).
I'm assigning the value to the hash in the normal way. e.g.
$hash->{'area'} = $area;
Here is where the fun begins. When I try to reference the value in TT e.g. [% hash.area %]
I don't get "Ordino" printed on the web page, I'm told I've passed an Array reference to TT.
After a little debugging, I've found that my hash variable hash.area, is somehow referencing an array (according to TT) holding the three values that I've returned from the subroutine "release". I.e.
hash.area = [42.6, 1.55, ordino] according to TT.
That is, to get the value "Ordino" within the web page, I have to access [% hash.area.2 %].
Further, I can set $hash->{'area'} to equal any of the variables, $lat, $lng, or $area and get the same behavior. TT believes all three variables reference the same array. that is
$lat = $lng = $area = [42.6, 1.55, ordino] according to TT
This is bizare, I can happily print the variables in perl and they appears as normal - not an array. I've tried dumping the hash with dumper, no array, everything is fine. Yet somehow, TT is finding an array. It's doing my head in.
The site is quite large, with a lot of pages and I happily pass variables and hashes via TT to web pages all the time, and have been for 4 years now. I've never seen this. On other pages, I even pass exactly the same output from the "release" method and it is processed correctly.
I don't think my TT processing code is the problem, however the following is relevant.
my $tt = Template->new({
INCLUDE_PATH => [ #$template_directories ],
COMPILE_EXT => '.ttc',
COMPILE_DIR => '/tmp/ttc',
FILTERS => YMGN::View->filters,
PLUGIN_BASE => [ 'YMGN::V::TT::Plugins' ],
EVAL_PERL => 1
});
$self->{tt} = $tt;
$self->{template_directories} = $template_directories;
$self->{output} = $params->{output} || undef;
$self->{data} = $params->{data} || [];
The above creates a new tt object and is part of the "new" function (refed below).
"data" contains the hash. "output" holds the processed template ready to send to users browser. We call new (above), process the data and create the output with the code below.
sub process {
my $self = shift;
my $params = shift;
if (!ref $self || !exists $self->{tt}) {
my $class = $self;
$self = $class->new($params);
}
if (!$self->{output}) {
die "You need to specify output";
}
delete $self->{error};
$self->y->utils->untaint(\$self->{template});
my $rv = $self->{tt}->process(
$self->{template},
$self->{data},
$self->{output},
binmode => ':utf8',
);
if (!$rv) {
warn $self->{tt}->error();
return {
error => $self->{tt}->error(),
};
}
return 0;
}
All of the above is sanitised because there is a lot of other stuff going on.
I believe what's important is that the data going in looks correct, here is a full dump of the complete data that is being processed by tt (at the point of processing). The thing that is causing the problem is bubbles->[*]->{'release'} (note, that release == area in the data. The name was changed for unrelated reasons). As you can see, dumper thinks it's a string. TT deals with everything else fine.
data $VAR1 = {
'system' => {
system stuff
},
'features' => {
site feature config
},
'message_count' => '0',
'bubbles' => [
bless( {
'history' => [
{
'creator' => '73',
'points' => '10',
'screenname' => 'sarah10',
'classname' => 'Flootit::M::Bubbles',
'id' => '1378',
'updated' => '1352050471',
'type' => 'teleport',
'label' => 'teleport',
'class' => 'Flootit::M::Bubbles'
}
],
'creator' => '6',
'release' => 'Escaldes-Engordany',
'image' => 'http://six.flooting.com/files/833/7888.png',
'pop_time' => '1352050644',
'y' => $VAR1->{'y'},
'taken_by' => '0',
'city' => '3',
'title' => 'hey a new bubble',
'id' => '566',
'class' => 'Flootit::M::Bubbles',
'prize' => 'go for it kids'
}, 'Flootit::M::Bubbles' ),
bless( {
'history' => [
{
'creator' => '6',
'points' => '10',
'screenname' => 'sarah20',
'classname' => 'Flootit::M::Bubbles',
'id' => '1723',
'updated' => '1349548017',
'type' => 'teleport',
'label' => 'teleport',
'class' => 'Flootit::M::Bubbles'
},
{
'creator' => '6',
'points' => '5',
'screenname' => 'sarah20',
'classname' => 'Flootit::M::Bubbles',
'id' => '1732',
'updated' => '1349547952',
'type' => 'blow',
'label' => 'blow',
'class' => 'Flootit::M::Bubbles'
}
],
'creator' => '89',
'release' => 'Ordino',
'image' => 'http://six.flooting.com/files/1651/8035.png',
'pop_time' => '1351203843',
'y' => $VAR1->{'y'},
'taken_by' => '0',
'city' => '3',
'title' => 'test4',
'id' => '1780',
'class' => 'Flootit::M::Bubbles',
'prize' => 'asdfasdf dsadsasdfasdfasdf'
}, 'Flootit::M::Bubbles' ),
bless( {
'history' => [],
'creator' => '6',
'release' => 'Andorra la Vella',
'image' => 'http://six.flooting.com/files/1671/8042.png',
'pop_time' => '0',
'y' => $VAR1->{'y'},
'taken_by' => '0',
'city' => '3',
'title' => 'Pretty flowers, tres joli',
'id' => '1797',
'class' => 'Flootit::M::Bubbles',
'prize' => 'With lots of pretty pictures'
}, 'Flootit::M::Bubbles' ),
bless( {
'history' => [],
'creator' => '6',
'release' => 'Hillrise Ward',
'image' => 'http://six.flooting.com/files/1509/8003.png',
'pop_time' => '0',
'y' => $VAR1->{'y'},
'taken_by' => '0',
'city' => '3',
'title' => 'Test beats',
'id' => '1546',
'class' => 'Flootit::M::Bubbles',
'prize' => 'Sound great'
}, 'Flootit::M::Bubbles' )
]
};
What comes out after processing is this (in $output)
There is a
[% FOREACH floot IN bubbles %]
Floating around ARRAY(0xfaf5d448). from [% floot.release %]
if we make this [% floot.release.2 %] it gives the correct value.
All the other fields can be referenced correctly - go figure.
The code that puts "bubbles" together is;
my $bubbles = $y->model('Bubbles')->search(['type' => 'golden', 'image' => '!NULL',
'bubble_prizes' => ['p', { 'p.bubble' => 'self.id'}], ], {
order_by => '(created>CURRENT_DATE() AND thumbsup+thumbsdown<10) DESC, COALESCE(thumbsup,0)-COALESCE(thumbsdown,0) DESC, pop_time DESC',
count => 10,
fields => ['p.title as title', 'p.prize as prize', 'city', 'taken_by', 'pop_time', 'id', 'creator'],
});
for (my $i=0; $i<#$bubbles; $i++) {
# Find specified bubbles (see below for when not found here)
my ($rellat, $rellng, $area) = $bubbles->[$i]->release() ;
$bubbles->[$i]->{'release'} = $area;
}
}
The controller then takes $bubble, bundles it up with session / site data, puts it inside an anonymous hash (as you can see in the data above) and passes it to view for processing.
The code for release is :
sub release {
my $self = shift;
my $postcode = $self->y->model('Prizes')->find({bubble => $self->id})->postcode;
my ( $user_lat, $user_long, $region_name );
if($postcode)
{
( $user_lat, $user_long, $region_name ) = $self->y->api('Location')->from_postcode($postcode);
return ( $user_lat, $user_long, $region_name );
}
}
API::Location is quite large, however the relevant lines are;
$postcode_record = $self->y->model('GeoData')->find( {
source => "ALL_COUNTRIES_POSTCODES",
country => $country_code,
sourceid => $postcode, } );
return ( $postcode_record->latitude, $postcode_record->longitude, $postcode_record->town );
The data dumps I've shown you are taken from inside TT.pm (part of view).
So, any ideas what might be going on or where to start? What can I do to try and debug this further? I'm out of ideas.
Maybe it's because $area is a blessed object; Try this to convert to a scalar string :
$string = ''.$area;
# e.g.
$hash->{'area'} = ''.$area;
Following #Moritz comment, to check that $area is blessed:
print ref($area);
use Data::Dumper; warn Dumper($area);
And q{""} overloaded:
print defined ${ref($area).'::'}{'(""'};
EDIT
sub release can return
- undef if $postcode evaluate to false
- a list but as it is used in as scalar context returns the last argument $region_name like parenthesized list (comma expression)
sub release {
my $self = shift;
my $postcode = $self->y->model('Prizes')->find({bubble => $self->id})->postcode;
my ( $user_lat, $user_long, $region_name );
if($postcode)
{
( $user_lat, $user_long, $region_name ) = $self->y->api('Location')->from_postcode($postcode);
return ( $user_lat, $user_long, $region_name );
}
}
It will be relevant to Dump $region_name or $area, or to look at from_postcode.
I found that the problem went away on other development servers and the production server.
I therefore tried uninstalling and reinstalling TT, however that didn't help.
As it appears it's an environment issue on my dev server, so I am retiring the box and starting a new one.