File Coverage

blib/lib/Template/Replace.pm
Criterion Covered Total %
statement 228 265 86.0
branch 102 156 65.3
condition 24 39 61.5
subroutine 25 31 80.6
pod 5 5 100.0
total 384 496 77.4


line stmt bran cond sub pod time code
1             package Template::Replace;
2              
3 8     8   309125 use strict;
  8         22  
  8         327  
4 8     8   212 use warnings;
  8         20  
  8         10865  
5 8     8   231 use 5.008;
  8         34  
  8         400  
6 8     8   27273 use utf8;
  8         85  
  8         45  
7 8     8   367 use warnings qw( FATAL utf8 );
  8         17  
  8         407  
8 8     8   49 use Carp;
  8         16  
  8         750  
9 8     8   8833 use Encode qw( encode decode );
  8         179682  
  8         1033  
10 8     8   9527 use File::Spec::Functions qw( :ALL );
  8         9177  
  8         2325  
11 8     8   22481 use open qw( :encoding(UTF-8) :std );
  8         18766  
  8         74  
12              
13             =head1 NAME
14              
15             Template::Replace - PurePerl Push-Style Templating Module
16              
17              
18             =head1 VERSION
19              
20             Version 0.04
21              
22             =cut
23              
24             our $VERSION = '0.04';
25              
26             #
27             # TODO !!!!!
28             #
29             # . Changed var delimiter for HTML ({} collides with CSS and JS).
30             # . Changed include delimiter for HTML (
70            
71            
72            
77            
78            
79              
80            

($ content_title_var $)

81              
82             ($ content_var | none $)
83              
84            
85            
Comments
86            
87            
88            
($ name $):
89             ($ comment $)
90            
91            
92            
93            
94            

No comments yet!

95            
96              
97            
98            
99            
100            
101              
102              
103             Data example:
104              
105             my $data = {
106             html_title_var => 'Template::Replace: An Example',
107             content_title_var => 'An Example',
108             content_var => $html_content,
109             Comments => [
110             {
111             url => $author[0]->{url},
112             name => $author[0]->{name},
113             comment => $author[0]->{comment},
114             },
115             {
116             url => $author[1]->{url},
117             name => $author[1]->{name},
118             comment => $author[1]->{comment},
119             },
120             ],
121             NotRepeating => { content => 'This is simple content.' },
122             };
123              
124              
125             =head1 EXPORT
126              
127             Nothing is exported. This module provides an object oriented interface.
128              
129              
130             =head1 DEPENDENCIES
131              
132             Requires Perl 5.8 (best served above 5.8.2), L, L and
133             L (Perl 5.8 core modules).
134              
135             This is a single file module that can be run without prior installation.
136              
137              
138             =head1 DESCRIPTION
139              
140             #
141             # TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
142             #
143              
144             Beware: This module's code is neither elegant nor ingenious! Au contraire -
145             it's ugly, it's a mess ... and it is doing what I wanted it to do. (Okay, not
146             as bad as stated, but don't complain when looking at it ;-)
147              
148              
149             =head1 METHODS
150              
151             =head2 C
152              
153             my $tmpl = Template::Replace->new({
154             path => [ 'path1', 'path2' ],
155             filename => 'template_filename',
156             delimiter => {
157             include => [ '' ],
158             section => [ '' ],
159             var => [ '($' , '$)' ],
160             test => [ '' ],
161             comment => [ '' ],
162             },
163             filter => {
164             default => 'xml',
165             special => \&my_special_filter_function,
166             },
167             });
168            
169             Path can be a single string or an array reference of multiple strings; given
170             paths have to exist, and template files (and includes) can only be loaded from
171             these paths (for security reasons)!
172              
173             Filename is an optional string; the template is loaded on object creation if
174             given.
175              
176             Single delimiter pairs can be given (default delimiters shown), but they have
177             to be array references with exactly 2 strings; the delimiters are fixed after
178             object creation, so this is the only chance to change them!
179              
180             Filters can be re-declared and custom filters attached; the default filter is
181             a pass-through filter; filters can be changed anytime before invoking
182             $tmpl->replace().
183              
184             All options are optional, but at least one existing path has to be given to
185             load a template from file (either with filename on object creation or later
186             with the load method).
187              
188             =cut
189              
190             sub new {
191              
192             # Process arguments ...
193 23 100   23 1 13369 croak __PACKAGE__ . '->new(): Only one optional argument!'
194             unless @_ < 3;
195              
196 22   100     108 my ($class, $conf) = (shift, shift || {});
197              
198 22 100 66     313 croak __PACKAGE__ . '->new(): Argument has to be a hash reference!'
199             unless defined $conf && ref $conf eq 'HASH';
200              
201 21 100 100     250 croak __PACKAGE__ . '->new(): Filename has to be a string!'
202             if $conf->{filename} && ref \$conf->{filename} ne 'SCALAR';
203              
204             # Create object hash with default values ...
205 0     0   0 my $self = {
206             template => {},
207             path => [],
208             filename => '',
209             encoding => 'UTF-8',
210             delimiter => {
211             include => [ '' ],
212             section => [ '' ],
213             var => [ '($' , '$)' ],
214             test => [ '' ],
215             comment => [ '' ],
216             },
217             is_block => {
218             include => 1,
219             section => 1,
220             test => 1,
221             comment => 1,
222             },
223             filter => {
224             default => \&_filter_none,
225             none => \&_filter_none,
226             literal => \&_filter_none,
227             xml => \&_filter_xml,
228             html => \&_filter_html,
229             uri => \&_filter_uri,
230             url => \&_filter_url,
231             'uri+xml' => sub {_filter_xml(_filter_uri(shift));},
232 0     0   0 'uri+html' => sub {_filter_html(_filter_uri(shift));},
233 0     0   0 'url+xml' => sub {_filter_xml(_filter_url(shift));},
234 0     0   0 'url+html' => sub {_filter_html(_filter_url(shift));},
235             },
236 20         776 };
237              
238             # Check for path or path array ...
239             # croak __PACKAGE__ . '->new(): No path or path array given!'
240             # unless $conf->{path};
241 20 100       99 if (defined $conf->{path}) {
242 8 100       58 if (ref $conf->{path} eq 'ARRAY') {
    100          
243 4         384 croak __PACKAGE__ . '->new(): Path array contains invalid path!'
244 3 100       4 if grep { !-e $_ } @{$conf->{path}};
  3         10  
245 1         4 $self->{path} = $conf->{path};
246             }
247             elsif (ref \$conf->{path} eq 'SCALAR') {
248 4 100       265 croak __PACKAGE__ . '->new(): Path '
249             . $conf->{path}
250             . ' does not exist!'
251             unless -e $conf->{path};
252 3         12 my @path = ($conf->{path});
253 3         10 $self->{path} = \@path;
254             }
255             else {
256 1         140 croak __PACKAGE__ . '->new(): Path is no string or array_ref!';
257             }
258             }
259              
260             # Check for filename ...
261 1         123 croak __PACKAGE__ . '->new(): No paths defined to load files from!'
262 16 100 66     68 if $conf->{filename} && $#{$self->{path}} == -1;
263 15         36 $self->{filename} = $conf->{filename};
264              
265             # Get delimiters from $conf ...
266 15 100 100     201 croak __PACKAGE__ . '->new(): Argument for delimiters is no hash ref!'
267             if $conf->{delimiter} && ref $conf->{delimiter} ne 'HASH';
268 14         24 foreach my $key (keys %{$self->{delimiter}}) {
  14         67  
269 68 100       203 if (defined $conf->{delimiter}{$key}) {
270 2         292 croak __PACKAGE__ . '->new(): ARRAY reference'
271             . " of two delimiter strings expected for $key!"
272             unless ref $conf->{delimiter}{$key} eq 'ARRAY'
273 2 50 33     9 && scalar @{$conf->{delimiter}{$key}} == 2;
274 0         0 $self->{delimiter}{$key} = $conf->{delimiter}{$key};
275             }
276             }
277              
278             # Create slicer and parser regexps ...
279 12         39 my $regexp_ref= {};
280 12         19 foreach my $key (keys %{$self->{delimiter}}) {
  12         64  
281             #my $rx = ($self->{is_block}{$key} ? '[ \t]*' : '')
282             #my $rx = ($self->{is_block}{$key} ? '\n?[ \t]*' : '')
283 60 100       435 my $rx =
    100          
284             ($self->{is_block}{$key} ? '(?(?<=\n)[ \t]*|(?:\A[ \t]*)?)' : '')
285             . quotemeta($self->{delimiter}{$key}[0])
286             . '\s*?(\S.*?)?\s*?'
287             . quotemeta($self->{delimiter}{$key}[1])
288             . ($self->{is_block}{$key} ? '(?:[ \t]*\n)?' : '')
289             ;
290 60         9262 $regexp_ref->{$key} = qr/$rx/s;
291             }
292            
293 12         49 $self->{regexp} = $regexp_ref;
294              
295             # Get filter from $conf ...
296 12 100       74 if ($conf->{filter}) {
297 4 100       156 croak __PACKAGE__ . '->new(): Argument for filters is no hash ref!'
298             unless ref $conf->{filter} eq 'HASH';
299 3         4 foreach my $key (keys %{$conf->{filter}}) {
  3         12  
300 2 50       25 if (ref $conf->{filter}{$key} eq 'CODE') {
    100          
301 0         0 $self->{filter}{$key} = $conf->{filter}{$key};
302             }
303             elsif (ref \$conf->{filter}{$key} eq 'SCALAR') {
304 1 50       147 $self->{filter}{$key} = $self->{filter}{$conf->{filter}{$key}}
305             or croak __PACKAGE__ . '->new(): Unknown pre-defined'
306             . " filter '$conf->{filter}{$key}'!";
307             }
308             else {
309 1         142 croak __PACKAGE__ . '->new(): Filter has to be '
310             . 'a pre-defined filter name or a CODE reference!';
311             }
312             }
313             }
314              
315             # Bless object hash ...
316 9         36 bless($self, $class);
317              
318 9 50       67 $self->load($self->{filename}) if $self->{filename};
319              
320 9         72 return $self;
321             }
322              
323              
324             =head2 C
325              
326             my $template_ref = $tmpl->parse($str);
327              
328             Parses a template from $str. Stores the template structure reference in the
329             $tmpl object and returns it. No includes, because they are handled only on
330             reading from file (use $tmpl->load() instead)!
331              
332             =cut
333              
334             sub parse {
335 2     2 1 71 my ($self, $str) = @_;
336 2         13 $self->{template} = $self->_parse_slices($self->_slice_str($str));
337 2         12 return $self->{template};
338             }
339              
340              
341             =head2 C
342              
343             my $template_ref = $tmpl->load($filename);
344              
345             Loads a template from file $filename and parses it. Stores the template
346             structure reference in the $tmpl object and returns it.
347              
348             =cut
349              
350             sub load {
351 0     0 1 0 my ($self, $filename) = @_;
352 0         0 return $self->parse($self->_read_file($filename));
353             }
354              
355              
356             =head2 C
357              
358             my $txt = $tmpl->replace($data);
359              
360             Replaces $data in $tmpl and returns the resulting string (text).
361              
362             =cut
363              
364             sub replace {
365 1     1 1 953 my ($self, $data) = @_;
366 1         22 return $self->_replace($self->{template}, $data);
367             }
368              
369              
370             =head2 C
371              
372             my $result = $tmpl->has($access_str);
373              
374             Tries to access a template element and returns the usage count of a section (0
375             or 1 for now) or of a variable in the template structure. The access string
376             has the following form:
377              
378             'RootSection' (or '/RootSection')
379             'RootSection/Subsection'
380             'RootVariable'
381             'RootSection/SectionVariable'
382             'RootSection/Subsection/SubsectionVariable'
383             etc.
384              
385             =cut
386              
387             # TODO: Adapt for new template structure (multiple same-name subsections),
388             # don't forget to change documentation!
389             #
390             sub has {
391 10     10 1 33 my ($self, $access_str) = @_;
392 10         34 return _access_template($access_str, $self->{template});
393             }
394              
395              
396             #
397             # Private methods ...
398             #
399              
400             sub _replace {
401             #
402             # my $txt = $self->_replace($sec_ref, @data_refs);
403             #
404             # Format of a data reference:
405             #
406             # my $data_ref = {
407             # 'GlobalVar' => GlobalVarString,
408             # 'Section1Name' => { # section data for single invocation
409             # var1_name => var1_scalar,
410             # var2_name => var2_scalar,
411             # 'SubSectionName' => ...,
412             # },
413             # 'Section2Name' => [ # section data for iterated invocation
414             # { var1_name => var1_scalar, var2_name => var2_scalar },
415             # { var1_name => var1_scalar, var2_name => var2_scalar },
416             # ],
417             # };
418             #
419 5     5   12 my ($self, $sec_ref, @data_refs) = @_;
420 5         9 my $sec_data_ref = $data_refs[$#data_refs];
421 5         10 my $txt = '';
422 5         8 my $skip = '';
423              
424 5         22 PART:
425 5         6 foreach my $part (@{$sec_ref->{parts}}) {
426 18 50       56 if ($skip) { # skip parts inside test
    100          
427 0 0 0     0 $skip = '' if ref $part eq 'HASH'
      0        
428             && $part->{test} && $part->{test} eq "/$skip";
429             }
430             elsif (ref $part eq 'HASH') { # subsection, test or var
431 7 100       24 if (my $subsec_name = $part->{sec}) { # subsection
    50          
432 2         5 my $subsec_idx = $part->{idx};
433 2         4 my $subsec_data_ref = $sec_data_ref->{$subsec_name};
434 2 50       7 next PART unless $subsec_data_ref;
435 2 50 66     16 croak __PACKAGE__ . '->_replace(): Data for section'
436             . " $subsec_name has to be"
437             . " a HASH or ARRAY reference!"
438             unless ref $subsec_data_ref eq 'HASH'
439             || ref $subsec_data_ref eq 'ARRAY'
440             ;
441 2 100       9 my @iterations = ref $subsec_data_ref eq 'HASH'
442             ? ($subsec_data_ref)
443             : @$subsec_data_ref
444             ;
445 2         5 foreach my $iteration_data_ref (@iterations) {
446 4         34 $txt .= $self->_replace(
447             $sec_ref->{children}{$subsec_name}[$subsec_idx],
448             @data_refs,
449             $iteration_data_ref,
450             );
451             }
452             }
453             elsif ($part->{test}) { # test
454 0 0       0 next PART if $part->{test} =~ m{^/};
455 0         0 my $test = $part->{test};
456 0 0       0 if ($test =~ s/^!//) {
457 0 0       0 $skip = _access_data($test, @data_refs)
458             ? $part->{test} : '' ;
459             }
460             else {
461 0 0       0 $skip = _access_data($test, @data_refs)
462             ? '' : $part->{test} ;
463             }
464             }
465             else { # var
466 5   33     23 my $filter = $self->{filter}{$part->{filter}}
467             || $self->{filter}{default};
468 5   50     16 $txt .= &$filter(
469             _access_data($part->{var}, @data_refs) || ''
470             );
471             }
472             }
473             else { # string
474 11         30 $txt .= $part;
475             }
476             }
477              
478 5         25 return $txt;
479             }
480              
481             sub _parse_slices {
482             #
483             # my $sec_ref = $self->_parse_slices(
484             # $slices_ref[, $sec_name[, $parent_sec_ref]]
485             # );
486             #
487             # The template's recursive structure is returned:
488             #
489             # {
490             # name => 'root',
491             # parent => undef,
492             # children => {
493             # 'var_name_1' => count,
494             # 'subsec_name_1' => [
495             # {
496             # name => 'subsec_name_1',
497             # parent => root_ref,
498             # children => {...},
499             # parts => {...}
500             # },
501             # {
502             # name => 'subsec_name_1',
503             # parent => root_ref,
504             # children => {...},
505             # parts => {...}
506             # }
507             # ],
508             # 'var_name_2' => count,
509             # 'subsec_name_2' => [...],
510             # },
511             # parts => [
512             # string,
513             # { var => 'var_name_1', filter => 'default' },
514             # { test => 'test_name' },
515             # { sec => 'subsec_name_1', idx => 0 },
516             # string,
517             # { var => 'var_name_2', filter => 'xml' },
518             # string,
519             # { test => '/test_name' },
520             # { sec => 'subsec_name_2', idx => 0 },
521             # { sec => 'subsec_name_1', idx => 1 },
522             # ...
523             # ]
524             # }
525             #
526             # The 'children' hashref is for faster template inspection (convenience)
527             # and (for subsections) to simplify the elements in the parts arrayref.
528             #
529             # Variables can be used more than once in a section, because they
530             # are not defined in the template but only filled with their data.
531             #
532             # Sections can be used more than one also, e.g. for localization inside a
533             # template (with language conditions - TODO). They will be processed with
534             # the same data!
535             #
536 17     17   1420 my ($self, $slices_ref, $sec_name, $parent_sec_ref) = @_;
537 17   100     68 $sec_name ||= 'root';
538 17         130 my $sec_ref = {
539             name => $sec_name,
540             parent => $parent_sec_ref,
541             children => {},
542             parts => [],
543             };
544 17         58 my $regexp_comment = quotemeta($self->{delimiter}{comment}[0]);
545              
546             SLICE:
547 17         53 while (my $slice = shift @$slices_ref) {
548 109 100       4329 if ($slice =~ m/$regexp_comment/) { # comment
    50          
    100          
    100          
549 1         4 next;
550             }
551             elsif ($slice =~ m/$self->{regexp}{test}/) { # test
552 0         0 push @{$sec_ref->{parts}}, { test => $1 };
  0         0  
553             }
554             elsif ($slice =~ m/$self->{regexp}{section}/) { # subsection
555 25         67 my $subsec_name = $1;
556 25 100       86 if ($subsec_name =~ m{^/(.+)$}) { # end of subsection
557 12 50       42 croak __PACKAGE__
558             . '->_parse_slices(): Section ended with '
559             . "$1 instead of $sec_name!"
560             unless $1 eq $sec_name;
561 12         32 last SLICE;
562             }
563 13 100       59 if (!$sec_ref->{children}{$subsec_name}) {
564 11         38 $sec_ref->{children}{$subsec_name} = [];
565             }
566 13         32 my $subsec_array_ref = $sec_ref->{children}{$subsec_name};
567 13 100       126 if (ref $subsec_array_ref ne 'ARRAY') {
568 1         12825 croak __PACKAGE__
569             . "->_parse_slices(): Section name '$subsec_name' "
570             . "already used for a variable in '$sec_name'!"
571             ;
572             }
573 12         71 my $subsec_ref = $self->_parse_slices(
574             $slices_ref, $subsec_name, $sec_ref
575             );
576 12         15 push @{$subsec_array_ref}, $subsec_ref;
  12         97  
577 12         108 push @{$sec_ref->{parts}}, {
  12         100  
578             sec => $subsec_name,
579             idx => $#$subsec_array_ref,
580             };
581             }
582             elsif ($slice =~ m/$self->{regexp}{var}/) { # var
583 33         196 my ($var, $filter) = $1 =~ m/^([^|\s]+)\s*\|?\s*([^\s]+)?$/;
584 33   100     146 $filter = lc($filter || 'default');
585 33 100       315 croak __PACKAGE__
586             . "->_parse_slices(): Variable name '$var' "
587             . "already used for a section in '$sec_name'!"
588             if ref $sec_ref->{children}{$var} eq 'ARRAY';
589 32         43 push @{$sec_ref->{parts}}, { var => $var, filter => $filter, };
  32         128  
590 32         184 $sec_ref->{children}{$var}++;
591             }
592             else { # string
593 50         65 push @{$sec_ref->{parts}}, $slice;
  50         223  
594             }
595             }
596              
597 15         43 return $sec_ref;
598             }
599              
600             sub _slice_str {
601             #
602             # my $slices = $tmpl->_slice_str($str);
603             #
604             # This method returns the reference to a list of strings that represents
605             # the found slices (the given string is cut to pieces - without any
606             # characters removed or added).
607             #
608 27     27   37288 my ($self, $str) = @_;
609 27 100       254 croak __PACKAGE__ . '->_slice_str(): Missing string argument!'
610             unless defined $str;
611 26 100       177 croak __PACKAGE__ . '->_slice_str(): Not a string argument!'
612             unless ref \$str eq 'SCALAR';
613              
614 25         658 my $rx = qr(
615             $self->{regexp}{comment}
616             | $self->{regexp}{test}
617             | $self->{regexp}{section}
618             | $self->{regexp}{var}
619             )x;
620 25         62 my $dbldelim = '^[ \t]*(';
621 25         33 foreach my $key (keys %{$self->{delimiter}}) {
  25         114  
622 125         430 $dbldelim .= quotemeta($self->{delimiter}{$key}[0]) . '|';
623             }
624 25         77 $dbldelim = substr($dbldelim, 0, -1) . ').*?\1';
625 25         251 $dbldelim = qr/$dbldelim/s;
626              
627 25         56 my @strings = ();
628 25         36 my $last_pos = 0;
629              
630 25         484 while ($str =~ m/$rx/cg) {
631 113         403 my $start = $-[0];
632 113         328 my $end = $+[0];
633 113 100       262 if ($start > $last_pos) { # string slice before match
634 77         278 push @strings, substr $str, $last_pos, $start - $last_pos;
635             }
636 113         203 my $slice = substr $str, $start, $end - $start; # element slice
637             # Check for doubled start delimiter (which breaks $rx) ...
638 113 100       1151 croak __PACKAGE__ . '->_slice_str(): Repeated start delimiter '
639             . "in slice '$slice'!"
640             if $slice =~ m/$dbldelim/;
641 111         248 push @strings, $slice;
642 111         2061 $last_pos = $end;
643             }
644              
645 23 100       65 if (length $str > $last_pos) { # remaining string slice
646 14         34 push @strings, substr $str, $last_pos;
647             }
648            
649 23         211 return \@strings;
650             }
651              
652             sub _read_file {
653             #
654             # my $str = $tmpl->_read_file($file_name[, @ancestors]);
655             #
656             # TODO:
657             # - Restrict filenames to an ASCII subset
658             # - Allways Unix path notation in Template::Replace?
659             # - Use explicit file encoding when reading
660             #
661 3     3   16 my ($self, $file_name, @ancestors) = @_;
662              
663             # Cleanup of file name ...
664 3         19 my @canon_path = splitpath(canonpath($file_name));
665 3         51 my @canon_dir = grep {$_ !~ /\.\./} splitdir($canon_path[1]);
  2         14  
666 3         22 my $canon_file_name = catfile(@canon_dir, $canon_path[2]);
667              
668             # Try to find file in paths ...
669 3         5 my $canon_file_path = '';
670 3         4 foreach my $path (@{$self->{path}}) {
  3         9  
671 3         20 $canon_file_path = catfile($path, $canon_file_name);
672 3 50       86 last if -e $canon_file_path;
673 0         0 $canon_file_path = '';
674             }
675 3 50       8 croak __PACKAGE__ . "->read_file(): File $file_name not found!"
676             . ' (Perhaps paths are wrong.)'
677             unless $canon_file_path;
678              
679 3         10 croak __PACKAGE__ . '->read_file(): File recursion for '
680             . "$canon_file_path detected!"
681 3 50       8 if grep {$canon_file_path eq $_} @ancestors;
682              
683 3 50       150 open(my $fd, "<:encoding($self->{encoding})", $canon_file_path)
684             or croak __PACKAGE__ . '->_read_file() can\'t open '
685             . "$canon_file_path: $!";
686 3         275 my $str = '';
687             {
688 3         3 local $/;
  3         10  
689 3 50       113 defined ($str = readline $fd) or
690             croak __PACKAGE__ . '->_read_file() can\'t read from '
691             . "$file_name: $!";
692             }
693 3         211 $str =~ s/\x0D?\x0A/\n/g;
694 3         8 push @ancestors, $canon_file_path;
695 3         211 $str =~ s/$self->{regexp}{include}/$self->_read_file($1, @ancestors)/ogme;
  2         24  
696              
697 3         69 return $str;
698             }
699              
700              
701             #
702             # Private functions ...
703             #
704              
705             sub _access_template {
706             #
707             # my $result = _access_template($access_str, $template_ref);
708             #
709             # The $access_str starts always at the template root and uses the
710             # following notation:
711             #
712             # 'bla', same as '/bla', is root element 'bla'
713             # 'bla/blub' is element 'blub' of section 'bla'
714             # 'bla/blub/blib' is element 'blib' of section 'blub',
715             # assuming the first section definition of 'blub',
716             # 'bla/blub/0/blib' is the same as 'bla/blub/blib'
717             # 'bla/blub/1 assumes 'blub' to be a section with a second definition
718             # etc.
719             #
720             # Elements can be sections or variables. For sections the array reference
721             # that contains the references to the data structures defined by the
722             # section name is returned (if no section count is given) or the section
723             # hash reference, for variables the usage count inside of
724             # their section.
725             #
726             # TODO: There's a problem with variable syntax (can contain '../Sec/Var')!
727             # (So variable references can't be detected.)
728             #
729 10     10   15 my ($access_str, $tmpl_ref) = @_;
730 10 50       39 croak __PACKAGE__ . '::_access_template(): '
731             . 'Template has to be a HASH reference!'
732             unless ref $tmpl_ref eq 'HASH';
733 10 50       32 croak __PACKAGE__ . '::_access_template(): '
734             . 'Access string is no SCALAR!'
735             unless ref \$access_str eq 'SCALAR';
736              
737 10 50       25 return unless $access_str;
738              
739 10         13 my $result = $tmpl_ref;
740 10         27 $access_str =~ s/^\///;
741 10         39 my @parts = split /\//, $access_str;
742              
743 10         20 foreach my $part (@parts) {
744 16 50       37 return unless $result;
745 16 100       46 if (ref $result eq 'HASH') {
    100          
746 10         36 $result = $result->{children}{$part};
747             }
748             elsif (ref $result eq 'ARRAY') {
749 5 100       35 $result = ($part =~ /^\d+$/) ? $result->[$part]
750             : $result->[0]{children}{$part};
751             }
752             else {
753 1         7 return;
754             }
755             }
756              
757 9         68 return $result;
758             }
759              
760             sub _access_data {
761             #
762             # my $data = _access_data($access_str, @data_refs);
763             #
764             # 'bla' is data in current section
765             # 'bla/blub' is subsection data (same as 'bla/0/blub' = first iteration)
766             # 'bla/2/blub' is third iteration data blub (iterations start w/ 0)
767             # '/bla' is root data
768             # '../bla' is parent data, '../../bla' is parent's parent data etc.
769             #
770             # and so on ...
771             #
772             # @data_refs is a stack of data references; topmost is the data ref of
773             # the current section (respectively of its current iteration), below the
774             # data ref of the parent section (respectively of its current iteration),
775             # and so forth, with the root data reference at the bottom.
776             #
777             # Therefor it is possible to access another iteration of the parent's data
778             # by going to the parent's parent data and down again from there ...
779             #
780 5     5   16 my ($access_str, @data_refs) = @_;
781             #
782             # Croak instead?
783             #
784 5 50       16 return unless scalar @data_refs;
785              
786 5 50       14 my $data = $access_str =~ s/^\/// ? $data_refs[0] : pop @data_refs ;
787 5         18 my @parts = split /\//, $access_str;
788             #
789             # Croak instead?
790             #
791 5 50       15 return unless scalar @parts;
792              
793 5         10 foreach my $part (@parts) {
794 5 50       15 next if $part eq '.'; # What if no parts left?
795 5 50       24 if ($part eq '..') {
    50          
    50          
796 0         0 $data = pop @data_refs;
797             }
798             elsif (ref $data eq 'ARRAY') {
799 0 0       0 $data = ($part =~ /^\d+$/) ? $data->[$part] : $data->[0]{$part};
800             }
801             elsif (ref $data eq 'HASH') {
802 5         18 $data = $data->{$part};
803             }
804             else {
805 0         0 return; # Is that right?
806             }
807             }
808            
809 5         25 return $data;
810             }
811              
812             #
813             # Pre-defined filter functions ...
814             #
815              
816             sub _filter_none {
817 7     7   70 local $_ = shift;
818 7         32 return $_;
819             }
820              
821             sub _filter_xml {
822 4     4   11 local $_ = shift;
823 4 50       15 croak __PACKAGE__ . "::_filter_xml: Undefined string not accepted!"
824             unless defined $_;
825 4 50       15 return '' unless length $_;
826 4         11 s/&/&/g;
827 4         11 s/
828 4         9 s/>/>/g;
829 4         9 s/'/'/g;
830 4         11 s/"/"/g;
831 4         20 return $_;
832             }
833              
834             sub _filter_html {
835 0     0   0 local $_ = shift;
836 0 0       0 croak __PACKAGE__ . "::_filter_xml: Undefined string not accepted!"
837             unless defined $_;
838 0 0       0 return '' unless length $_;
839 0         0 s/&/&/g;
840 0         0 s/
841 0         0 s/>/>/g;
842 0         0 s/"/"/g;
843 0         0 return $_;
844             }
845              
846             our $URI_ESCAPES; # Cache of escaped characters
847              
848             sub _filter_uri {
849             # URI escape a string. This code is borrowed from Gisle Aas' URI::Escape
850             # module, copyright 1995-2004. See RFC2396 for details.
851 3     3   6 my $str = shift;
852 3 50       16 return '' unless length $str;
853              
854 256         727 $URI_ESCAPES ||= {
855 3   100     13 map { ( chr($_), sprintf("%%%02X", $_) ) } (0..255),
856             };
857              
858 3 50       30 if ($] >= 5.008002) {
859 3 50       11 utf8::encode($str) if utf8::is_utf8($str);
860 3         18 $str =~ s/([^A-Za-z0-9\-_.!~*'()])/$URI_ESCAPES->{$1}/eg;
  53         154  
861             }
862             else {
863             # More reliable with older Perl versions, but complicated and slow ...
864             # This particular implementation is not yet tested (used in the past)!
865 8     8   111322 use bytes;
  8         23  
  8         1076  
866 0         0 my @bytes = split '', $str;
867 0         0 foreach my $byte (@bytes) {
868 0 0       0 $byte = uc("%" . unpack('H*', $byte)) if /[^A-Za-z0-9\-_.!~*'()]/;
869             };
870 0         0 $str = join '', @bytes;
871             }
872              
873 3         20 return $str;
874             }
875              
876             sub _filter_url {
877             # URI escape a string. This code is borrowed from Gisle Aas' URI::Escape
878             # module, copyright 1995-2004. See RFC2396 for details.
879             # Less agressive than _filter_uri().
880 3     3   7 my $str = shift;
881 3 50       12 return '' unless length $str;
882              
883 0         0 $URI_ESCAPES ||= {
884 3   50     10 map { ( chr($_), sprintf("%%%02X", $_) ) } (0..255),
885             };
886              
887 3 50       10 if ($] >= 5.008002) {
888 3 50       11 utf8::encode($str) if utf8::is_utf8($str);
889 3         18 $str =~ s/([^;\/?:@&=+\$,A-Za-z0-9\-_.!~*'()])/$URI_ESCAPES->{$1}/eg;
  39         134  
890             }
891             else {
892             # More reliable with older Perl versions, but complicated and slow ...
893             # This particular implementation is not yet tested (used in the past)!
894 8     8   3621 use bytes;
  8         20  
  8         39  
895 0         0 my @bytes = split '', $str;
896 0         0 foreach my $byte (@bytes) {
897 0 0       0 $byte = uc("%" . unpack('H*', $byte))
898             if /[^;\/?:@&=+\$,A-Za-z0-9\-_.!~*'()]/;
899             };
900 0         0 $str = join '', @bytes;
901             }
902              
903 3         20 return $str;
904             }
905              
906             1;
907              
908             __END__