Stringify Struct in Perl - 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;

Related

Rearrange data in a PDF file

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
}

Tk::MListbox doesn't expand

I would like to create a Tk application with a MListbox to display some data. If there is too much information, I would like that a scrollbar appears.
My problem is that the MListbox doesn't fill all the available space. There is a blank space on the right. It doesn't look very nice.
Is it possible to solve this problem? Or should I use another widget? (TableMatrix seems interesting but I can't download it). I chose MLlistbox because I want to be able to hide some columns and change the size of each column.
This is the code I had so far:
my $frameDocuments = $mw->Frame(-background => '#CCCCFF');
$documentsListbox = $frameDocuments->Scrolled(
'MListbox',
-scrollbars => 'osoe',
-columns => [
[-text => 'Name'], [-text => 'Path'], [-text => 'Format'],
[-text => 'Loader Type'], [-text => 'Cache directory']
],
-resizeable => 1,
-moveable => 1,
-sortable => 1,
-selectmode => 'browse',
);
$frameDocuments->pack(-anchor => "n",-expand => "1",-fill => "both",-side => "top");
$documentsListbox->pack(-anchor => "n",-expand => "1",-fill => "both",-side => "top");
It seems like the Tk::MListbox does not resize its own columns when the window width becomes larger than the sum of the column widths. Seems like a bug, maybe you should report it?
Anyway, you can try work around it by using columnPack function. According to the documentation:
$ml->columnPack(array)
Repacks all columns in the MListbox widget
according to the specification in array. Each element in array is a
string on the format index:width. index is a column index, width
defines the columns width in pixels (may be omitted). The columns are
packed left to right in the order specified by by array. Columns not
specified in array will be hidden.
Here is an example1 where I maximize the window to fill the whole screen, and then compute the column widths:
#! /usr/bin/env perl
use strict;
use warnings;
use Tk;
use Tk::MListbox;
my $mw = MainWindow->new();
my $frameDocuments = $mw->Frame(-background => '#CCCCFF');
my #columns = (
[-text => 'Name'],
[-text => 'Path'],
[-text => 'Format'],
[-text => 'Loader Type'],
[-text => 'Cache directory']
);
my $numCols = scalar #columns;
my $documentsListbox = $frameDocuments->Scrolled(
'MListbox',
-scrollbars => 'osoe',
-columns => \#columns,
-resizeable => 1,
-moveable => 1,
-sortable => 1,
-selectmode => 'browse',
);
$frameDocuments->pack(
-anchor => "n",
-expand => "1",
-fill => "both",
-side => "top"
);
$documentsListbox->pack(
-anchor => "n",
-expand => "1",
-fill => "both",
-side => "top"
);
my $screenHeight = $mw->screenheight;
my $screenWidth = $mw->screenwidth;
$mw->geometry( sprintf "%dx%d+0+0", $screenWidth, $screenHeight );
my $colWidth = int( $screenWidth / $numCols );
my #ar = map { "$_:$colWidth" } 0 .. ($numCols - 1);
$documentsListbox->columnPack(#ar);
MainLoop;
Resulting window:
Footnotes:
I used camelCase for variable names in the code snippet since you already used it in your question. Note that snake_case is more common in Perl.

Inserting one hash into another using Perl

I've tried many different versions of using push and splice, but can't seem to combine two hashes as needed. Trying to insert the second hash into the first inside the 'Item' array:
(
ItemData => { Item => { ItemNum => 2, PriceList => "25.00", UOM => " " } },
)
(
Alternate => {
Description => "OIL FILTER",
InFile => "Y",
MfgCode => "FRA",
QtyAvailable => 29,
Stocked => "Y",
},
)
And I need to insert the second 'Alternate' hash into the 'Item' array of the first hash for this result:
(
ItemData => {
Item => {
Alternate => {
Description => "OIL FILTER",
InFile => "Y",
MfgCode => "FRA",
QtyAvailable => 29,
Stocked => "Y",
},
ItemNum => 2,
PriceList => "25.00",
UOM => " ",
},
},
)
Can someone suggest how I can accomplish this?
Assuming you have two hash references, this is straight-forward.
my $item = {
'ItemData' => {
'Item' => {
'PriceList' => '25.00',
'UOM' => ' ',
'ItemNum' => '2'
}
}
};
my $alt = {
'Alternate' => {
'MfgCode' => 'FRA',
'Description' => 'OIL FILTER',
'Stocked' => 'Y',
'InFile' => 'Y',
'QtyAvailable' => '29'
}
};
$item->{ItemData}->{Item}->{Alternate} = $alt->{Alternate};
The trick here is not to actually merge $alt into some part of $item, but to only take the specific part you want and put it where you want it. We take the Alternate key from $alt and put it's content into a new Alternate key inside the guts of $item.
Adam Millerchip pointed out in a hence deleted comment that this is not a copy. If you alter any of the keys inside of $alt->{Alternative} after sticking it into $item, the data will be changed inside of $item as well because we are dealing with references.
$item->{ItemData}->{Item}->{Alternate} = $alt->{Alternate};
$alt->{Alternate}->{InFile} = 'foobar';
This will actually also change the value of $item->{ItemData}->{Item}->{Alternate}->{InFile} to foobar as seen below.
$VAR1 = {
'ItemData' => {
'Item' => {
'ItemNum' => '2',
'Alternate' => {
'Stocked' => 'Y',
'MfgCode' => 'FRA',
'InFile' => 'foobar',
'Description' => 'OIL FILTER',
'QtyAvailable' => '29'
},
'UOM' => ' ',
'PriceList' => '25.00'
}
}
};
References are supposed to do that, because they only reference something. That's what's good about them.
To make a real copy, you need to dereference and create a new anonymous hash reference.
# create a new ref
# deref
$item->{ItemData}->{Item}->{Alternate} = { %{ $alt->{Alternate} } };
This will create a shallow copy. The values directly inside of the Alternate key will be copies, but if they contain references, those will not be copied, but referenced.
If you do want to merge larger data structures where more than the content of one key needs to be merged, take a look at Hash::Merge instead.

Perl push values in a hash

I am always confusing or don't know how to handle hash in perl.
So here is the problem,
Considering the whole thing, i am trying to change the key name in the below hash.
my %hash_new = {
'customername' => 'Lee & toys',
'employee_name' => 'Checngwang',
'customer_id' => 'X82349K',
'customer_address' => 'classic denver ranch, meadows drive',
'types' => 'category la',
};
my %selectCols = ('customername' => 'CUSTOMERNAME','employee_name' => 'EMP_NAME','customer_id' => 'cusid','customer_address' => 'cusaddr','types' => 'Typs');
my %new_hash = ();
foreach my $hash_keys (keys %hash_new){
my $newKey = $selectCols{$hash_keys};
$new_hash{$newKey} = $hash_new{$hash_keys};
}
print Dumper %new_hash;
Output of %new_hash is something like a key value combination of continuous string as below,
CUTOMERNAMELee & toysEMP_NAMEChecngwangcus_idX82349Kcusaddrclassic denver ranch, meadows driveTypscategory la
But instead of this, i need the hash like,
$VAR1 = {
'CUSTOMERNAME' => 'Lee & toys',
'EMP_NAME' => 'Checngwang',
'cusid' => 'X82349K',
'cusaddr' => 'classic denver ranch, meadows drive',
'Typs' => 'category la',
};
Please help me around this!
If I understood you correctly, then this works:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %hash_new = (
'customername' => 'Lee & toys',
'employee_name' => 'Checngwang',
'customer_id' => 'X82349K',
'customer_address' => 'classic denver ranch, meadows drive',
'types' => 'category la'
);
my %selectCols = (
'customername' => 'CUSTOMERNAME',
'employee_name' => 'EMP_NAME',
'customer_id' => 'cusid',
'customer_address' => 'cusaddr',
'types' => 'Typs'
);
my %new_hash = ();
foreach my $hash_keys (keys %hash_new){
my $newKey = $selectCols{$hash_keys};
$new_hash{$newKey} = $hash_new{$hash_keys};
}
print Dumper \%new_hash;
The only code I changed in your code was using () instead of {} in %hash_new and escaped the % in the Dumper statement. The % should be escaped because Dumper expects a reference, not a hash (that's true also for all other Perl variable types in use with Dumper).
Output:
$VAR1 = {
'Typs' => 'category la',
'cusaddr' => 'classic denver ranch, meadows drive',
'EMP_NAME' => 'Checngwang',
'cusid' => 'X82349K',
'CUSTOMERNAME' => 'Lee & toys'
};
Also, don't use confusing names like %hash_new and %new_hash. It's - well - confusing.

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'
}
};