File Coverage

blib/lib/HTML/Processor.pm
Criterion Covered Total %
statement 12 509 2.3
branch 0 158 0.0
condition 0 137 0.0
subroutine 4 48 8.3
pod 6 36 16.6
total 22 888 2.4


line stmt bran cond sub pod time code
1             package HTML::Processor;
2              
3 1     1   6148 use strict;
  1         3  
  1         49  
4 1     1   5 use vars qw($VERSION $syntax_pre $syntax_post @ISA);
  1         4  
  1         2772  
5              
6             require Exporter;
7              
8             @ISA = qw(Exporter);
9              
10             $VERSION = '0.2.1';
11              
12             #----------------------------------------------
13             # Constructor for Template Object
14             #----------------------------------------------
15             sub new {
16             # new Template
17 0     0 0   my $proto = shift;
18 0           my $config_ref = shift; # hash of config options
19            
20             # BEGIN CONFIG ////////////////////////////////
21             # These are template defaults and can be changed
22             # prior to installation or over-ridden when
23             # setting up the object
24 0           my %config = (
25             clean => 0,
26             escape => 0,
27             debug => 'Off',
28             footprint => 1,
29             syntax_pre => '\[TPL ',
30             syntax_post => '\]'
31             );
32             # END CONFIG //////////////////////////////////
33            
34            
35 0   0       my $class = ref($proto) || $proto;
36 0           my $self = {
37             PATHS => {},
38             DEBUG_LEVELS=> {
39             Fatal => 3,
40             Normal => 2,
41             Verbose => 1,
42             Off => 0
43             },
44             DEBUG_LEVEL => {},
45             FOOTPRINT => {},
46             CLEAN => {},
47             ESCAPE => {},
48             NESTS => {},
49             LOOPS => {},
50             VARIABLES => {},
51             OPTIONS => {},
52             INCLUDES => {},
53             HEADER => undef
54             };
55            
56             # set up configs with passed data or default
57 0   0       $self->{ DEBUG_LEVEL } = $self->{DEBUG_LEVELS}{ $config_ref->{ debuglevel } } || $self->{DEBUG_LEVELS}{$config{ debug }};
58 0   0       $self->{ FOOTPRINT } = $config_ref->{ footprint } || $config{ footprint };
59 0   0       $self->{ CLEAN } = $config_ref->{ clean } || $config{ clean };
60 0   0       $self->{ ESCAPE } = $config_ref->{ escape } || $config{ escape };
61 0   0       $syntax_pre = $config_ref->{ syntax_pre } || $config{ syntax_pre };
62 0   0       $syntax_post = $config_ref->{ syntax_post } || $config{ syntax_post };
63 0           bless($self, $class);
64            
65             # debug the debug level!
66 0 0 0       if($config_ref->{ debuglevel } && !exists $self->{DEBUG_LEVELS}{ $config_ref->{ debuglevel } }){
67 0           $self->debug("param",3,"Invalid debug level: '$config_ref->{ debuglevel }' not one of (". join(", ", keys %{$self->{DEBUG_LEVELS}}) . ")",3);
  0            
68             }
69 0           return $self;
70             }
71              
72              
73             #----------------------------------------------
74             # Constructor for Loop Object
75             #----------------------------------------------
76             sub new_loop {
77 0     0 0   my $self = shift;
78 0           my $name = shift;
79 0           my $nest = shift;
80            
81 0 0         if($nest){
82 0           push @{ $self->{ NESTS }->{$name}->{ KEYS } }, $nest;
  0            
83 0           $name = "${name}~${nest}";
84             }
85 0           $self->{ LOOPS }->{$name} = HTML::Processor::Loop->new();
86 0           return $self->{ LOOPS }->{$name}; # return a handle
87             }
88              
89             #----------------------------------------------
90             # build variables
91             #----------------------------------------------
92             sub variable {
93 0     0 1   my $self = shift;
94 0           my $name = shift;
95 0           my $val = shift;
96              
97 0 0         $self->{ VARIABLES }{$name} = $val if( defined $val );
98 0           return $self->{ VARIABLES }{$name};
99              
100             }
101              
102             #----------------------------------------------
103             # concatenate variable value with input
104             #----------------------------------------------
105             sub concat {
106 0     0 0   my $self = shift;
107 0           my $name = shift;
108 0           my $val = shift;
109 0           my $invert = shift;
110            
111 0 0         if ( $invert ) {
112 0           $self->{ VARIABLES }{$name} = $val . $self->{ VARIABLES }{$name};
113             }
114             else{
115 0           $self->{ VARIABLES }{$name} .= $val;
116             }
117 0           return $self->{ VARIABLES }{$name};
118             }
119              
120             #----------------------------------------------
121             # perform basic maths on a variable
122             #----------------------------------------------
123             sub math {
124 0     0 1   my $self = shift;
125 0           my ( $var, $val, $opr, $invert ) = @_;
126 0           my $result;
127 0           my %operands = (
128             '+' => \&addition,
129             '-' => \&subtraction,
130             '*' => \&multiplication,
131             '/' => \&division,
132             );
133             # check if this is a valid variable
134 0 0         if(!$self->{ VARIABLES }{$var}){
    0          
135 0           $self->debug("process",3,"'$var' is an un-declared variable, can't do math");
136             }
137             # check if this is a valid operand
138             elsif(!$operands{$opr}){
139 0           $self->debug("process",3,"'$opr' is not a valid math operand from('+', '-', '*','/')");
140             }
141             else {
142             # retrieve the values
143 0 0         my $val_val = ( exists $self->{ VARIABLES }{ $val } ) ? $self->{ VARIABLES }{ $val } : $val;
144 0           my $var_val = $self->{ VARIABLES }{ $var };
145              
146 0 0         my @pair = ($invert) ? ($val_val, $var_val) : ($var_val, $val_val);
147 0           $result = &{ $operands{$opr} }( \@pair );
  0            
148             }
149 0           $self->variable($var, $result);
150 0           return $result;
151             }
152              
153             #----------------------------------------------
154             # build included files
155             #----------------------------------------------
156             sub include {
157 0     0 0   my $self = shift;
158 0           my $name = shift;
159 0           my $file = shift;
160            
161 0 0         $self->{ INCLUDES }{ $name } = $file if ( $file );
162 0           return $self->{ INCLUDES }{ $name };
163             }
164             #----------------------------------------------
165             # Add base paths
166             #----------------------------------------------
167             sub add_path {
168 0     0 0   my $self = shift;
169 0           my $path = shift;
170            
171 0           $self->{ PATHS }->{ $path }++;
172             }
173              
174             #----------------------------------------------
175             # build options
176             #----------------------------------------------
177             sub option {
178 0     0 0   my $self = shift;
179 0           my $name = shift;
180 0           my $val = shift;
181            
182 0 0         $self->{ OPTIONS }{ $name } = $val if ( $val );
183 0           return $self->{ OPTIONS }{ $name };
184             }
185              
186             #----------------------------------------------
187             # Print a value and exit
188             #----------------------------------------------
189             sub print_die {
190 0     0 0   my $self = shift;
191 0           my $data = shift;
192            
193 0           print "Content-type: text/html \n\n";
194 0           print $data;
195 0           exit;
196             }
197              
198             #----------------------------------------------
199             # print content inline 4 debugging
200             #----------------------------------------------
201             sub print {
202 0     0 0   my $self = shift;
203 0           my $data = shift;
204 0           my $line_end = shift;
205 0 0         if(!$self->{ HEADER }){
206             # print a header on first pass
207 0           print "Content-type: text/html \n\n";
208 0           $self->{ HEADER } = 1;
209             }
210 0   0       print $data . ($line_end || "
\n");
211             }
212              
213             #----------------------------------------------
214             # Print a value and exit
215             #----------------------------------------------
216             sub error {
217 0     0 0   my $self = shift;
218 0           my $data = shift;
219 0           my $app = shift;
220            
221 0           my( $file, $line, $pack, $sub ) = id(1);
222 0           print "Content-type: text/html \n\n";
223 0           my $out = qq|

$app Software Error:

224            
$data 
225             at: $file line: $line
226            
227             |;
228 0           print $out;
229 0           exit;
230             }
231              
232             #----------------------------------------------
233             # Set clean on||off
234             #----------------------------------------------
235             sub set_clean {
236 0     0 0   my $self = shift;
237 0           my $state = shift;
238            
239 0   0       $self->{ CLEAN } = $state || 0;
240             }
241              
242             #----------------------------------------------
243             # Set escape on||off
244             #----------------------------------------------
245             sub set_escape {
246 0     0 0   my $self = shift;
247 0           my $state = shift;
248              
249 0   0       $self->{ ESCAPE } = $state || 0;
250             }
251             #----------------------------------------------
252             # Set footprint on||off
253             #----------------------------------------------
254             sub set_footprint {
255 0     0 0   my $self = shift;
256 0           my $state = shift;
257            
258 0   0       $self->{ FOOTPRINT } = $state || 0;
259             }
260              
261             #----------------------------------------------
262             # Set config options
263             #----------------------------------------------
264             sub set_config {
265 0     0 0   my $self = shift;
266 0           my $state = shift;
267              
268             # define cofig for various states
269 0           my %states = (
270             'email' => {
271             FOOTPRINT => 0,
272             CLEAN => 0
273             },
274             'default' => {
275             FOOTPRINT => 1,
276             CLEAN => 1
277             }
278             );
279             # set config params according to passed state
280 0 0         if( $states{$state} ){
281 0           foreach my $func(keys %{ $states{$state} }){
  0            
282 0           $self->{$func} = $states{$state}->{$func};
283             }
284             }
285             }
286              
287             #----------------------------------------------
288             # Sort a loop by one of its keys
289             #----------------------------------------------
290             sub sort {
291 0     0 0   my $self = shift;
292 0           my $sortby = shift;
293            
294              
295             # return if there is not sort by values
296 0 0         return unless $sortby;
297            
298 0           $self->{ SORTBY } = $sortby;
299 0           my ( $dir, $loop, $sort_on );
300 0           my @sorts = reverse split( /-/, $sortby );
301            
302             # test the sort key for: dir - sorton - loop
303 0 0 0       if ( @sorts == 3 ) {
    0 0        
    0 0        
    0 0        
304 0           $dir = lc $sorts[0];
305 0           $sort_on = $sorts[1];
306 0           $loop = $sorts[2];
307             }
308             elsif (@sorts == 2 && (lc $sorts[0] eq "asc" || lc $sorts[0] eq "desc")){
309 0           $dir = lc $sorts[0];
310 0           $sort_on = $sorts[1];
311             }
312             elsif (@sorts == 2 && (lc $sorts[0] ne "asc" || lc $sorts[0] ne "desc")){
313 0           $dir = "asc";
314 0           $sort_on = $sorts[0];
315 0           $loop = $sorts[1];
316             }
317             elsif (@sorts == 1){
318 0           $dir = "asc";
319 0           $sort_on = $sorts[0];
320             }
321            
322             # if we don't have an loop to sort on yet - go find one
323 0           my @multiples;
324 0 0         if ( !$loop ) {
325 0           foreach my $loop_name ( keys %{ $self->{ LOOPS } }){
  0            
326             # use the last loop that tests true
327 0 0         if($self->{ LOOPS }->{ $loop_name }->{ $sort_on }){
328 0           $loop = $loop_name;
329 0           push @multiples, $loop_name;
330             }
331             }
332             # die if we have more than 1 possible loop
333 0 0         $self->debug("parse",
334             3,
335             "sort called without specifying loop & " .
336             "multiple possible loops [" .
337             join(", ", @multiples) .
338             "] found for sort key: $sort_on"
339             ) if @multiples > 1;
340             }
341            
342 0 0         if($self->{ LOOPS }->{ $loop }{$sort_on}){
343 0           my @sortkeys = @{ $self->{ LOOPS }->{ $loop }{$sort_on} };
  0            
344             # check if data is string or int
345 0 0 0       my $data_type = ($sortkeys[0] =~ /(\D)/ && $1 !~ /\.|\,/) ? "STRING" : "INT";
346 0 0         if ( $data_type eq "INT" ) {
347             # strip commas
348 0           $sortkeys[$_] =~ s/,//g for 0..$#sortkeys;
349             }
350             my %sortcode = (
351 0     0     "STRING-asc" => sub { return sort { uc $sortkeys[$a] cmp uc $sortkeys[$b] } 0..$#sortkeys },
  0            
352 0     0     "STRING-desc" => sub { return sort { uc $sortkeys[$b] cmp uc $sortkeys[$a] } 0..$#sortkeys },
  0            
353 0     0     "INT-asc" => sub { return sort { $sortkeys[$a] <=> $sortkeys[$b] } 0..$#sortkeys },
  0            
354 0     0     "INT-desc" => sub { return sort { $sortkeys[$b] <=> $sortkeys[$a] } 0..$#sortkeys }
  0            
355 0           );
356 0           my @sorted = &{ $sortcode{"${data_type}-${dir}"} };
  0            
357 0           $self->{ LOOPS }->{ $loop }{$_} = [ @{$self->{ LOOPS }->{ $loop }{$_}}[@sorted] ] for keys %{ $self->{ LOOPS }->{ $loop } };
  0            
  0            
358 0           $self->{ SKEYS } = [@sorted];
359            
360             # sort the bg colours if there are any
361             #--------------------------------------------------------
362 0 0         if ( $self->{ LOOPS }->{ $loop }{ 'bgcolor' } ) {
363            
364             # get the unique colours
365 0           my %colours;
366 0           foreach my $colour ( @{ $self->{ LOOPS }->{ $loop }{ 'bgcolor' } } ){
  0            
367 0           $colours{ $colour }++;
368 0 0         last if ( scalar keys %colours >= 2 );
369             }
370             # if we have 2 to work with
371 0 0         if ( keys %colours == 2 ) {
372             # create the colour toggle
373 0           my $toggle = $self->create_toggle( keys %colours );
374 0           my $colour;
375             # reset each of the bgcolour elements
376 0           for ( my $i = 0; $i < scalar @{ $self->{ LOOPS }->{ $loop }{ 'bgcolor' } }; $i++ ){
  0            
377 0           $colour = $toggle->( $colour );
378 0           $self->{ LOOPS }->{ $loop }{ 'bgcolor' }->[$i] = $colour;
379             }
380             }
381             }
382              
383             }
384             else {
385 0           $self->debug("parse",2,"sortby called with non-existent sort key: $loop => $sortby");
386             }
387             }
388              
389             #--------------------------------------------
390             # toggle a colour
391             #--------------------------------------------
392             sub create_toggle {
393 0     0 0   my $self = shift;
394 0           my ( $primary, $secondary ) = @_;
395              
396 0           my $current = $secondary;
397             return sub {
398 0     0     my $cur_ref = \$current;
399 0 0         $$cur_ref = ($$cur_ref eq $primary) ? $secondary : $primary;
400 0           return $$cur_ref;
401             }
402 0           }
403              
404             #--------------------------------------------------
405             # generic subroutine for creating HTML dropdowns
406             #--------------------------------------------------
407             sub create_dropdown {
408              
409 0     0 0   my $self = shift;
410 0           my ( $itr_name, $ref_array_id, $ref_array_name, $selected_id, $selected_name ) = @_;
411             # create loop
412 0           my $itr = $self->new_loop($itr_name);
413 0           my $count = 0;
414             # populate loop
415 0           foreach my $id ( @$ref_array_id ) {
416 0 0         my $name = ( $ref_array_name ) ? $$ref_array_name[$count++] : '';
417 0           my $type_selected = '';
418            
419 0           $itr->array("id", $id);
420 0   0       $itr->array("name", $name || $id);
421             # is this the selected item?
422 0 0         if ( $selected_id ) {
423 0 0         $type_selected = ( $selected_id eq $id ) ? " SELECTED" : "";
424             }
425             else {
426 0 0         $type_selected = ( $selected_name eq $name ) ? " SELECTED" : "";
427             }
428 0           $itr->array("selected", $type_selected);
429             }
430             }
431              
432             #----------------------------------------------
433             # Parse and Return the template
434             #----------------------------------------------
435             sub process {
436 0     0 0   my $self = shift;
437 0           my $template_path = shift;
438 0           my $debug_object = shift;
439              
440             # prepend template path and name to output if footprint is set
441 0 0         my $template = ($self->{ FOOTPRINT }) ? "\n" : "";
442              
443             # check for a template file on line 1 of template path
444 0 0         if ( (split /\n/, $template_path)[0] =~ /\.\w{3,4}$/ ) {
445             # we're processing a file
446            
447             # if the file is not found: check if its relative to a stored path
448 0 0         unless ( -e $template_path ) {
449 0           foreach my $stored_path ( keys %{ $self->{ PATHS } } ) {
  0            
450             # check if the template exists at the stored PATH location
451 0 0         if ( -e ($stored_path . $template_path) ) {
452 0           $template_path = $stored_path . $template_path;
453 0           last;
454             }
455             }
456             }
457            
458 0           local $/ = undef; # undef record separator for reading file into scalar
459 0 0         open (READFILE, $template_path) or $self->debug("file",3,"Can't open content file : $template_path $!");
460 0           $template .= ;
461 0           close READFILE;
462            
463             }
464             else {
465             # we're processing a data block
466 0           $template = $template_path;
467             # set base path to Cwd for includes from data
468 1     1   7 use Cwd;
  1         6  
  1         156  
469 0           $template_path = cwd;
470             }
471            
472 0           $self->{ TEMPLATE } = $template_path;
473            
474             # process the html template
475 0           $self->do_includes (\$template,\$template_path);
476 0           $self->do_options (\$template);
477 0           $self->do_ifelse (\$template);
478 0           $self->do_variables (\$template);
479 0           $self->do_loops (\$template);
480 0 0         $self->do_sort_dir (\$template) if $self->{ SORTBY };
481 0 0         $self->do_clean (\$template) if $self->{ CLEAN };
482            
483             # if we are dumping object data
484 0 0         if( $debug_object ){
485 1     1   1108 use Data::Dumper;
  1         10621  
  1         4125  
486             # take a copy of the object
487 0           my $debugObj = $self;
488             # clean up the object before debug display
489 0           delete $debugObj->{ IT_STACK };
490 0           delete $debugObj->{ DEBUG_LEVELS };
491 0           $template .= "
" . Dumper( $debugObj ) . "
";
492             }
493             # add debug data
494 0 0         $template .= $self->build_err if($self->{ DEBUG_LEVEL } > 0);
495            
496 0           return $template;
497             }
498              
499              
500              
501             #=========================================================
502             # PRIVATE METHODS
503             #=========================================================
504              
505             #=====================================
506             # math private methods
507             #=====================================
508             sub addition {
509 0     0 1   my $pair = shift;
510 0           my ($this, $that) = @$pair;
511 0           return $this + $that;
512             }
513              
514             sub subtraction {
515 0     0 1   my $pair = shift;
516 0           my ($this, $that) = @$pair;
517 0           return $this - $that;
518             }
519              
520             sub multiplication {
521 0     0 1   my $pair = shift;
522 0           my ($this, $that) = @$pair;
523 0           return $this * $that;
524             }
525              
526             sub division {
527 0     0 1   my $pair = shift;
528 0           my ($this, $that) = @$pair;
529 0           return $this / $that;
530             }
531              
532             #=====================================
533              
534             #=====================================
535             # change the sort direction
536             #=====================================
537             sub do_sort_dir {
538 0     0 0   my $self = shift;
539 0           my $template_ref = shift;
540            
541 0 0         return if $self->{ SORTBY } =~ /ASC|DESC/;
542 0           my $out;
543 0           my $skey = $self->{ SORTBY };
544 0           $skey =~ /(asc|desc)/;
545 0           my $dir = $1;
546 0 0         my $dirout = ($dir eq "desc") ? "asc" : "desc";
547 0 0         if ( $dir ) {
548 0           $out = $skey;
549 0           $out =~ s/$dir/$dirout/;
550             }
551             else {
552 0           $out = $skey . "-$dirout";
553             }
554 0           $$template_ref =~ s/sort=$skey/sort=$out/o;
555              
556             }
557              
558             #=====================================
559             # include files into template
560             #=====================================
561             sub do_includes {
562 0     0 0   my $self = shift;
563 0           my $template_ref = shift;
564 0           my $template_path_ref = shift;
565              
566             # use template path as root for includes
567 0 0         if ( $$template_path_ref =~ m/\// ) {
568             # if there is a path
569 0           $$template_path_ref =~ s/(.*)(\/)(.*?)$/$1$2/;
570             }
571             else {
572             # file name only
573 0           $$template_path_ref = "";
574             }
575             # start paths library
576 0           $self->{ PATHS }->{ $$template_path_ref } = 1;
577            
578 0           while ( $$template_ref =~ m/\A(.*)${syntax_pre}include='(.*?)'${syntax_post}(.*)\Z/msi ) {
579 0           my $inc_pre = $1; # pre included file
580 0           my $file = $2; # $variables name for file
581 0           my $inc_post = $3; # post include data
582 0           my $filepath = '';
583            
584 0 0         if ( $file =~ /\./ ) {
585 0           $filepath = $$template_path_ref . $file;
586 0           my $filename;
587 0           my $local_path = "";
588             # has a path
589 0 0         if ($file =~ /\// ) {
590 0           $local_path = $file;
591 0           $local_path =~ s/(.*)(\/)(.*?)(\.\w{3,4}$)/$1$2/;
592 0           $filename = $3.$4;
593             }
594             else {
595 0           $filename = $file;
596             }
597             # store path
598 0           $self->{ PATHS }->{$$template_path_ref . $local_path}++;
599            
600             # if the file is relative to an included file
601 0 0         unless ( -e $filepath ) {
602 0           foreach ( keys %{ $self->{PATHS} } ) {
  0            
603 0 0         if ( -e $_ . $filename ) {
604 0           $filepath = $_ . $filename;
605             }
606             }
607             }
608             }
609             else {
610 0           $filepath = $self->include($file);
611             }
612              
613 0 0         my $footpre = ( $self->{FOOTPRINT} ) ? "\n\n" : "";
614 0 0         my $footpost = ( $self->{FOOTPRINT} ) ? "\n\n\n\n" : "";
615            
616             # test for existence of file
617 0 0         if ( -e $filepath ) {
618 0           local $/ = undef;
619 0           open( INCLUDE, "< $filepath" );
620 0           my $inc_data = ;
621 0           close(INCLUDE);
622 0           $$template_ref = "$inc_pre$footpre$inc_data$footpost$inc_post";
623             }
624             # no file to include
625             else {
626 0           $$template_ref = $inc_pre . "" . $inc_post;
627 0           $self->debug( "parse", 3, "File to include does not exist:'$filepath'" );
628             }
629             }
630             }
631             #=====================================
632             # evaluate optional content
633             #=====================================
634             sub do_options {
635 0     0 0   my $self = shift;
636 0           my $template_ref = shift;
637            
638 0           while ($$template_ref =~ m/\A(.*)${syntax_pre}OPTION name([ !=]+)'([\w\d-]*)'${syntax_post}(.*?)${syntax_pre}OPTION END${syntax_post}(.*)\Z/msi){
639 0           my $opt_pre = $1;
640 0           my $opt_type = $2;
641 0           my $opt_name = $3;
642 0           my $opt_data = $4;
643 0           my $opt_post = $5;
644             # clean the option comarisons
645 0           $opt_type =~ s/ //g;
646 0           $opt_type =~ s/==/=/;
647            
648 0   0       my $test_val = $self->option($opt_name) || $self->variable($opt_name);
649            
650 0 0 0       if($opt_type eq "=" && $test_val){
    0 0        
651 0           $$template_ref = $opt_pre.$opt_data.$opt_post;
652             }
653             elsif($opt_type eq "!=" && !$test_val){
654 0           $$template_ref = $opt_pre.$opt_data.$opt_post;
655             }
656             # option fails
657             else {
658             # loose the optional content
659 0           $$template_ref = $opt_pre.$opt_post;
660             }
661             }
662             }
663              
664             #=====================================
665             # Evaluate If/Else clauses
666             #=====================================
667             sub do_ifelse {
668 0     0 0   my $self = shift;
669 0           my $template_ref = shift;
670              
671 0           while ( $$template_ref =~ m/\A(.*)${syntax_pre}IF (\w+)([ =!<>]+)'([\w-]*)'${syntax_post}(.*?)(${syntax_pre}ENDIF${syntax_post})(.*)\Z/msi ) {
672 0           my $if_pre = $1; # pre if data
673 0           my $fs_var = $2; # first if variable
674 0           my $fs_oper = $3; # first if operand
675 0           my $fs_val = $4; # first value to test
676 0           my $if_cont = $5; # block to do work on
677 0           my $if_end = $6; # [ENDIF] end of string
678 0           my $if_post = $7; # rest of template
679 0           my $if_output = '';
680              
681             # test if the first is true
682 0 0         if ( $self->compare( $fs_oper, $self->variable($fs_var), $fs_val ) ) {
683 0 0         $if_cont =~ s/(.*?)${syntax_pre}(ELSIF|ELSE).*\Z/$1/msi if $if_cont =~ /${syntax_pre}(ELSIF|ELSE)/msi;
684 0           $if_output = $if_cont;
685             }
686             # loop through remaining tests
687             else {
688 0           my $if_data = $if_cont . $if_end;
689              
690             # evaluate each ELSIF|ELSE
691 0           while ( $if_data =~ m/${syntax_pre}(ELSIF|ELSE)(| (\w+)([ =!<>]+)'([\w-]*)')${syntax_post}(.*?)(${syntax_pre}(ELSIF|ELSE|ENDIF).*)\Z/msi ) {
692 0           my $if_type = $1; # clause type (IF|ELSIF|ELSE)
693 0           my $if_var = $3; # value to test
694 0           my $oper = $4; # if operator
695 0           my $if_val = $5; # value to test
696 0           my $if_content = $6; # output for the block
697 0           my $if_tail = $7; # following IF/ELSE clauses
698              
699             # evaluate clauses
700 0 0 0       if ( $if_type =~ /ELSIF|IF/ && $self->compare( $oper, $self->variable($if_var), $if_val ) ) {
    0          
701 0           $if_output = $if_content;
702 0           last; # exit if there's a match
703             }
704             # use the else if above fails
705             elsif ( $if_type =~ /ELSE/ ) {
706 0           $if_output = $if_content;
707             }
708             # truncate the string for each loop by what we just tested
709 0           $if_data = $if_tail;
710             }
711             }
712 0           $$template_ref = $if_pre . $if_output . $if_post;
713             }
714             }
715              
716              
717             #=====================================
718             # replace loops
719             #=====================================
720             #my (%it_stack);
721             sub do_loops {
722 0     0 0   my $self = shift;
723 0           my $template_ref = shift;
724            
725 0           $self->{ IT_STACK } = {};
726            
727 0           while ( $$template_ref =~ m/\A(.*)${syntax_pre}LOOP name='([\w\d-]+)'${syntax_post}(.*?)${syntax_pre}LOOP END${syntax_post}(.*)\Z/msi) {
728 0           my $it_pre = $1; # data before the loop block
729 0           my $it_name = $2; # name of loop
730 0           my $data = $3; # loop data to process
731 0           my $it_post = $4; # data after the loop block
732            
733             # insert place holders
734 0           $$template_ref = $it_pre."[LOOP:'$it_name']".$it_post;
735 0 0 0       $self->debug("parse",1,"loop: $it_name not found in object") unless ( $self->{ LOOPS }{ $it_name } || $self->{ NESTS }{ $it_name });
736             # push loop block to named hash of arrays,
737             # allows for multiple loops of the same name
738 0           push @{ $self->{ IT_STACK }->{$it_name} }, $data;
  0            
739             }
740            
741              
742 0           my (%multi);
743             # add looped content to template
744 0           while ($$template_ref =~ m/\A(.*)\[LOOP:'([\w\d-]+)'\](.*)\Z/msi){
745 0           my $pre = $1;
746 0           my $iter = $2;
747 0           my $post = $3;
748              
749             # log duplicate loops
750 0           $multi{$iter}++;
751             # go iterate over loop
752 0           $$template_ref = $pre.$self->iterate_loop( $iter, $self->{ IT_STACK }->{$iter}[$multi{$iter}-1] ).$post;
753             }
754             }
755              
756             #=====================================
757             # loop processing
758             #=====================================
759             #my (%parents);
760             sub iterate_loop {
761 0     0 0   my $self = shift;
762 0           my $it_name = shift;
763 0           my $dat_ref = shift;
764 0           my $p_count = shift;
765 0           my ($iterated);
766 0           my $input_name = $it_name;
767              
768             # initialise position counter
769             # start at -1 so ++ on first pass gives 0
770             #if(!exists $parents{$input_name}){ $parents{$input_name} = -1 };
771 0   0       $self->{ PARENTS }{$input_name} = $self->{ PARENTS }{$input_name} || -1;
772              
773             # if we are processing a nest - indicated by passed value for
774             # 'p_count' (parent counter)
775 0 0         if($p_count ne ''){
776             # use sort keys for position if we have a sorted parent
777 0 0         my $position = ( $self->{ SKEYS } ) ? $self->{ SKEYS }[$p_count] : $p_count;
778 0           my $it_key = $self->{ NESTS }->{$it_name}->{ KEYS }[$position];
779 0           $it_name = "${it_name}~${it_key}";
780             }
781              
782 0           my $max_count = 0;
783             # get the longest array for this loop
784 0           foreach (keys %{ $self->{ LOOPS }->{$it_name} }){
  0            
785 0 0         if(ref $self->{ LOOPS }->{$it_name}->{$_} eq "ARRAY"){
786 0           my $a_length = @{$self->{ LOOPS }->{$it_name}->{$_}};
  0            
787 0 0         $max_count = $a_length if ( $a_length > $max_count );
788             }
789             }
790              
791             # loop for the length of the longest array
792 0           for (my $count = 0; $count < $max_count; $count++) {
793 0           my $loop = $dat_ref; # deref & scope loop data
794 0           while ($loop =~ m/\A(.*)${syntax_pre}array='([\w\d-]*)'${syntax_post}(.*)\Z/msi) {
795 0           my $ins_pre = $1;
796 0           my $ins_name = $2;
797 0           my $ins_post = $3;
798            
799 0           my $it_val = $self->{ LOOPS }->{$it_name}->{$ins_name}[$count];
800 0 0         $it_val = $self->escape_html($it_val) if $self->{ ESCAPE };# ***
801             # build the output for this instance
802 0           $loop = $ins_pre.$it_val.$ins_post;
803             }
804             # increment the count for this pass
805 0           $self->{ PARENTS }{$input_name}++;
806            
807             # Loop Option processing
808             #---------------------------------------------------
809 0           while ($loop =~ m/\A(.*)${syntax_pre}LOOP OPTION name(!=|=)'([\w\d-]*)'${syntax_post}(.*?)${syntax_pre}LOOP OPTION END${syntax_post}(.*)\Z/msi){
810 0           my $opt_pre = $1;
811 0           my $opt_type = $2;
812 0           my $opt_name = $3;
813 0           my $opt_data = $4;
814 0           my $opt_post = $5;
815 0           my $opt_itr_val;
816            
817             # check if there is an explicit options setting
818 0 0         if(exists $self->{ LOOPS }->{$it_name}->{ OPTIONS }->{$opt_name}){
    0          
819 0           $opt_itr_val = $self->{ LOOPS }->{$it_name}->{ OPTIONS }->{$opt_name}[$count];
820             }
821             # otherwise look for the 'array' in the current loop
822             elsif(exists $self->{ LOOPS }->{$it_name}->{$opt_name}){
823 0           $opt_itr_val = $self->{ LOOPS }->{$it_name}->{$opt_name}[$count];
824             }
825             else {
826 0           $self->debug("process",2,"The loop option: '$opt_name' cannot be found");
827             }
828            
829             # if the option variable is true
830 0 0 0       if ($opt_itr_val && $opt_type eq "="){
    0 0        
831 0           $loop = $opt_pre.$opt_data.$opt_post;
832             }
833             # if the option variable is not true
834             elsif (!$opt_itr_val && $opt_type eq "!="){
835 0           $loop = $opt_pre.$opt_data.$opt_post;
836             }
837             else {
838             # loose the optional content
839 0           $loop = $opt_pre.$opt_post;
840             }
841             }
842             # Loop if/else processing
843             #---------------------------------------------------------
844 0           while ( $loop =~ m/\A(.*)${syntax_pre}LOOP IF (\w+)([ =!<>]+)'([\w-]*)'${syntax_post}(.*?)(${syntax_pre}LOOP ENDIF${syntax_post})(.*)\Z/msi ) {
845 0           my $if_pre = $1; # pre if data
846 0           my $fs_var = $2; # first if variable
847 0           my $fs_oper = $3; # first if operand
848 0           my $fs_val = $4; # first value to test
849 0           my $if_cont = $5; # block to do work on
850 0           my $if_end = $6; # [ENDIF] end of string
851 0           my $if_post = $7; # rest of template
852 0           my $if_output = '';
853              
854             # test if the first is true
855 0 0         if ( $self->compare( $fs_oper, $self->{LOOPS}->{$it_name}->{$fs_var}[$count], $fs_val ) ) {
856 0 0         $if_cont =~ s/(.*?)${syntax_pre}LOOP (ELSIF|ELSE).*\Z/$1/msi if $if_cont =~ /${syntax_pre}LOOP (ELSIF|ELSE)/msi;
857 0           $if_output = $if_cont;
858             }
859             # loop through remaining tests
860             else {
861 0           my $if_data = $if_cont . $if_end;
862              
863             # evaluate each ELSIF|ELSE
864 0           while ( $if_data =~ m/${syntax_pre}LOOP (ELSIF|ELSE)(| (\w+)([ =!<>]+)'([\w-]*)')${syntax_post}(.*?)(${syntax_pre}LOOP (ELSIF|ELSE|ENDIF).*)\Z/msi ) {
865 0           my $if_type = $1; # clause type (IF|ELSIF|ELSE)
866 0           my $if_var = $3; # value to test
867 0           my $oper = $4; # if operator
868 0           my $if_val = $5; # value to test
869 0           my $if_content = $6; # output for the block
870 0           my $if_tail = $7; # following IF/ELSE clauses
871              
872             # evaluate clauses
873 0 0 0       if ( $if_type =~ /ELSIF|IF/ && $self->compare( $oper, $self->{LOOPS}->{$it_name}->{$if_var}[$count], $if_val ) ) {
    0          
874 0           $if_output = $if_content;
875 0           last; # exit if there's a match
876             }
877             # use the else if above fails
878             elsif ( $if_type =~ /ELSE/ ) {
879 0           $if_output = $if_content;
880             }
881             # truncate the sring each loop by what we just tested
882 0           $if_data = $if_tail;
883             }
884             }
885 0           $loop = $if_pre . $if_output . $if_post;
886             }
887            
888             # Nested Loop processing (recursive)
889             #---------------------------------------------------------
890 0           my %nest_multi;
891 0           while ($loop =~ /\A(.*)\[LOOP:'([\w\d-]+)'\](.*)\Z/msi){
892 0           my $curr_pre = $1;
893 0           my $curr_nst = $2;
894 0           my $curr_post = $3;
895            
896 0 0         $self->debug("parse",1,"loop: $it_name not found in object") unless $self->{ LOOPS }{ $it_name };
897            
898             # handle duplicate naming of nested loops
899 0           $nest_multi{$curr_nst}++ ;
900 0           my $thisdata = $self->iterate_loop( $curr_nst, $self->{ IT_STACK }->{$curr_nst}[$nest_multi{$curr_nst}-1], $self->{ PARENTS }{$input_name} );
901 0           $loop = $curr_pre . $thisdata . $curr_post;
902            
903             }
904             # add to iterated content
905 0           $iterated .= $loop;
906             }
907 0           return $iterated;
908             }
909              
910             #=====================================
911             # replace normal variables
912             #=====================================
913             sub do_variables {
914 0     0 0   my $self = shift;
915 0           my $template_ref = shift;
916              
917 0 0         $$template_ref =~ s/${syntax_pre}variable='([\d\w-]+)'${syntax_post}/$self->{ ESCAPE } ? $self->escape_html($self->variable($1)) : ($self->variable($1) )/eg;
  0            
918             }
919              
920              
921             # encode html un-friendly entities
922             #----------------------------------
923             sub escape_html {
924 0     0 0   my $self = shift;
925 0           my $data = shift;
926 0           my %esc = (
927             '"' => '"',
928             '&' => '&',
929             '<' => '<',
930             '>' => '>'
931             );
932 0           $data =~ s/([\"<>])/$esc{$1}/g;
933 0           return $data;
934             }
935              
936             #=====================================
937             # Clean spaces
938             #=====================================
939             sub do_clean {
940 0     0 0   my $self = shift;
941 0           my $template_ref = shift;
942            
943             # remove empty lines except in text areas
944 0 0         $$template_ref =~ s/\s+\n/\n/sg unless $$template_ref =~ /textarea(.*?)\s+\n(.*?)\/textarea/msi;
945 0           $$template_ref =~ s/\n\s+
946             }
947              
948             #=====================================
949             # perform comparisons
950             #=====================================
951             sub compare {
952 0     0 0   my $self = shift;
953 0           my $oper = shift;
954 0   0       my $left = shift || 0;
955 0   0       my $right = shift || 0;
956 0           $oper =~ s/ //g;
957              
958 0 0 0       if(
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
959             ( $oper eq "=" && $left eq $right ) ||
960             ( $oper eq "==" && $left eq $right ) ||
961             ( $oper eq "!=" && $left ne $right ) ||
962             ( $oper eq "<" && $left < $right ) ||
963             ( $oper eq "<=" && $left <= $right ) ||
964             ( $oper eq ">" && $left > $right ) ||
965             ( $oper eq ">=" && $left >= $right ) ||
966             ( $oper eq "LIKE" && $left =~ m/$right/ ) ||
967             ( $oper eq "NOTLIKE" && $left !~ m/$right/ )
968 0           ) { return 1; }
969 0           else { return 0; }
970             }
971              
972             #=====================================
973             # debug template data
974             #=====================================
975             my $error_count;
976             sub debug {
977 0     0 0   my $self = shift;
978 0           my $type = shift;
979 0           my $level = shift;
980 0           my $msg = shift;
981            
982 0           $$error_count++;
983             # get caller data
984 0           my( $file, $line, $pack, $sub ) = id(2);
985            
986 0 0         if( !$level ) { print "no debug level at: at $file line $line"; exit; }
  0            
  0            
987            
988             # insert the error in to the object hash
989 0           $self->{ DEBUG }{ $level }{ $type }{ $$error_count }{ MSG } = $msg;
990 0           $self->{ DEBUG }{ $level }{ $type }{ $$error_count }{ SUB } = $sub;
991 0           $self->{ DEBUG }{ $level }{ $type }{ $$error_count }{ LOC } = "at $file line $line";
992            
993             # HIGH level debugging: print error and exit
994             #---------------------------------
995 0 0         if ( $level == $self->{DEBUG_LEVELS}{'Fatal'} ) {
996 0           print $self->build_err($self->{DEBUG_LEVELS}{'Fatal'});
997 0           exit;
998             }
999              
1000             }
1001             #===============================
1002             # format error data
1003             #===============================
1004             sub build_err {
1005 0     0 0   my $self = shift;
1006 0           my $level = shift;
1007            
1008             # possible error types
1009 0           my %error_types = (
1010             param => "setup parameter",
1011             file => "file open / close / print",
1012             process => "building object data",
1013             parse => "parsing the html temlate"
1014             );
1015 0   0       my $level_type = $level || $self->{ DEBUG_LEVEL };
1016              
1017             # Shameless use of html for 'pretty' debugging
1018             # The module is, after all HTML::Processor
1019 0           my $error = qq|
1020            
1025             |; } |; " . " . \n"; |;
1026            
Template Processing Debug Info:
1027             |;
1028 0 0         if( $$error_count == 0 ) { $error .= qq|
No bugs to report
  0            
1029 0           my %level_key = reverse %{ $self->{DEBUG_LEVELS} };
  0            
1030 0           foreach my $err_lvl(sort {{$b} <=> {$a}} keys %{ $self->{ DEBUG } } ){
  0            
  0            
1031            
1032             # debug up to & including given level
1033 0 0         if($err_lvl >= $level_type) {
1034            
1035 0           foreach my $err_typ(keys %{ $self->{ DEBUG }{ $err_lvl } } ){
  0            
1036            
1037 0           $error .= qq|
GROUP: [$error_types{$err_typ}] LEVEL: $level_key{$err_lvl}
1038            
1039 0           foreach my $err_num(sort {{$a} <=> {$b}} keys %{ $self->{ DEBUG }{ $err_lvl }{ $err_typ } } ){
  0            
  0            
1040             $error .= "
sub:" .
1041             $self->{ DEBUG }{ $err_lvl }{ $err_typ }{ $err_num } { SUB } .
1042             "
1043             "
what:" .
1044             $self->{ DEBUG }{ $err_lvl }{ $err_typ }{ $err_num } { MSG } .
1045             "
1046             "
where:" .
1047             $self->{ DEBUG }{ $err_lvl }{ $err_typ }{ $err_num } { LOC } .
1048 0           "

1049             }
1050 0           $error .= qq|
1051             }
1052             }
1053             }
1054 0           $error .= qq|
1055            
1056             |;
1057 0           return $error;
1058             }
1059             #===============================
1060             # get error location data
1061             #===============================
1062             sub id {
1063 0     0 0   my $level = shift;
1064 0           my ( $pack, $file, $line, $sub ) = caller($level);
1065 0           my ( $id ) = $file =~ m|([^/]+)\z|;
1066 0           return ( $file, $line, $pack, $sub );
1067             }
1068              
1069              
1070             package HTML::Processor::Loop;
1071              
1072             #----------------------------------------------
1073             # Constructor for Loop Object
1074             #----------------------------------------------
1075             sub new {
1076 0     0     my $proto = shift;
1077              
1078 0   0       my $class = ref( $proto ) || $proto;
1079 0           my $self = {};
1080              
1081 0           bless( $self, $class );
1082 0           return $self;
1083             }
1084              
1085             #----------------------------------------------
1086             # iterarion array method
1087             #----------------------------------------------
1088             sub array {
1089 0     0     my $self = shift;
1090 0           my ( $name, $val ) = @_;
1091            
1092 0           push @{ $self->{$name} }, $val;
  0            
1093              
1094             }
1095              
1096              
1097             #----------------------------------------------
1098             # build loop options
1099             #----------------------------------------------
1100             sub option {
1101 0     0     my $self = shift;
1102 0           my $name = shift;
1103 0           my $val = shift;
1104 0           my $posit = shift;
1105              
1106 0           push @{ $self->{ OPTIONS }->{$name} }, $val;
  0            
1107 0           return $self->{ OPTIONS }->{$name};
1108             }
1109             1;
1110              
1111             __END__