JusaEngineer has asked for the wisdom of the Perl Monks concerning the following question:

I know what I want to do but I can't find the right syntax to accomplish it. I get pieces of the puzzle, but I can't solve all of them to put them together. So I'll give an example hash of what I have, and the End result I'm looking for, and some pieces that I have solved. I tried to get some answers to single questions but there seem to be so many ways to accomplish them, I don't know how to take the answer and use it to what I understand how to solve my problem. So Here goes, I'm going to try to be as detailed as possible so I'm sorry if it gets long. To get this data I'm parsing a XML file and printing to a .tex file.

My goal is to pars an XML file and pull out all the different types of buttons and numeric entries and collect them in a hash (I know how to do this), Then I need to create a document that will have this data sorted in columns to display on a table. I need to be able to count 14 max per page while going through screens with varying numbers of buttons. At the end of each button that is not the last button I need a partial line "\cline{2-11}". At the end of all buttons of one screen I need a full horizontal Line "\hline". At the end of the page(14lines) I need to enter"\hline\pagebreak". I'm going to change the 14 Line for this to 5 so that not as much data is needed.


Here is my example Hash

use warnings; use strict; use Data::Dumper; my %hash = ( 'Alarms.Alarm Acknowledge' => { 'ScreenName' => 'Alarms', 'Description' => 'Alarm Acknowledge', 'Type' => 'On/Off' }, 'Flow Diagram.Sequence Hold' => { 'ScreenName' => 'Flow Diagram', 'Description' => 'Sequence Hold', 'Type' => 'Momentary' }, 'Flow Diagram.Sequence Advance' => { 'ScreenName' => 'Flow Diagram', 'Description' => 'Sequence Advance', 'Type' => 'Momentary' }, 'Flow Diagram.Sanitize Enable/Disable' => { 'ScreenName' => 'Flow Diagram', 'Description' => 'Sanitize Enable/Disable', 'Type' => 'On/Off' }, 'Trend to USB.Start/Stop Trend Toggle' => { 'ScreenName' => 'Trend to USB', 'Description' => 'Start/Stop Trend Toggle', 'Type' => 'On/Off' }, 'Alarms.Alarm Reset' => { 'ScreenName' => 'Alarms', 'Description' => 'Alarm Reset', 'Type' => 'On/Off' }, 'Alarms.Alarm Trigger' => { 'ScreenName' => 'Alarms', 'Description' => 'Alarm Trigger', 'Type' => 'Momentary' }, ); print Dumper(%hash); print "\n\n\n"; my %ScreenNameCount; $ScreenNameCount{$hash{$_}{ScreenName}}++ foreach keys %hash; my %HashCount; my $Screenkey = 'New'; my $ScreenkeyOld = 'Old'; my @List; my $i = 1; foreach my $key (sort keys %hash) { my $Screenkey = $hash{$key}{'ScreenName'}; my $Description = $hash{$key}{'Description'}; my $Type = $hash{$key}{'Type'}; $HashCount{$Screenkey} = $Screenkey; $HashCount{$Screenkey}= {0 => $ScreenNameCount{$Screenkey}}; if ($Screenkey eq $ScreenkeyOld) { $HashCount{$Screenkey} = {$i => "\\t \\t \\t \\t\& \\mytabhead{$De +scription} \\t\& $Type \\t\\\\ \\hline \\n $i - $Screenkey - $Screenk +eyOld"}; $i++; } else { $HashCount{$Screenkey} = {$i => "\\mytabhead{$Screenkey} \\t\& \\m +ytabhead{$Description} \\t\& $Type \\t\\\\ \\hline \\n $i - $Screenke +y - $ScreenkeyOld"}; $i=1; } $ScreenkeyOld = $Screenkey; } print "\n\n\n"; print Dumper(%HashCount); print "\n\n\n"; print Dumper \%ScreenNameCount; print "\n\n\n";

The .tex file should look what is below. "Trend to USB" has one button, so since the first button is the last it needs to end with \hline. "Alarms" has three and they all fit on the same page so the first 2 buttons end in \cline {2-11} and the last end in \hline. "Flow Diagram" has three buttons also but there is only room for one more button on the page. So the first button will end in \hline \pagebreak thus ending the page and restarting the count to 5. The next two buttons follow the same format, First button on the new page (the Second Flow Diagram Button) will end in \Cline{2-11}, and the last will end in \hline. The first button on the page must also start with "\\mytabhead{Flow Diagram}" verses the three tabs.


\multirow{1}{0.85in}{\mytabhead{Trend to USB}} & \mytabhead{Start/ +Stop Trend Toggle} & On/Off \hline \multirow{3}{0.85in}{\mytabhead{Alarms}} & \mytabhead{Alarm Tr +igger} & Momentary \cline{2-11} & \mytabhead{Alarm Ack +nowledge} & On/Off \cline{2-11} & \mytabhead{Alarm Res +et} & On/Off \hline \multirow{1}{0.85in}{\mytabhead{Alarms}} & \mytabhead{Sequence + Advance} & Momentary \hline\pagebreak \multirow{2}{0.85in}{\mytabhead{Alarms}} & \mytabhead{Sequence + Hold} & On/Off \cline{2-11} & \mytabhead{Sanitize +Enable/Disable} & On/Off \hline

I'm able to get the hash to look like this with the code above:


$VAR1 = 'Trend to USB'; $VAR2 = { '3' => '\\mytabhead{Trend to USB} \\t& \\mytabhead{Start/Sto +p Trend Toggle} \\t& On/Off \\t\\\\ \\hline \\n 3 - Trend to USB - Fl +ow Diagram' }; $VAR3 = 'Alarms'; $VAR4 = { '2' => '\\t \\t \\t \\t& \\mytabhead{Alarm Trigger} \\t& Mom +entary \\t\\\\ \\hline \\n 2 - Alarms - Alarms' }; $VAR5 = 'Flow Diagram'; $VAR6 = { '2' => '\\t \\t \\t \\t& \\mytabhead{Sequence Hold} \\t& Mom +entary \\t\\\\ \\hline \\n 2 - Flow Diagram - Flow Diagram' };

When I want it to look like this:


$VAR1 = 'Trend to USB'; $VAR2 = { '0' => 1, '1' => '\\mytabhead{Trend to USB} \\t& \\mytabhead{Start/Sto +p Trend Toggle} \\t& On/Off \\t\\\\ \\hline \\n 1 - Trend to USB - Ol +d' }; $VAR3 = 'Alarms'; $VAR4 = { '0' => 3, '1' => '\\mytabhead{Alarms} \\t& \\mytabhead{Alarm Trigger} + \\t& Momentary \\t\\\\ \\hline \\n 1 - Alarms - Trend to USB' '2' => '\\t \\t \\t \\t& \\mytabhead{Alarm Acknowledge} \\t& + On/Off \\t\\\\ \\hline \\n 2 - Alarms - Alarms' '3' => '\\t \\t \\t \\t& \\mytabhead{Alarm Reset} \\t& On/Of +f \\t\\\\ \\hline \\n 3 - Alarms - Alarms' }; $VAR5 = 'Flow Diagram'; $VAR6 = { '0' => 3, '1' => '\\mytabhead{Flow Diagram} \\t& \\mytabhead{Sequence + Advance} \\t& Momentary \\t\\\\ \\hline \\n 1 - Flow Diagram - Alarm +s' '2' => '\\t \\t \\t \\t& \\mytabhead{Sequence Hold} \\t& Mom +entary \\t\\\\ \\hline \\n 2 - Flow Diagram - Flow Diagram' '3' => '\\t \\t \\t \\t& \\mytabhead{Sanitize Enable/Disable +} \\t& On/Off \\t\\\\ \\hline \\n 3 - Flow Diagram - Flow Diagram' };


From here my thought was to use the button count ($hash{ScreenName}{0}) for each button to fill in the number on the .tex file (\multirow{???}) and count up to the Button count while also tracking buttons per page and somehow splitting the remaining and putting on the next page.


I know now I'm in way over my head lol please help me get further in this task. I don't want to have to do this manually in a word document. Thanks you for any help.

Mel

Replies are listed 'Best First'.
Re: Hash Manipulation
by Fletch (Bishop) on Jun 07, 2021 at 18:41 UTC

    This may sound a bit like pushing you out into deeper water, but you probably should look into the Template module (see also template-toolkit.org). Rather than trying to whip up your formatted data in your hash, concentrate on extracting just the info you want to appear in your output and pass that to a template which has placeholders in your TeX output. That lets you separate manipulating your data from the presentation (and the template lets you more easily get the output in the right "shape" and you just have to pass the right things to populate the placeholders).

    use 5.010; use Template; my $t = Template->new; my $document_template = <<'EOT'; [% FOR item IN rows.keys %] %% Item [% loop.count %] of [% loop.size %] \multirow{1}{0.85in}{\mytabhead{[% item %]}} & \mytabhead{[% rows.$ite +m.Description %]} & [% rows.$item.Type %] \hline [% END %] EOT my $output_text = q{}; my $context = {}; $context->{rows} = \%hash; # from your original data $t->process( \$document_template, $context, \$output_text ) or die qq{Template error: }, $t->error, qq{\n}; say $output_text;

    Edit: Two things came to mind after seeing haukex' reply below:

    I didn't look closely at your manipulation code (because what you're trying to do made my eyes water . . .) but he's completely right about you overwriting things reassigning new hashrefs. Similarly using integers as the keys of a hash is a sign you prossibly want a different data structure using an arrayref instead. Using an array(ref) and push would maybe make some of your problems with tracking the indexen and positions easier. Checkout the data structures cookbook for more details (perldsc; look for an AoH, Array Of Hashes).

    Also another TT feature that may be of use is the loop special variable that gives you access to an iterator object inside the template FOR loop (added a comment using it to sample above). You could use that and arithmetic to alter the output based on which element you're on to get your pagination / special breaks. Alternately you could use something like List::MoreUtils and its natatime to partition your list into whatever size chunks.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Thank you for your answers, It's just going to take me a little while before I can decipher them lol. You are far more advanced than I am at understanding perl. My code is clunky because I don't know better ways to accomplish what I need. So I try to take a bunch of baby stems then see if I can put those baby steps together for less steps. I have about 500 buttons over about 20 screens and I can only have 14 per page. I know how to pull the data from the XML file because I use this file to make other documents.

      I'm able to get most of what I need formatted. I'll make a new post with the simplified hash so it's easier to read. Again thanks for your help!

Re: Hash Manipulation
by haukex (Archbishop) on Jun 08, 2021 at 07:20 UTC

    The main issue with the code you've shown is that in the three places you're doing something like $HashCount{$Screenkey} = {$i => "..."}, you're replacing what's stored in $HashCount{$Screenkey} with a new reference to a new anonymous hash, instead of adding something to the existing hash. You can delete the line $HashCount{$Screenkey} = $Screenkey; since that value is going to get replaced anyway. Then, you can replace the three aforementioned hash accesses with $HashCount{$Screenkey}{0} and $HashCount{$Screenkey}{$i} respectively - what this does is take advantage of a Perl feature called autovivification, which means that simply accessing $HashCount{$Screenkey} as if it were a reference to a hash causes a new anonymous hash to be created for you.

    I think you've also got a logic issue with $i - in the case that $Screenkey ne $ScreenkeyOld, I am guessing that you probably want to start the numbering over - but you're only resetting $i=1 after already having used its previous value. The same goes for $i++. If I fix this, the next issue is that I think you've got a mistake in your sample data: since you're iterating over the sorted keys of %hash, Alarms will come first, but in your sample data you seem to expect the order to be Trend to USB, Alarms, and Flow Diagram. I haven't fixed this issue in the code below.

    In general, note that you have two unused variables, @List and the outer $Screenkey, since you're only using the second $Screenkey defined inside the loop. Also, your code would benefit from better indentation - perltidy can help with that. And you can do away with %ScreenNameCount, since you can do that operation directly in %HashCount.

    Putting all of that together (as mentioned above the test will fail since I haven't changed the order of the expected output you showed in your question):

      Thank you for your insight. You have solved some of my issues and moved me along. I'm getting the data in the format I would like it. I am able to print the screens with the correct number of buttons in the "\multirow{??}".
      The only issue I have now that is keeping me from completing this is, counting down buttons available per page and then formatting the changes to "\multirow{??}" as it is effected by being split between pages.
      Sorry about the order of the sorted elements, I wasn't thinking about it when I was typing up the sample result. I was trying to make sure that a $key was page broken.

      This is what I have now. I think that if I manipulate a few more if statements I can count down remaining buttons and check that each time that $ele = 0, to determine if I need to split it and update the "\multirow{??}". I may need to count down buttons per screen remaining also.

      my $MaxButtons = 5; my $RemainingButtons = $MaxButtons; my $ButtonsOnPage = 0; for $key (sort keys %HashCount) { for $ele (sort keys %{$HashCount{$key}}) { if ($ele eq 0){ } else { if ($ele eq 1){ print "\\multirow{$HashCount{$key}->{0}}{0.85in}{\\myt +abhead{$key}} \& " . $HashCount{$key}->{$ele} . "\n"; } else { print " \& " . $HashCount{$key}->{$ele} . "\n"; } } } }

        In your code you have this block (I've shortened the print statements for clarity):

        if ($ele eq 0){ } else { if ($ele eq 1){ print "Condition: ele is 1\n"; } else { print "Condition: ele is neither 1 nor 0\n"; } }

        This is confusing because of the empty block straddling the first 2 lines. There are a couple of ways to avoid it in general. One is to negate the condition:

        if ($ele ne 0){ if ($ele eq 1){ print "Condition: ele is 1\n"; } else { print "Condition: ele is neither 1 nor 0\n"; } }

        The other is to turn the if into an unless:

        unless ($ele eq 0){ if ($ele eq 1){ print "Condition: ele is 1\n"; } else { print "Condition: ele is neither 1 nor 0\n"; } }

        Either of these removes the empty block, so that's a plus. But we can go farther. The logic can be simplified because really you have only 2 conditions as indicated by my rewritten print statements. So we can remove the outer condition by folding it into the else like so:

        if ($ele eq 1){ print "Condition: ele is 1\n"; } elsif ($ele ne 0) { print "Condition: ele is neither 1 nor 0\n"; }

        Finally, deducing from your previous code that $ele is only ever a whole number you can switch to using numerical comparisons rather than string comparisons (== instead of eq, etc.). Perhaps this is the clearest:

        if ($ele == 1) { print "Condition: ele is 1\n"; } elsif ($ele > 1) { print "Condition: ele is neither 1 nor 0\n"; }

        I understand that you are just hacking about with this code but wanted to point out these alternative ways of going about the logical if-blocks. The simpler you can make it the fewer bugs can creep in and that has to be a good thing.


        🦛