File Coverage

blib/lib/Text/Reform.pm
Criterion Covered Total %
statement 341 439 77.6
branch 149 230 64.7
condition 81 147 55.1
subroutine 27 35 77.1
pod 2 16 12.5
total 600 867 69.2


line stmt bran cond sub pod time code
1             package Text::Reform;
2            
3 2     2   68280 use strict; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); use Carp;
  2     2   5  
  2     2   83  
  2         10  
  2         4  
  2         162  
  2         13  
  2         7  
  2         189  
4 2     2   47 use 5.005;
  2         6  
  2         831  
5             #use version;
6             $VERSION = '1.20';
7            
8             require Exporter;
9            
10             @ISA = qw(Exporter);
11             @EXPORT = qw( form );
12             @EXPORT_OK = qw( columns tag break_with break_at break_wrap break_TeX debug );
13            
14             my @bspecials = qw( [ | ] );
15             my @lspecials = qw( < ^ > );
16             my $ljustified = '[<]{2,}[>]{2,}';
17             my $bjustified = '[[]{2,}[]]{2,}';
18             my $bsingle = '~+';
19             my @specials = (@bspecials, @lspecials);
20             my $fixed_fieldpat = join('|', ($ljustified, $bjustified,
21             $bsingle,
22             map { "\\$_\{2,}" } @specials));
23             my ($lfieldmark, $bfieldmark, $fieldmark, $fieldpat, $decimal);
24             my $emptyref = '';
25            
26             sub import
27             {
28             #$decimal = localeconv()->{decimal_point} || '.';
29 2     2   26 $decimal = '.';
30            
31 2         7 my $lnumerical = '[>]+(?:'.quotemeta($decimal).'[<]{1,})';
32 2         8 my $bnumerical = '[]]+(?:'.quotemeta($decimal).'[[]{1,})';
33            
34 2         7 $fieldpat = join('|', ($lnumerical, $bnumerical,$fixed_fieldpat));
35            
36 2         6 $lfieldmark = join '|', ($lnumerical, $ljustified, map { "\\$_\{2}" } @lspecials);
  6         18  
37 2         7 $bfieldmark = join '|', ($bnumerical, $bjustified, $bsingle, map { "\\$_\{2}" } @bspecials);
  6         15  
38 2         9 $fieldmark = join '|', ($lnumerical, $bnumerical,
39             $bsingle,
40             $ljustified, $bjustified,
41             $lfieldmark, $bfieldmark);
42            
43 2         289 Text::Reform->export_to_level(1, @_);
44             }
45            
46             sub carpfirst {
47 2     2   12 use vars '%carped';
  2         4  
  2         7848  
48 0     0 0 0 my ($msg) = @_;
49 0 0       0 return if $carped{$msg}++;
50 0         0 carp $msg;
51             }
52            
53             ###### USEFUL TOOLS ######################################
54            
55             #===== form =============================================#
56            
57 1     1 0 227 sub BAD_CONFIG { 'Configuration hash not allowed between format and data' }
58            
59             sub break_with
60             {
61 10     10 0 3881 my $hyphen = $_[0];
62 10         19 my $hylen = length($hyphen);
63 10         12 my @ret;
64             sub
65             {
66 42 50   42   71 if ($_[2]<=$hylen)
67             {
68 0         0 @ret = (substr($_[0],0,1), substr($_[0],1))
69             }
70             else
71             {
72 42         158 @ret = (substr($_[0],0,$_[1]-$hylen),
73             substr($_[0],$_[1]-$hylen))
74             }
75 42 50       133 if ($ret[0] =~ /\A\s*\Z/) { return ("",$_[0]); }
  0         0  
76 42         121 else { return ($ret[0].$hyphen,$ret[1]); }
77             }
78            
79 10         104 }
80            
81             sub break_at {
82 4     4 0 1026 my ($hyphen, $opts_ref) = @_;
83 4         7 my $hylen = length($hyphen);
84 4         8 my $except = $opts_ref->{except};
85 4         6 my @ret;
86             sub
87             {
88 10     10   14 my $max = $_[2]-$hylen;
89 10 50 100     180 if ($max <= 0) {
    100 66        
    100          
    50          
    50          
90 0         0 @ret = (substr($_[0],0,1), substr($_[0],1))
91             }
92             elsif (defined $except && $_[0] =~ m/\A (.{1,$max}) ($except .*)/xms) {
93 1         4 @ret = ($1,$2);
94             }
95             elsif (defined $except && $_[0] =~ m/\A ($except) (.*)/xms) {
96 1         3 @ret = ($1,$2);
97             }
98             elsif ($_[0] =~ /\A (.{1,$max}$hyphen) (.*)/xms) {
99 0         0 @ret = ($1,$2);
100             }
101             elsif (length($_[0])>$_[2]) {
102 8         37 @ret = (substr($_[0],0,$_[1]-$hylen).$hyphen,
103             substr($_[0],$_[1]-$hylen))
104             }
105             else {
106 0         0 @ret = ("",$_[0]);
107             }
108 10 50       33 if ($ret[0] =~ /\A\s*\Z/) { return ("",$_[0]); }
  0         0  
109 10         28 else { return @ret; }
110             }
111 4         46 }
112            
113             sub break_wrap
114             {
115 5 100   5 0 851 return \&break_wrap unless @_;
116 3         6 my ($text, $reqlen, $fldlen) = @_;
117 3 100       8 if ($reqlen==$fldlen) { $text =~ m/\A(\s*\S*)(.*)/s }
  2         10  
118 1         4 else { ("", $text) }
119             }
120            
121             my %hyp;
122             sub break_TeX
123             {
124 0   0 0 0 0 my $file = $_[0] || "";
125            
126 0 0       0 croak "Can't find TeX::Hyphen module"
127             unless require "TeX/Hyphen.pm";
128            
129 0 0 0     0 $hyp{$file} = TeX::Hyphen->new($file||undef)
130             || croak "Can't open hyphenation file $file"
131             unless $hyp{$file};
132            
133             return sub {
134 0     0   0 for (reverse $hyp{$file}->hyphenate($_[0])) {
135 0 0       0 if ($_ < $_[1]) {
136 0         0 return (substr($_[0],0,$_).'-',
137             substr($_[0],$_) );
138             }
139             }
140 0         0 return ("",$_[0]);
141             }
142 0         0 }
143            
144             my $debug = 0;
145 2880 50   2880   6765 sub _debug { print STDERR @_, "\n" if $debug }
146 0     0 0 0 sub debug { $debug = 1; }
147            
148             sub notempty
149             {
150 142     142 0 135 my $ne = ${$_[0]} =~ /\S/;
  142         333  
151 142         160 _debug("\tnotempty('${$_[0]}') = $ne\n");
  142         423  
152 142         607 return $ne;
153             }
154            
155             sub strtod1 {
156 27     27 0 40 my $n=shift;
157 27         45 my $real_re='((?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)'.
158             '(?:(?:[eE])(?:(?:[+-]?)(?:[0123456789]+))|))';
159 27 100       183 if ($n=~/^\s*$real_re(.*)$/os) {
160 23   100     121 return ($1,length($2 || ''));
161             } else {
162 4         9 return (undef,length($n));
163             }
164             }
165            
166             sub replace($$$$) # ($fmt, $len, $argref, $config)
167             {
168 161     161 0 189 my $ref = $_[2];
169 161         186 my $text = '';
170 161         174 my $rem = $_[1];
171 161         189 my $config = $_[3];
172 161         159 my $filled = 0;
173            
174 161 50       285 if ($config->{fill}) { $$ref =~ s/\A\s*// }
  0         0  
175 161         523 else { $$ref =~ s/\A[ \t]*// }
176            
177 161         229 my $fmtnum = length $_[0];
178            
179 161 100 100     745 if ($$ref =~ /\S/ && $fmtnum>2)
180             {
181 54         102 NUMERICAL:{
182 25         27 my ($ilen,$dlen) = map {length} $_[0] =~ m/([]>]+)\Q$decimal\E([[<]+)/;
  27         217  
183 27         63 my ($num,$unconsumed) = strtod1($$ref);
184            
185 27 100       70 if ($unconsumed == length $$ref)
186             {
187 4         12 $$ref =~ s/\s*\S*//;
188 4 100 66     26 redo NUMERICAL if $config->{numeric} =~ m/\bSkipNaN\b/i
189             && $$ref =~ m/\S/;
190 2         7 $text = '?' x $ilen . $decimal . '?' x $dlen;
191 2         2 $rem = 0;
192 2         6 return $text;
193             }
194 23         176 my $formatted = sprintf "%$fmtnum.${dlen}f", $num;
195 23 100       58 $text = (length $formatted > $fmtnum)
196             ? '#' x $ilen . $decimal . '#' x $dlen
197             : $formatted;
198 23 100 100     236 $text =~ s/(\Q$decimal\E\d+?)(0+)$/$1 . " " x length $2/e
  7         29  
199             unless $config->{numeric} =~ m/\bAllPlaces\b/i
200             || $num =~ /\Q$decimal\E\d\d{$dlen,}$/;
201 23 100       36 if ($unconsumed)
202             {
203 20 50       38 if ($unconsumed == length $$ref)
204 0         0 { $$ref =~ s/\A.[^0-9.+-]*// }
205             else
206 20         38 { substr($$ref,0,-$unconsumed) = ""}
207             }
208 3         7 else { $$ref = "" }
209 23         45 $rem = 0;
210             }
211             }
212             else
213             {
214 136         382 while ($$ref =~ /\S/)
215             {
216 181 100 66     839 if (!$config->{fill} && $$ref=~s/\A[ \t]*\n//)
217 11         13 { $filled = 2; last }
  11         17  
218 170 50       629 last unless $$ref =~ /\A(\s*)(\S+)(.*)\z/s;
219 170         436 my ($ws, $word, $extra) = ($1,$2,$3);
220 170         255 my $nonnl = $ws =~ /[^\n]/;
221 170 0       350 $ws =~ s/\n/$nonnl? "" : " "/ge if $config->{fill};
  0 50       0  
222 170 100       328 my $lead = ($config->{squeeze} ? ($ws ? " " : "") : $ws);
    100          
223 170         233 my $match = $lead . $word;
224 170         505 _debug "Extracted [$match]";
225 170 50 66     483 last if $text && $match =~ /\n/;
226 170         196 my $len1 = length($match);
227 170 100       261 if ($len1 <= $rem)
228             {
229 90         200 _debug "Accepted [$match]";
230 90         129 $text .= $match;
231 90         126 $rem -= $len1;
232 90         182 $$ref = $extra;
233             }
234             else
235             {
236 80         188 _debug "Need to break [$match]";
237             # was: if ($len1 > $_[1] and $rem-length($lead)>$config->{minbreak})
238 80 100       206 if ($rem-length($lead)>$config->{minbreak})
239             {
240 55         126 _debug "Trying to break '$match'";
241 55         137 my ($broken,$left) =
242             $config->{break}->($match,$rem,$_[1]);
243 55         81 $text .= $broken;
244 55         147 _debug "Broke as: [$broken][$left]";
245 55         98 $$ref = $left.$extra;
246 55         92 $rem -= length $broken;
247             }
248 80         134 last;
249             }
250             }
251 90         279 continue { $filled=1 }
252             }
253            
254 159 100 100     588 if (!$filled && $rem>0 && $$ref=~/\S/ && length $text == 0)
      100        
      100        
255             {
256 6         78 $$ref =~ s/^\s*(.{1,$rem})//;
257 6         13 $text = $1;
258 6         13 $rem -= length $text;
259             }
260            
261 159 100 100     1008 if ( $text=~/ / && $_[0] eq 'J' && $$ref=~/\S/ && $filled!=2 ) {
    100 66        
    100 66        
262             # FULLY JUSTIFIED
263 2         5 $text = reverse $text;
264 2 100       12 $text =~ s/( +)/($rem-->0?" ":"").$1/ge while $rem>0;
  4         20  
265 2         5 $text = reverse $text;
266             }
267             elsif ( $_[0] =~ /\>|\]/ ) { # RIGHT JUSTIFIED
268 42 100       111 substr($text,0,0) =
269             substr($config->{filler}{left} x $rem, -$rem)
270             if $rem > 0;
271             }
272             elsif ( $_[0] =~ /\^|\|/ ) { # CENTRE JUSTIFIED
273 6 100       16 if ($rem>0) {
274 2         7 my $halfrem = int($rem/2);
275 2         10 substr($text,0,0) =
276             substr($config->{filler}{left}x$halfrem, -$halfrem);
277 2         4 $halfrem = $rem-$halfrem;
278 2         6 $text .= substr($config->{filler}{right}x$halfrem, 0, $halfrem);
279             }
280             }
281             else { # LEFT JUSTIFIED
282 109 100       285 $text .= substr($config->{filler}{right}x$rem, 0, $rem)
283             if $rem > 0;
284             }
285            
286 159         406 return $text;
287             }
288            
289             my %std_config =
290             (
291             header => sub{""},
292             footer => sub{""},
293             pagefeed => sub{""},
294             pagelen => 0,
295             pagenum => undef,
296             pagewidth => 72,
297             break => break_with('-'),
298             minbreak => 2,
299             squeeze => 0,
300             filler => {left=>' ', right=>' '},
301             interleave => 0,
302             numeric => "",
303             _used => 1,
304             );
305            
306             sub lcr {
307 0     0 0 0 my ($data, $pagewidth, $header) = @_;
308 0   0     0 $data->{width} ||= $pagewidth;
309 0   0     0 $data->{left} ||= "";
310 0   0     0 $data->{centre} ||= $data->{center}||"";
      0        
311 0   0     0 $data->{right} ||= "";
312             return sub {
313 0 0   0   0 my @l = split "\n", (ref $data->{left} eq 'CODE'
314             ? $data->{left}->(@_) : $data->{left}), -1;
315 0 0       0 my @c = split "\n", (ref $data->{centre} eq 'CODE'
316             ? $data->{centre}->(@_) : $data->{centre}), -1;
317 0 0       0 my @r = split "\n", (ref $data->{right} eq 'CODE'
318             ? $data->{right}->(@_) : $data->{right}), -1;
319 0         0 my $text = "";
320 0   0     0 while (@l||@c||@r) {
      0        
321 0 0       0 my $l = @l ? shift(@l) : "";
322 0 0       0 my $c = @c ? shift(@c) : "";
323 0 0       0 my $r = @r ? shift(@r) : "";
324 0         0 my $gap = int(($data->{width}-length($c))/2-length($l));
325 0 0       0 if ($gap < 0) {
326 0         0 $gap = 0;
327 0 0       0 carpfirst "\nWarning: $header is wider than specified page width ($data->{width} chars)" if $^W;
328             }
329 0         0 $text .= $l . " " x $gap
330             . $c . " " x ($data->{width}-length($l)-length($c)-$gap-length($r))
331             . $r
332             . "\n";
333             }
334 0         0 return $text;
335             }
336 0         0 }
337            
338             sub fix_config(\%)
339             {
340 24     24 0 34 my ($config) = @_;
341 24 50       96 if (ref $config->{header} eq 'HASH') {
    50          
342 0         0 $config->{header} =
343             lcr $config->{header}, $config->{pagewidth}, 'header';
344             }
345             elsif (ref $config->{header} eq 'CODE') {
346 24         36 my $tmp = $config->{header};
347             $config->{header} = sub {
348 30     30   52 my $header = &$tmp;
349 30 50       82 return (ref $header eq 'HASH')
350             ? lcr($header,$config->{pagewidth},'header')->()
351             : $header;
352             }
353 24         106 }
354             else {
355 0         0 my $tmp = $config->{header};
356 0     0   0 $config->{header} = sub { $tmp }
357 0         0 }
358 24 50       85 if (ref $config->{footer} eq 'HASH') {
    100          
359 0         0 $config->{footer} =
360             lcr $config->{footer}, $config->{pagewidth}, 'footer';
361             }
362             elsif (ref $config->{footer} eq 'CODE') {
363 22         33 my $tmp = $config->{footer};
364             $config->{footer} = sub {
365 25     25   43 my $footer = &$tmp;
366 25 50       61 return (ref $footer eq 'HASH')
367             ? lcr($footer,$config->{pagewidth},'footer')->()
368             : $footer;
369             }
370 22         107 }
371             else {
372 2         3 my $tmp = $config->{footer};
373 7     7   11 $config->{footer} = sub { $tmp }
374 2         7 }
375 24 100       90 unless (ref $config->{pagefeed} eq 'CODE')
376 2     3   4 { my $tmp = $config->{pagefeed}; $config->{pagefeed} = sub { $tmp } }
  2         8  
  3         5  
377 24 100       63 unless (ref $config->{break} eq 'CODE')
378 1         5 { $config->{break} = break_at($config->{break}) }
379 24 50 66     75 if (defined $config->{pagenum} && ref $config->{pagenum} ne 'SCALAR')
380 0         0 { my $tmp = $config->{pagenum}+0; $config->{pagenum} = \$tmp }
  0         0  
381 24 50       64 unless (ref $config->{filler} eq 'HASH') {
382 0         0 $config->{filler} = { left => "$config->{filler}",
383             right => "$config->{filler}" }
384             }
385             }
386            
387             sub FormOpt::DESTROY
388             {
389 1 50 33 1   428 print STDERR "\nWarning: lexical &form configuration at $std_config{_line} was never used.\n"
390             if $^W && !$std_config{_used};
391 1         2 %std_config = %{$std_config{_prev}};
  1         17  
392             }
393            
394             sub form
395             {
396 2     2   23 use vars '%carped';
  2         4  
  2         757  
397 56     56 1 26525 local %carped;
398 56         569 my $config = {%std_config};
399 56         121 my $startidx = 0;
400 56 100 66     310 if (@_ && ref($_[0]) eq 'HASH') # RESETTING CONFIG
401             {
402 23 100       50 if (@_ > 1) # TEMPORARY RESET
    50          
403             {
404 22         82 $config = {%$config, %{$_[$startidx++]}};
  22         186  
405 22         118 fix_config(%$config);
406 22         37 $startidx = 1;
407             }
408             elsif (defined wantarray) # CONTEXT BEING CAPTURED
409             {
410 1         8 $_[0]->{_prev} = { %std_config };
411 1         3 $_[0]->{_used} = 0;
412 1         6 $_[0]->{_line} = join " line ", (caller)[1..2];;
413 1         5 %{$_[0]} = %std_config = (%std_config, %{$_[0]});
  1         10  
  1         21  
414 1         8 fix_config(%std_config);
415 1         7 return bless $_[0], 'FormOpt';
416             }
417             else # PERMANENT RESET
418             {
419 0         0 $_[0]->{_used} = 1;
420 0         0 $_[0]->{_line} = join " line ", (caller)[1..2];;
421 0         0 %std_config = (%std_config, %{$_[0]});
  0         0  
422 0         0 fix_config(%std_config);
423 0         0 return;
424             }
425             }
426 55 50       128 $config->{pagenum} = do{\(my $tmp=1)}
  55         97  
427             unless defined $config->{pagenum};
428            
429 55         82 $std_config{_used}++;
430 55         84 my @ref = map { ref } @_;
  142         323  
431 55         109 my @orig = @_;
432 55         100 my $caller = caller;
433 2     2   13 no strict;
  2         4  
  2         7517  
434            
435 55         132 for (my $nextarg=0; $nextarg<@_; $nextarg++)
436             {
437 142         217 my $next = $_[$nextarg];
438 142 50 66     685 if (!defined $next) {
    100 100        
    50          
    100          
    100          
    100          
439 0         0 my $tmp = "";
440 0         0 splice @_, $nextarg, 1, \$tmp;
441             }
442             elsif ($ref[$nextarg] eq 'ARRAY') {
443 5         29 splice @_, $nextarg, 1, \join("\n", @$next)
444             }
445             elsif ($ref[$nextarg] eq 'HASH' && $next->{cols} ) {
446 0 0       0 croak "Missing 'from' data for 'cols' option"
447             unless $next->{from};
448 0 0       0 croak "Can't mix other options with 'cols' option"
449             if keys %$next > 2;
450 0         0 my ($cols, $data) = @{$next}{'cols','from'};
  0         0  
451 0 0 0     0 croak "Invalid 'cols' option.\nExpected reference to array of column specifiers but found " . (ref($cols)||"'$cols'")
452             unless ref $cols eq 'ARRAY';
453 0 0 0     0 croak "Invalid 'from' data for 'cols' option.\nExpected reference to array of hashes or arrays but found " . (ref($data)||"'$data'")
454             unless ref $data eq 'ARRAY';
455 0         0 splice @_, $nextarg, 2, columns(@$cols,@$data);
456 0         0 splice @ref, $nextarg, 2, ('ARRAY')x@$cols;
457 0         0 $nextarg--;
458             }
459 137         403 elsif (!defined eval { local $SIG{__DIE__};
460 137         816 $_[$nextarg] = $next;
461 47         232 _debug "writeable: [$_[$nextarg]]";
462 47         312 1})
463             {
464 90         258 _debug "unwriteable: [$_[$nextarg]]";
465 90         161 my $arg = $_[$nextarg];
466 90         381 splice @_, $nextarg, 1, \$arg;
467             }
468             elsif (!$ref[$nextarg]) {
469 21         78 splice @_, $nextarg, 1, \$_[$nextarg];
470             }
471             elsif ($ref[$nextarg] ne 'HASH' and $ref[$nextarg] ne 'SCALAR')
472             {
473 1         4 splice @_, $nextarg, 1, \"$next";
474             }
475             }
476            
477 55         69 my $header = $config->{header}->(${$config->{pagenum}});
  55         145  
478 55 100 66     166 $header.="\n" if $header && substr($header,-1,1) ne "\n";
479            
480 55         59 my $footer = $config->{footer}->(${$config->{pagenum}});
  55         142  
481 55 100 66     128 $footer.="\n" if $footer && substr($footer,-1,1) ne "\n";
482            
483 55         75 my $prevfooter = $footer;
484            
485 55         102 my $linecount = $header=~tr/\n/\n/ + $footer=~tr/\n/\n/;
486 55         63 my $hfcount = $linecount;
487            
488 55         65 my $text = $header;
489 55         56 my @format_stack;
490            
491 55   66     141 LINE: while ($startidx < @_ || @format_stack)
492             {
493 60 100 100     287 if (($ref[$startidx]||'') eq 'HASH')
494             {
495 1         6 $config = {%$config, %{$_[$startidx++]}};
  1         8  
496 1         7 fix_config(%$config);
497 1         3 next;
498             }
499 59 50       110 unless (@format_stack) {
500             @format_stack = $config->{interleave}
501             ? map "$_\n", split /\n/, ${$_[$startidx++]}||""
502 59 50 0     127 : ${$_[$startidx++]}||"";
      50        
503             }
504 59         97 my $format = shift @format_stack;
505 59         154 _debug("format: [$format]");
506            
507 59         882 my @parts = split /(\n|(?:\\.)+|$fieldpat)/, $format;
508 59 50 33     312 push @parts, "\n" unless @parts && $parts[-1] eq "\n";
509 59         72 my $fieldcount = 0;
510 59         64 my $filled = 0;
511 59         58 my $firstline = 1;
512 59         95 while (!$filled)
513             {
514 151         168 my $nextarg = $startidx;
515 151         150 my @data;
516 151         194 foreach my $part ( @parts )
517             {
518 553 100 100     6629 if ($part =~ /\A(?:\\.)+/)
    100          
    100          
519             {
520 2         7 _debug("esc literal: [$part]");
521 2         3 my $tmp = $part;
522 2         20 $tmp =~ s/\\(.)/$1/g;
523 2         4 $text .= $tmp;
524             }
525             elsif ($part =~ /($lfieldmark)/)
526             {
527 21 100       38 if ($firstline)
528             {
529 20         22 $fieldcount++;
530 20 50       49 if ($nextarg > $#_)
531 0         0 { push @_,\$emptyref; push @ref, '' }
  0         0  
532 20         39 my $type = $1;
533 20 100       73 $type = 'J' if $part =~ /$ljustified/;
534 20 100       48 croak BAD_CONFIG if ($ref[$startidx] eq 'HASH');
535 19         51 _debug("once field: [$part]");
536 19         22 _debug("data was: [${$_[$nextarg]}]");
  19         62  
537 19         52 $text .= replace($type,length($part),$_[$nextarg],$config);
538 19         25 _debug("data now: [${$_[$nextarg]}]");
  19         54  
539             }
540             else
541             {
542 1         5 $text .= substr($config->{filler}{left} x length($part), -length($part));
543 1         5 _debug("missing once field: [$part]");
544             }
545 20         28 $nextarg++;
546             }
547             elsif ($part =~ /($fieldmark)/ and substr($part,0,2) ne '~~')
548             {
549 142 100       306 $fieldcount++ if $firstline;
550 142 50       283 if ($nextarg > $#_)
551 0         0 { push @_,\$emptyref; push @ref, '' }
  0         0  
552 142         278 my $type = $1;
553 142 100       387 $type = 'J' if $part =~ /$bjustified/;
554 142 50       282 croak BAD_CONFIG if ($ref[$startidx] eq 'HASH');
555 142         352 _debug("multi field: [$part]");
556 142         159 _debug("data was: [${$_[$nextarg]}]");
  142         423  
557 142         364 $text .= replace($type,length($part),$_[$nextarg],$config);
558 142         233 _debug("data now: [${$_[$nextarg]}]");
  142         438  
559 142         359 push @data, $_[$nextarg];
560 142         180 $nextarg++;
561             }
562             else
563             {
564 388         935 _debug("literal: [$part]");
565 388         518 my $tmp = $part;
566 388         478 $tmp =~ s/\0(\0*)/$1/g;
567 388         419 $text .= $tmp;
568 388 100       909 if ($part eq "\n")
569             {
570 153         156 $linecount++;
571 153 100 100     449 if ($config->{pagelen} && $linecount>=$config->{pagelen})
572             {
573 5         15 _debug("\tejecting page: $config->{pagenum}");
574 5 50 33     39 carpfirst "\nWarning: could not format page ${$config->{pagenum}} within specified page length"
  0   33     0  
575             if $^W && $config->{pagelen} && $linecount > $config->{pagelen};
576 5         6 ${$config->{pagenum}}++;
  5         8  
577 5         7 my $pagefeed = $config->{pagefeed}->(${$config->{pagenum}});
  5         13  
578 5         6 $header = $config->{header}->(${$config->{pagenum}});
  5         12  
579 5 100 66     24 $header.="\n" if $header && substr($header,-1,1) ne "\n";
580 5         9 $text .= $footer
581             . $pagefeed
582             . $header;
583 5         7 $prevfooter = $footer;
584 5         4 $footer = $config->{footer}->(${$config->{pagenum}});
  5         12  
585 5 100 66     21 $footer.="\n" if $footer && substr($footer,-1,1) ne "\n";
586 5         8 $linecount = $hfcount =
587             $header=~tr/\n/\n/ + $footer=~tr/\n/\n/;
588 5         20 $header = $pagefeed
589             . $header;
590             }
591             }
592             }
593 552         1300 _debug("\tnextarg now: $nextarg");
594 552         1415 _debug("\tstartidx now: $startidx");
595             }
596 150         201 $firstline = 0;
597 150         272 $filled = ! grep { notempty $_ } @data;
  142         230  
598             }
599 58         330 $startidx += $fieldcount;
600             }
601            
602             # ADJUST FINAL PAGE HEADER OR FOOTER AS REQUIRED
603 54 100 100     317 if ($hfcount && $linecount == $hfcount) # UNNEEDED HEADER
    100 100        
604             {
605 1         21 $text =~ s/\Q$header\E\Z//;
606             }
607             elsif ($linecount && $config->{pagelen}) # MISSING FOOTER
608             {
609 3         8 $text .= "\n" x ($config->{pagelen}-$linecount)
610             . $footer;
611 3         5 $prevfooter = $footer;
612             }
613            
614             # REPLACE LAST FOOTER
615            
616 54 100       108 if ($prevfooter) {
617 2         3 my $lastfooter = $config->{footer}->(${$config->{pagenum}},1);
  2         6  
618 2 50 33     14 $lastfooter.="\n"
619             if $lastfooter && substr($lastfooter,-1,1) ne "\n";
620 2         11 my $footerdiff = ($lastfooter =~ tr/\n/\n/)
621             - ($prevfooter =~ tr/\n/\n/);
622             # Enough space to squeeze longer final footer in?
623 2         5 my $tail = '^[^\S\n]*\n' x $footerdiff;
624 2 50 33     6 if ($footerdiff > 0 && $text =~ /($tail\Q$prevfooter\E)\Z/m) {
625 0         0 $prevfooter = $1;
626 0         0 $footerdiff = 0;
627             }
628             # Apparently, not, so create an extra (empty) page for it
629 2 50       6 if ($footerdiff > 0) {
630 0         0 ${$config->{pagenum}}++;
  0         0  
631 0         0 my $lastheader = $config->{header}->(${$config->{pagenum}});
  0         0  
632 0 0 0     0 $lastheader.="\n"
633             if $lastheader && substr($lastheader,-1,1) ne "\n";
634 0         0 $lastfooter = $config->{footer}->(${$config->{pagenum}},1);
  0         0  
635 0 0 0     0 $lastfooter.="\n"
636             if $lastfooter && substr($lastfooter,-1,1) ne "\n";
637            
638 0         0 $text .= $lastheader
639             . ("\n" x ( $config->{pagelen}
640             - ($lastheader =~ tr/\n/\n/)
641             - ($lastfooter =~ tr/\n/\n/)
642             )
643             )
644             . $lastfooter;
645             }
646             else {
647 2         5 $lastfooter = ("\n"x-$footerdiff).$lastfooter;
648 2         6 substr($text, -length($prevfooter)) = $lastfooter;
649             }
650             }
651            
652             # RESTORE ARG LIST
653 54         120 for my $i (0..$#orig)
654             {
655 139 100       373 if ($ref[$i] eq 'ARRAY')
    100          
656 5         6 { eval { @{$orig[$i]} = map "$_\n", split /\n/, ${$_[$i]} } }
  5         6  
  5         17  
  5         12  
657             elsif (!$ref[$i])
658 109 50       124 { eval { _debug("restoring $i (".$_[$i].") to " .
  109         460  
659             defined($orig[$i]) ? $orig[$i] : "");
660 109         135 ${$_[$i]} = $orig[$i] } }
  109         323  
661             }
662            
663 54         77 ${$config->{pagenum}}++;
  54         95  
664 54 50       114 $text =~ s/[ ]+$//gm if $config->{trim};
665 54 50       436 return $text unless wantarray;
666 0         0 return map "$_\n", split /\n/, $text;
667             }
668            
669            
670             #==== columns ========================================#
671            
672             sub columns {
673 0     0 0 0 my @cols;
674 0         0 my (@fullres, @res);
675 0         0 while (@_) {
676 0         0 my $arg = shift @_;
677 0         0 my $type = ref $arg;
678 0 0       0 if ($type eq 'HASH') {
    0          
679 0         0 push @{$res[$_]}, $arg->{$cols[$_]} for 0..$#cols;
  0         0  
680             }
681             elsif ($type eq 'ARRAY') {
682 0         0 push @{$res[$_]}, $arg->[$cols[$_]] for 0..$#cols;
  0         0  
683             }
684             else {
685 0 0       0 if (@res) {
686 0         0 push @fullres, @res;
687 0         0 @res = @cols = ();
688             }
689 0         0 push @cols, $arg;
690             }
691             }
692 0         0 return @fullres, @res;
693             }
694            
695            
696             #==== tag ============================================#
697            
698             sub invert($)
699             {
700 7     7 0 12 my $inversion = reverse $_[0];
701 7         25 $inversion =~ tr/{[<(/}]>)/;
702 7         22 return $inversion;
703             }
704            
705             sub tag # ($tag, $text; $opt_endtag)
706             {
707 7     7 1 2767 my ($tagleader,$tagindent,$ldelim,$tag,$tagargs,$tagtrailer) =
708             ( $_[0] =~ /\A((?:[ \t]*\n)*)([ \t]*)(\W*)(\w+)(.*?)(\s*)\Z/ );
709            
710 7 100       23 $ldelim = '<' unless $ldelim;
711 7         27 $tagtrailer =~ s/([ \t]*)\Z//;
712 7   100     36 my $textindent = $1||"";
713            
714 7         14 my $rdelim = invert $ldelim;
715            
716 7         10 my $i;
717 7   100     46 for ($i = -1; -1-$i < length $rdelim && -1-$i < length $tagargs; $i--)
718             {
719 5 100       24 last unless substr($tagargs,$i,1) eq substr($rdelim,$i,1);
720             }
721 7 100       15 if ($i < -1)
722             {
723 3         4 $i++;
724 3         6 $tagargs = substr($tagargs,0,$i);
725 3         5 $rdelim = substr($rdelim,$i);
726             }
727            
728 7   33     26 my $endtag = $_[2] || "$ldelim/$tag$rdelim";
729            
730 17         62 return "$tagleader$tagindent$ldelim$tag$tagargs$rdelim$tagtrailer".
731 7         28 join("\n",map { "$tagindent$textindent$_" } split /\n/, $_[1]).
732             "$tagtrailer$tagindent$endtag$tagleader";
733            
734             }
735            
736            
737             1;
738            
739             __END__