File Coverage

blib/lib/Hub/Parse/Parser.pm
Criterion Covered Total %
statement 56 278 20.1
branch 9 144 6.2
condition 1 55 1.8
subroutine 7 20 35.0
pod 8 8 100.0
total 81 505 16.0


line stmt bran cond sub pod time code
1             package Hub::Parse::Parser;
2 1     1   6 use strict;
  1         2  
  1         41  
3 1     1   5 use Hub qw/:lib/;
  1         2  
  1         6  
4             our $VERSION = '4.00043';
5             our @EXPORT = qw//;
6             our @EXPORT_OK = qw/
7             PARSER_ALL_BEGIN
8             PARSER_ALL_END
9             PARSER_MAX_DEPTH
10             PARSER_MAX_SCOPE_DEPTH
11             /;
12             use constant {
13 1         3873 PARSER_ALL_BEGIN => '[#',
14             PARSER_ALL_END => ']',
15             # recursion accross _populate calls consequently, also max replacements per
16             # template
17             PARSER_MAX_DEPTH => 10000,
18             PARSER_MAX_SCOPE_DEPTH => 100,
19 1     1   9 };
  1         3  
20              
21              
22             # ------------------------------------------------------------------------------
23             # %EVALUATORS - Routines invoked when the parser encounters a directive
24             #
25             # Each subroutine will be called with three arguments, a pointer back to the
26             # parser, a parameter hash, and a result hash:
27             #
28             # my ($self, $params, $result) = @_;
29             #
30             # $params ARRAY
31             #
32             # 0 outer_str $ The directive text which should be replaced
33             # 1 fields \@ The parsed parameters from the directive
34             # 2 pos \$ Current position
35             # 3 text \$ The template
36             # 4 parents \@ Array of ancestors which this should be parsed into
37             # 5 valdata \@ Current stack of value data
38             #
39             # $result HASH:
40             #
41             # 'value' $ The new value
42             # 'width' $ Width of data to be replaced with 'value'
43             # 'keep_ws' $ Keep whitespace around the original directive
44             # 'goto' $ Go to this position after the replacement
45             # ------------------------------------------------------------------------------
46              
47             our %EVALUATORS;
48              
49             $EVALUATORS{'parser'} = sub {
50             my ($self, $params, $result) = @_;
51             my ($outer_str, $fields, $pos, $text, $parents, $valdata) = @$params;
52             if ($$fields[1] eq 'off') {
53             $$result{'goto'} = $self->_find_point($text, $$pos, 'parser', 'on');
54             }
55             if ($$fields[1] eq 'off' || $$fields[1] eq 'on') {
56             $$result{'value'} = undef;
57             }
58             };
59              
60             # ------------------------------------------------------------------------------
61             # new - Construct a new instance
62             # new -template => \$text|$text, [options]
63             #
64             # options:
65             #
66             # -var_begin => $string # Identifies beginning of a variable (no regexp)
67             # -var_end => $string # Identifies the end of a variable (no regexp)
68             # ------------------------------------------------------------------------------
69              
70             sub new {
71 7     7 1 16 my $self = shift;
72 7   33     39 my $class = ref( $self ) || $self;
73 7         22 my $obj = bless {}, $class;
74 7         50 $obj->refresh(@_);
75 7         27 return $obj;
76             }#new
77              
78             # ------------------------------------------------------------------------------
79             # refresh - Return instance to initial state
80             # ------------------------------------------------------------------------------
81              
82             sub refresh {
83              
84 7     7 1 50 my ($self,$opts) = Hub::objopts(\@_, {
85             'template' => '',
86             'var_begin' => PARSER_ALL_BEGIN,
87             'var_end' => PARSER_ALL_END,
88             'max_depth' => Hub::bestof($$Hub{'/conf/parser/max_depth'},
89             PARSER_MAX_DEPTH),
90             'max_scope_depth' => Hub::bestof($$Hub{'/conf/parser/max_scope_depth'},
91             PARSER_MAX_SCOPE_DEPTH),
92             });
93 7 50       33 croak "Illegal call to instance method" unless ref($self);
94              
95             # Template may be provided as arg1
96 7 50 0     16 @_ and $$opts{'template'} ||= shift;
97              
98             # Set member variables via options
99 7         28 foreach my $k (keys %$opts) {
100 42         81 $self->{$k} = $$opts{$k};
101             }
102              
103             # Create characters for inner matching
104 7         30 $self->{'beg_char'} = substr $self->{'var_begin'}, 0, 1;
105 7         443 $self->{'end_char'} = substr $self->{'var_end'}, 0, 1;
106              
107             # Create regex-able versions of the terminators
108 7         20 $self->{'regex_begin'} = $self->{'var_begin'};
109 7         17 $self->{'regex_end'} = $self->{'var_end'};
110 7         62 $self->{'regex_begin'} =~ s/(?
111 7         57 $self->{'regex_end'} =~ s/(?
112              
113             }#refresh
114              
115             # ------------------------------------------------------------------------------
116             # populate \HASH+
117             #
118             # Populate our template with provided variable definitions.
119             #
120             # PARAMETERS:
121             #
122             # \HASH Variable name to definition map
123             # ------------------------------------------------------------------------------
124             #|test(match) my $parser = mkinst( 'Parser', -template => 'Hello [#who]' );
125             #| ${$parser->populate( { who => 'World' } )};
126             #~ Hello World
127             # ------------------------------------------------------------------------------
128              
129             sub populate {
130 7     7 1 24 my ($self,$opts) = Hub::objopts( \@_ );
131 7         22 $self->{'values'} = \@_;
132 7         13 $self->{'*depth'} = 0;
133 7         18 $self->{'*exit_point'} = 0;
134 7         26 my $text = $self->_populate();
135 7         24 my $parser_directives = $$self{'regex_begin'}
136             . 'parser ["\'](on|off)["\']' . $$self{'regex_end'} . '[\r\n]{0,2}';
137 7         99 $$text =~ s/$parser_directives//g;
138 7         26 return $text;
139             }#populate
140              
141             # ------------------------------------------------------------------------------
142             # _populate [OPTIONS], \HASH+
143             #
144             # Internal worker function.
145             # Recursive.
146             #
147             # PARAMETERS:
148             #
149             # \HASH Variable name to definition map
150             #
151             # OPTIONS:
152             #
153             # -text \SCALAR Template text to populate
154             # ------------------------------------------------------------------------------
155              
156             sub _populate {
157            
158 7     7   25 my ($self,$opts) = Hub::objopts(\@_);
159 7 50       29 my $text = defined $$opts{'text'} ? $$opts{'text'} : $self->{'template'};
160 7 50       48 ref($text) eq 'SCALAR' and $text = $$text;
161 7         12 $self->{'*depth'}++;
162 7 50       16 return unless defined $text;
163              
164             # Parsing constants
165 7         15 my $BEGIN = $self->{'var_begin'};
166 7         11 my $END = $self->{'var_end'};
167 7         12 my $BEGINCHAR = $self->{'beg_char'};
168 7         13 my $ENDCHAR = $self->{'end_char'};
169 7         11 my $MAX_DEPTH = $self->{'max_depth'};
170 7         11 my $MAX_SCOPE_DEPTH = $self->{'max_scope_depth'};
171              
172 7         13 my @parents = (); # templates we will pass the parsed text into
173 7         13 my %skip = (); # remember undefined values
174 7         10 my $p = 0; # string position as we progress
175 7         20 $self->{'*replace_count'} = 0;
176              
177             # recursion control (high level, templates calling templates)
178 7 50       19 croak "High-level recursion limit ($MAX_DEPTH) exceeded"
179             . $self->get_hint($p, \$text)
180             if $self->{'*depth'} > $MAX_DEPTH;
181              
182             # recursion control (medium level, like foreach loops)
183 7 50       18 croak "Medium-level recursion limit ($MAX_SCOPE_DEPTH) exceeded"
184             . $self->get_hint($p, \$text)
185             if @_ > $MAX_SCOPE_DEPTH;
186              
187 7         17 while( $p > -1 ) {
188              
189             # find the beginning of a variable definition: '['
190 7         90 $p = index( $text, $BEGIN, $p );
191 7 50       18 last unless $p > -1;
192              
193             # recursion control (low level, variable nesting)
194 0 0       0 if ($p > $self->{'*exit_point'}) {
195 0         0 $self->{'*replace_count'} = 0;
196 0         0 $self->{'*exit_point'} = $p;
197             } else {
198 0 0       0 if ($self->{'*replace_count'} > $MAX_DEPTH) {
199 0         0 croak "Low-level recursion limit ($MAX_DEPTH) exceeded"
200             . $self->get_hint($p, \$text);
201             }
202             }
203              
204             # find the end of this definition: ']'
205 0         0 my $p2 = $p + length($BEGIN); # start of the current search
206 0         0 my $p3 = index( $text, $ENDCHAR, $p2 ); # point of closing
207 0         0 while( $p3 > -1 ) {
208 0         0 my $ic = 0; # inner count of begin chars
209 0         0 my $im = index( $text, $BEGINCHAR, $p2 ); # inner match
210 0   0     0 while( ($im > -1) && ($im < $p3) ) {
211 0         0 $ic++;
212 0         0 $p2 = ($im + 1);
213 0         0 $im = index( $text, $BEGINCHAR, $p2 );
214             }
215 0 0       0 last unless $ic > 0;
216 0         0 for( 1 .. $ic ) {
217 0         0 $p3 = index( $text, $ENDCHAR, ($p3 + 1) );
218             }
219             }
220              
221             # unterminated variable
222 0 0       0 if( $p3 <= $p ) {
223 0         0 warn "Unterminated variable ($p3)" . $self->get_hint($p, \$text);
224 0         0 $p += length($BEGIN);
225 0         0 next;
226             }#if
227              
228             # inside the '[#' .. ']' marks
229 0         0 my $inner_str = substr( $text, ($p + length($BEGIN)),
230             ($p3 - ($p + length($BEGIN))) );
231              
232             # include the '[#' and ']' marks
233 0         0 my $outer_str = substr( $text, $p,
234             (($p3 + length($END)) - $p) );
235              
236             # evaluate inner '[#..]' matches first
237 0 0       0 if (index($inner_str, $BEGIN) > -1) {
238 0         0 my $inner_val = ${$self->_populate(-text => \$inner_str, @_)};
  0         0  
239 0         0 $self->{'*depth'}--;
240 0 0 0     0 if(defined $inner_val && $inner_val ne $inner_str) {
241 0         0 $self->remove_variables(\$inner_val);
242             # replace
243 0         0 substr $text, $p + length($BEGIN),
244             length($inner_str), $inner_val;
245 0         0 next; # repeat without moving pointer
246             } else {
247             # unresolved, move on
248 0         0 $p += length($outer_str);
249 0         0 next;
250             }
251             }
252              
253             # Break apart the inner string into fields
254 0         0 my @fields = ();
255 0 0       0 if ($inner_str =~ /^["'](.*)["']$/) {
256 0         0 push @fields, $1;
257             } else {
258 0         0 @fields = map {s/[\r\n]+//g; $_} split
  0         0  
  0         0  
259             /\s+["']{1}|=["']{1}|(?
260 0 0       0 next unless (@fields); # empty construct
261             # Account for un-quoted first parameter
262 0         0 my @name_fields = split /\s+/, $fields[0];
263 0 0       0 if (@name_fields > 1) {
264 0         0 shift @fields;
265 0         0 unshift @fields, @name_fields;
266             }
267             }
268              
269             # Evaluate the match
270 0         0 $self->_evaluate($fields[0],
271             [$outer_str, \@fields, \$p, \$text, \@parents, \@_,]);
272             }
273              
274             # Parents are templates specified by the 'into' directive
275 7 50       18 my $result = ref($text) eq 'SCALAR' ? $text : \$text;
276 7         21 while (my $parent = pop @parents) {
277 0         0 my $contents = $Hub->resolve($$parent{'into'});
278 0 0       0 if (defined $contents) {
279 0 0       0 if (defined $$parent{'as'}) {
280             # Do not reparse this text
281 0         0 substr $$result, 0, 0, '[#parser "off"]';
282 0         0 $$result .= '[#parser "on"]';
283             # Populate the parent with ourselves
284 0         0 $result = $self->_populate(-text => $contents, {
285             $$parent{'as'} => $result,
286             }, @_);
287             } else {
288 0         0 $result = $self->_populate(-text => $contents . $$result, @_);
289             }
290             }
291             }
292              
293 7         29 return $result; # scalar ref
294             }
295              
296             # ------------------------------------------------------------------------------
297             # get_evaluator - Hook into evaluator loop by overriding this method.
298             # get_evaluator $directive
299             #
300             # Returns a subroutine (CODE) reference.
301             #
302             # This method is used by this base class to get the evaluator when a particular
303             # directive is incountered. For instance, if the template contains:
304             #
305             # Hello [#if 'var1' eq 'var2']
306             #
307             # get_evaluator('if') will be called. See L for
308             # an example of how this class is extended.
309             # ------------------------------------------------------------------------------
310              
311             sub get_evaluator {
312 0     0 1   return $EVALUATORS{$_[1]};
313             }#get_evaluator
314              
315             # ------------------------------------------------------------------------------
316             # _evaluate - Evaluate the expression
317             # _evaluate \@value_data, @parameters
318             #
319             # Where @parameters are:
320             #
321             # -fields => \@fields
322             # -outer_str => $outer_str
323             # -pos => $position
324             # -text => \$text
325             # ------------------------------------------------------------------------------
326              
327             sub _evaluate {
328 0     0     my $self = shift;
329 0 0         croak "Illegal call to instance method" unless ref($self);
330 0 0         my $name = shift or croak 'Provide an address to evaluate';
331 0           my $params = shift;
332 0           my ($outer_str, $fields, $pos, $text, $parents, $valdata) = @$params;
333              
334             # Return values
335 0           my $result = {
336             'value' => undef,
337             'width' => length($outer_str),
338             'keep_ws' => 0,
339             'goto' => 0,
340             };
341              
342 0           my $evaluator = $self->get_evaluator($name);
343 0 0         if (ref $evaluator eq 'CODE') {
344 0           &$evaluator($self, $params, $result);
345             } else {
346 0           $$result{'keep_ws'} = 1;
347 0           shift @$fields; # strip off $name
348             # Get value (with parameters)
349 0           $$result{'value'} = $self->resolve($name, \@$valdata, $fields);
350 0 0         if (defined $$result{'value'}) {
351             # Infinite recursion variables
352 0           $self->{'*replace_count'}++;
353 0           $self->{'*exit_point'} += length($$result{'value'}) - $$result{'width'};
354             # if ($self->{'*replace_count'} > 4) {
355             # warn "Approaching MAX_DEPTH: $name"
356             # . $self->get_hint($$pos, $text);
357             # }
358             } else {
359 0 0         warn "Value not found", $self->get_hint($$pos, $text)
360             if $$Hub{'/sys/ENV/DEBUG'};
361             }
362             }
363              
364             # Replace the directive
365 0 0         if (defined $$result{'value'}) {
366             # Eat whitespace due to indenting (limit of 80 char indent)
367 0 0         unless($$result{'keep_ws'}) {
368 0           my @padding = _padding($text, $$pos, $$result{'width'});
369 0 0         if (@padding) {
370 0           $$pos -= $padding[0];
371 0           $$result{'width'} += $padding[0];
372 0           $$result{'width'} += $padding[1];
373             }
374             }
375             # Do the replacement
376 0           substr($$text, $$pos, $$result{'width'}, $$result{'value'});
377             # Infinite recursion control. Trim out directives which extract portions
378             # of the template (like #define and if/else blocks)
379 0 0         if (length($$result{'value'}) == 0) {
380 0           $self->{'*exit_point'} -= $$result{'width'};
381             }
382             } else {
383 0   0       $$result{'goto'} ||= $$pos + $$result{'width'};
384             }
385 0 0         $$pos = $$result{'goto'} if ($$result{'goto'})
386              
387             }#_evaluate
388              
389             # ------------------------------------------------------------------------------
390             # get_value - Search the provided hashes for a value
391             # get_value $name, $hash, [$hash..]
392             # ------------------------------------------------------------------------------
393              
394             sub get_value {
395 0     0 1   my ($self, $name, $valdata, $params) = @_;
396 0 0         croak "Illegal call to instance method" unless ref($self);
397 0           my $value = undef;
398 0 0         return unless $name;
399             # Literal values are encapsulated in quotes
400 0           my ($literal) = $name =~ /^['"](.*)['"]$/;
401 0 0         return $literal if defined $literal;
402             # Executable variables
403 0 0         if ($name =~ s/^\!//) {
404 0   0       $params ||= [];
405             push @$params, ('-_get_value', sub {
406 0     0     my ($n, $vd, $p) = @_;
407 0   0       $vd ||= [];
408 0           push @$vd, @$valdata;
409 0           $self->resolve($n, $vd, $p);
410 0           });
411 0           my ($file, $method) = split ':{1,2}', $name;
412 0 0         if ($$Hub{$file}) {
413 0           return Hub::modexec(-filename => $file, -method => $method, $params);
414             } else {
415 0           return warn "Cannot find module: $file";
416             }
417             }
418             # Search value data for the value
419 0           foreach my $h (@$valdata, @{$self->{'values'}}) {
  0            
420 0 0         next unless defined $h;
421 0 0         if (ref($h)) {
422 0 0         $value = isa($h, 'Hub::Base::Registry')
423             ? $$h{$name}
424             : Hub::getv($h, $name);
425             }
426 0 0         last if defined $value;
427             }
428             # Alternative value
429 0 0 0       if (!defined($value) && defined $params && @$params) {
      0        
430             # Make params hash-friendly
431             ## push @$params, undef if ((scalar (@$params) % 2) != 0);
432             # if ((scalar (@$params) % 2) != 0) {
433             # warn "Odd number of elements: ", join(", ", @$params), "\n";
434             # }
435 0           my ($param_opts, %params) = Hub::hashopts($params);
436 0 0 0       if (defined $params{'or'} && $params{'or'} ne $name) {
437 0           $value = $self->resolve($params{'or'}, $valdata, $params);
438             }
439             }
440 0           return $value;
441             }
442              
443             # ------------------------------------------------------------------------------
444             # resolve - Get a string representation of a value
445             # ------------------------------------------------------------------------------
446              
447             sub resolve {
448 0     0 1   my $self = shift;
449 0 0         croak "Illegal call to instance method" unless ref($self);
450 0           my $value = $self->get_value(@_);
451             # Convert objects to strings
452 0 0         if (ref($value)) {
453 0           $value = Hub::resolve($value);
454             # if (UNIVERSAL::can($value,'get_content')) {
455             # $value = $value->get_content();
456             # } elsif (UNIVERSAL::can($value,'populate')) {
457             # $value = ${$value->populate()};
458             # } elsif (ref($value) eq 'SCALAR') {
459             # $value = $$value;
460             # }
461             }
462 0 0 0       if (defined $value && !ref($value)) {
    0          
463 0           my ($name, $valdata, $params) = @_;
464 0 0 0       if (defined $params && @$params && $name !~ /^\!/) {
      0        
465             # Populate value with parameters
466 0           my %args = @$params;
467 0 0 0       $value = ${$self->_populate( -text => $value, \%args, @$valdata)}
  0            
468             unless ((@$params == 2) && (defined $args{'or'}));
469             }
470             } elsif (isa($value, 'HASH')) {
471 0           $value = Hub::hprint($value);
472             }
473 0           return $value;
474             }#resolve
475              
476             # ------------------------------------------------------------------------------
477             # _value_of - The default string value of an object
478             # ------------------------------------------------------------------------------
479              
480             sub _to_string {
481 0     0     my $self = shift;
482             # Translate file objects into their relative pathname
483 0 0         if (UNIVERSAL::isa($_[0], 'Hub::Data::File')) {
484 0           my $path = Hub::abspath($_[0]{'*filename'});
485 0 0         if (defined $$Hub{'/sys/ENV/WORKING_DIR'}) {
486 0           $path = substr $path, length($$Hub{'/sys/ENV/WORKING_DIR'});
487             }
488 0           return $path;
489             }
490 0           return $_[0];
491             }#_to_string
492              
493             # ------------------------------------------------------------------------------
494             # get_hint - Show where we are in parsing the text
495             # get_hint $position, \$text
496             # ------------------------------------------------------------------------------
497              
498             sub get_hint {
499 0     0 1   my $self = shift;
500 0           my ($p, $text) = @_;
501 0           my $hint = substr($$text, $p, 60);
502 0           $hint =~ s/[\n]/\\n/g;
503 0           $hint =~ s/[\r]/\\r/g;
504 0           return " at char[$p]: '$hint...'";
505 0           last;
506             }
507              
508             # ------------------------------------------------------------------------------
509             # remove_variables - Remove variable statements from the text
510             # remove_variables \$text
511             # This will *not* remove parents of nested variables.
512             # ------------------------------------------------------------------------------
513              
514             sub remove_variables {
515 0     0 1   my $self = shift;
516 0           my $str = shift;
517 0 0         croak "Illegal call to instance method" unless ref($self);
518 0 0         croak "Provide a scalar reference" unless ref($str) eq 'SCALAR';
519             # Prefix with '\' for regex pattern
520 0           my $BEGIN = '\\' . $self->{'var_begin'};
521 0           my $END = '\\' . $self->{'var_end'};
522 0           $$str =~ s/$BEGIN[^($BEGIN)]+?$END//g;
523             }
524              
525             # ------------------------------------------------------------------------------
526             # _get_block - Find the block for a given directive
527             # _get_block $start_position, \$text, $type
528             # ------------------------------------------------------------------------------
529              
530             sub _get_block {
531 0     0     my ($self, $start_p, $text, $type) = @_;
532 0 0         croak "Illegal call to instance method" unless ref($self);
533 0 0         croak "Provide a scalar reference" unless ref($text) eq 'SCALAR';
534 0           my $subtext = '';
535             # Find start of conditional text
536 0           while (substr($$text, $start_p, 1) =~ /[\r\n]/) {
537 0           $start_p++;
538             }
539             # Find the end point
540 0           my $end_p = $self->_find_end_point($text, $start_p, $type);
541 0 0         if ($end_p > 0) {
542 0           $subtext = substr $$text, $start_p, $end_p - $start_p;
543             } else {
544 0           $subtext = substr $$text, $start_p;
545 0           $end_p = length($$text) - 1;
546             }
547 0           return ($end_p, \$subtext);
548             }
549              
550             # ------------------------------------------------------------------------------
551             # _find_point - Find the next occurance of a single argument directive
552             # _find_point \$text, $begin_point, $directive_name, $argument_value
553             #
554             # If you are looking for: [#parser "on"] then you would use this method as:
555             #
556             # _find_point($text, $pos, 'parser', 'on');
557             # ------------------------------------------------------------------------------
558              
559             sub _find_point {
560 0     0     my $self = shift;
561 0 0         croak "Illegal call to instance method" unless ref($self);
562 0           my ($text, $pos, $name, $arg) = @_;
563             # TODO Pack this regular expression better (remove backslash prefix hack)
564 0           my $str = '\\' . $self->{'var_begin'} . $name . "\\s+['\"]" . $arg . "['\"]"
565             . '\\' . $self->{'var_end'};
566 0           my $p = Hub::indexmatch($$text, $str, $pos);
567 0 0         return $p > 0 ? $p : length($$text) - 1;
568             }#_find_point
569              
570             # ------------------------------------------------------------------------------
571             # _find_end_point - Find the '[#end "???"]' marker
572             # _find_end_point - \$text, $begin_point, $type
573             #
574             # Returns the beg
575             # ------------------------------------------------------------------------------
576              
577             sub _find_end_point {
578 0     0     my $self = shift;
579 0 0         croak "Illegal call to instance method" unless ref($self);
580              
581 0           my ($text, $p, $type) = @_;
582 0           my $begin_str = $self->{'var_begin'} . $type;
583 0           my $end_p = $self->_find_point($text, $p, 'end', $type);
584              
585             # Account for nested elements
586 0           my $nested_p = $p;
587 0           while (($nested_p =
588             index($$text, $begin_str, $nested_p)) > -1) {
589 0           $nested_p += length($begin_str);
590 0 0         last if $nested_p > $end_p;
591 0           my $start_p = index $$text, $self->{'var_end'}, $end_p;
592 0           $end_p = $self->_find_point($text, $start_p, 'end', $type);
593 0 0         die "Directive not terminated"
594             . $self->get_hint($p, $text) if $end_p < 0;
595             }
596              
597 0 0         if ($end_p >= 0) {
598 0           my $closing_p = index($$text, $self->{'var_end'}, $end_p) +1;
599 0           my $width = ($closing_p - $end_p);
600             #warn "end[$width]=$$text'", substr($$text, ($end_p), ($width)), "'\n";
601 0           my @padding = _padding($text, $end_p, $width);
602 0 0         $end_p -= $padding[0] if @padding;
603             #warn "removing $padding[0]\n" if @padding;
604 0           return $end_p;
605             } else {
606 0           return length($$text) - 1;
607             }
608              
609             }
610              
611             # ------------------------------------------------------------------------------
612             # _padding - Get number of preceeding and trailing whitespace characters
613             # _padding \$text, $pos, $width
614             #
615             # \$text template
616             # $pos current position in $$text
617             # $width width of the current match
618             #
619             # Returns an array of widths: ($w1, $w2)
620             #
621             # $w1 = Number of preceeding whitespace characters
622             # $w2 = Number of trailing whitespace characters
623             #
624             # Returns an empty array if non-whitespace characters are found in the
625             # preceeding or trailing regions.
626             #
627             # We will look up to 80 characters in front of the current position (ie, 80
628             # character indent maximum.)
629             # ------------------------------------------------------------------------------
630              
631             sub _padding {
632              
633 0     0     my ($text, $pos, $width) = @_;
634 0           my ($prefix, $suffix, $starts_line) = ();
635              
636 0 0         if ($pos == 0) {
637 0           $prefix = 0;
638 0           $starts_line = 1;
639             } else {
640 0           for my $i (1 .. 80) {
641 0           my $prev_c = substr $$text, $pos - $i, 1;
642 0 0         last unless $prev_c =~ /\s/;
643 0 0         $prefix = 0 if !defined $prefix;
644 0 0 0       if (($prev_c eq "\r") || ($prev_c eq "\n")) {
645 0           $starts_line = 1;
646 0 0         if ($i > 1) {
647 0           $prefix = $i - 1;
648             }
649 0           last;
650             }
651             }
652             }
653              
654 0 0         if ($starts_line) {
655 0           $suffix = 0;
656 0           my $next_p = $pos + $width;
657 0           my $last_c = '';
658 0           for my $i (0 .. 1) {
659 0           my $next_c = substr $$text, $next_p + $i, 1;
660 0 0 0       if ((($next_c eq "\r") || ($next_c eq "\n"))
      0        
661             && ($next_c ne $last_c)) {
662 0           $suffix++;
663 0           $last_c = $next_c;
664             } else {
665 0           last;
666             }
667             }
668             }
669              
670 0 0 0       return defined $prefix && defined $suffix
671             ? ($prefix, $suffix)
672             : ();
673              
674             }#_padding
675              
676             sub _split_if_else {
677 0     0     my $self = shift;
678 0           my $text = shift;
679 0 0         croak "Illegal call to instance method" unless ref($self);
680 0           my $str_if = $self->{'var_begin'} . 'if';
681 0           my $str_else = $self->{'var_begin'} . 'else' . $self->{'var_end'};
682 0           my $p = 0;
683 0           while ($p > -1) {
684 0           my $p_else = index($$text, $str_else, $p);
685 0 0         if ($p_else > -1) {
686 0           my $p_if = index($$text, $str_if, $p );
687 0 0 0       if (($p_if > -1) && ($p_if < $p_else)) {
688 0           $p = $self->_find_end_point($text, $p_if + length($str_if), 'if');
689             #warn "p=$p p_else=$p_else p_if=$p_if\n";
690             } else {
691 0           my $separator = length($str_else);
692 0           my $terminator = substr($$text, $p_else + $separator, 2);
693 0           $separator += $terminator =~ s/[\r\n]//g;
694             return (
695 0           substr($$text, 0, $p_else),
696             substr($$text, $p_else + $separator)
697             );
698             }
699             } else {
700 0           return($$text,'');
701             }
702             }
703             }
704              
705             # ------------------------------------------------------------------------------
706             1;
707              
708             __END__