File Coverage

blib/lib/Text/Autoformat.pm
Criterion Covered Total %
statement 192 323 59.4
branch 91 198 45.9
condition 44 95 46.3
subroutine 19 23 82.6
pod 0 7 0.0
total 346 646 53.5


line stmt bran cond sub pod time code
1             package Text::Autoformat;
2             $Text::Autoformat::VERSION = '1.75';
3 5     5   268456 use 5.006;
  5         51  
4 5     5   28 use strict;
  5         8  
  5         142  
5 5     5   25 use warnings;
  5         7  
  5         163  
6 5     5   36 use Carp;
  5         10  
  5         392  
7              
8             require Exporter;
9              
10 5     5   3096 use Text::Reform qw( form tag break_at break_with break_wrap break_TeX );
  5         33840  
  5         34  
11 5     5   3237 use Text::Autoformat::Hang;
  5         14  
  5         185  
12 5     5   2094 use Text::Autoformat::NullHang;
  5         13  
  5         2986  
13              
14             our @ISA = qw(Exporter);
15             our @EXPORT = qw( autoformat );
16             our @EXPORT_OK =
17             qw( form tag break_at break_with break_wrap break_TeX ignore_headers );
18              
19              
20             my %std_highlight_ignore = map {$_=>1} qw {
21             a an at as and are
22             but by
23             ere
24             for from
25             in into is
26             of on onto or over
27             per
28             the to that than
29             until unto upon
30             via
31             with while whilst within without
32             };
33              
34             my $STD_HIGHLIGHT_IGNORES = sub {
35             my ($word) = @_;
36             return $std_highlight_ignore{lc $word} ? recase($word,'lower') : recase($word,'title');
37             };
38              
39             my @entities = qw {
40             Á á Â â Æ æ
41             À à Α α Ã ã
42             Ä ä Β β Ç ç
43             Χ χ Δ δ É é
44             Ê ê È è Ε ε
45             Η η Ð ð Ë ë
46             Γ γ Í í Î î
47             Ì ì Ι ι Ï ï
48             Κ κ Λ λ Μ μ
49             Ñ ñ Ν ν Ó ó
50             Ô ô Œ œ Ò ò
51             Ω ω Ο ο Õ õ
52             Ö ö Φ φ Π π
53             ″ ′ Ψ ψ Ρ ρ
54             Š š Σ σ Τ τ
55             Θ θ Þ þ Ú ú
56             Û û Ù ù Υ υ
57             Ü ü Ξ ξ Ý ý
58             Ÿ ÿ Ζ ζ
59             };
60              
61             my %lower_entities = @entities;
62             my %upper_entities = reverse @entities;
63              
64             my %casing = (
65             lower => [ \%lower_entities, \%lower_entities,
66             sub { $_ = lc }, sub { $_ = lc } ],
67             upper => [ \%upper_entities, \%upper_entities,
68             sub { $_ = uc }, sub { $_ = uc } ],
69             title => [ \%upper_entities, \%lower_entities,
70             sub { $_ = ucfirst lc }, sub { $_ = lc } ],
71             );
72              
73             my $default_margin = 72;
74             my $default_widow = 10;
75              
76             $Text::Autoformat::widow_slack = 0.1;
77              
78              
79             sub defn($)
80             {
81 150 50   150 0 508 return $_[0] if defined $_[0];
82 0         0 return "";
83             }
84              
85             my $ignore_headers = qr/
86             \A
87             (?: From \b .* $)?
88             (?: [^:\n]+ : .* \n
89             (?: [ \t] .* \n)*
90             )+
91             \s*
92             \Z
93             /mx;
94             my $ignore_indent = qr/^[^\S\n].*(\n[^\S\n].*)*$/;
95              
96 6 100   6 0 37 sub ignore_headers { $_[0] && /$ignore_headers/ }
97              
98             # BITS OF A TEXT LINE
99              
100             my $quotechar = qq{[!#%=|:;]};
101             my $quotechunk = qq{(?:$quotechar(?![a-z])|(?:[a-z]\\w*)?>+)};
102             my $QUOTER = qq{(?:(?i)(?:$quotechunk(?:[ \\t]*$quotechunk)*))};
103              
104             my $separator = q/(?:[-_]{2,}|[=#*]{3,}|[+~]{4,})/;
105              
106 5     5   6034 use overload;
  5         4905  
  5         30  
107             sub autoformat # ($text, %args)
108             {
109 6     6 0 2368 my ($text,%args,$toSTDOUT);
110              
111 6         21 foreach ( @_ )
112             {
113 11 100 33     79 if (ref eq 'HASH')
    50 33        
114 5         31 { %args = (%args, %$_) }
115             elsif (!defined($text) && !ref || overload::Method($_,'""'))
116 6         17 { $text = "$_" }
117             else {
118 0         0 croak q{Usage: autoformat([text],[{options}])}
119             }
120             }
121              
122 6 50       23 unless (defined $text) {
123 0         0 $text = join("",);
124 0         0 $toSTDOUT = !defined wantarray();
125             }
126              
127 6 100       34 return $text unless $text =~ /\S/;
128              
129 5 100       19 $args{right} = $default_margin unless exists $args{right};
130 5 50       21 $args{justify} = "" unless exists $args{justify};
131 5 50 50     35 $args{widow} = 0 if ($args{justify}||"") =~ /full/;
132 5 50       18 $args{widow} = $default_widow unless exists $args{widow};
133 5 50       22 $args{case} = '' unless exists $args{case};
134 5 100       14 $args{lists} = 1 unless exists $args{lists};
135 5 50       19 $args{squeeze} = 1 unless exists $args{squeeze};
136 5 50       18 $args{gap} = 0 unless exists $args{gap};
137 5 50       30 $args{break} = break_at('-') unless exists $args{break};
138 5         89 $args{impfill} = ! exists $args{fill};
139 5         16 $args{expfill} = $args{fill};
140 5 50       20 $args{tabspace} = 8 unless exists $args{tabspace};
141 5 50       18 $args{renumber} = 1 unless exists $args{renumber};
142 5 50       15 $args{autocentre} = 1 unless exists $args{autocentre};
143 5 50       16 $args{_centred} = 1 if $args{justify} =~ /cent(er(ed)?|red?)/;
144 5   100     22 $args{all} ||= $args{mail};
145              
146             # SPECIAL IGNORANCE...
147 5 100       15 if ($args{ignore}) {
148 2         5 $args{all} = 1;
149 2         6 $args{ignore} = _build_ignore( $args{ignore} );
150             }
151             else {
152 3     5   14 $args{ignore} = sub{0};
  5         10  
153             }
154              
155 5 100       18 if ( $args{mail} ) {
156 1         2 my $ignore = $args{ignore};
157 1 100   7   5 $args{ignore} = sub { $ignore->(@_) || ignore_headers(@_) };
  7         17  
158             }
159            
160             # DETABIFY
161 5         62 my @rawlines = split /\n/, $text;
162 5     5   4703 use Text::Tabs; $tabstop = $args{tabspace};
  5         3609  
  5         15686  
  5         14  
163 5         21 @rawlines = expand(@rawlines);
164              
165             # HANDLE QUOTING CHANGE
166 5 50       281 my $quoter = exists $args{quoter} ? $args{quoter} : $QUOTER;
167 5         16 $quoter =~ s//$QUOTER/g;
168              
169             # PARSE EACH LINE
170              
171 5         10 my $pre = 0;
172 5         8 my @lines;
173 5         14 foreach (@rawlines)
174             {
175 30         86 push @lines, { raw => $_ };
176 30 50       553 s/\A([ \t]*)($quoter?)([ \t]*)//
177             or die "Internal Error ($@) on '$_'";
178 30         80 $lines[-1]{presig} = $lines[-1]{prespace} = defn $1;
179 30         113 $lines[-1]{presig} .= $lines[-1]{quoter} = defn $2;
180 30         63 $lines[-1]{presig} .= $lines[-1]{quotespace} = defn $3;
181              
182 30         102 $lines[-1]{hang} = Text::Autoformat::Hang->new($_, $args{lists});
183              
184 30 50       262 s/([ \t]*)(.*?)(\s*)$//
185             or die "Internal Error ($@) on '$_'";
186 30         67 $lines[-1]{hangspace} = defn $1;
187 30         55 $lines[-1]{text} = defn $2;
188 30   100     80 $lines[-1]{empty} = $lines[-1]{hang}->empty() && $2 !~ /\S/;
189 30         232 $lines[-1]{separator} = $lines[-1]{text} =~ /^$separator$/;
190             }
191              
192             # SUBDIVIDE DOCUMENT INTO COHERENT SUBSECTIONS
193              
194 5         11 my @chunks;
195 5         18 push @chunks, [shift @lines];
196 5         15 foreach my $line (@lines)
197             {
198 25 100 33     168 if ($line->{separator} ||
      66        
      66        
      100        
199             $line->{quoter} ne $chunks[-1][-1]->{quoter} ||
200             $line->{empty} ||
201             @chunks && $chunks[-1][-1]->{empty})
202             {
203 10         21 push @chunks, [$line];
204             }
205             else
206             {
207 15         20 push @{$chunks[-1]}, $line;
  15         37  
208             }
209             }
210              
211              
212              
213             # DETECT CENTRED PARAS
214              
215 5         14 CHUNK: foreach my $chunk ( @chunks )
216             {
217 15 50 66     96 next CHUNK if !$args{autocentre} || @$chunk < 2 || $chunk->[0]{hang};
      66        
218 0         0 my @length;
219 0         0 my $ave = 0;
220 0         0 foreach my $line (@$chunk)
221             {
222             my $prespace = $line->{quoter} ? $line->{quotespace}
223 0 0       0 : $line->{prespace};
224             my $pagewidth =
225 0         0 2*length($prespace) + length($line->{text});
226 0         0 push @length, [length $prespace,$pagewidth];
227 0         0 $ave += $pagewidth;
228             }
229 0         0 $ave /= @length;
230 0         0 my $diffpre = 0;
231 0         0 foreach my $l (0..$#length)
232             {
233 0 0       0 next CHUNK unless abs($length[$l][1]-$ave) <= 2;
234 0 0 0     0 $diffpre ||= $length[$l-1][0] != $length[$l][0]
235             if $l > 0;
236             }
237 0 0       0 next CHUNK unless $diffpre;
238 0         0 foreach my $line (@$chunk)
239             {
240 0         0 $line->{centred} = 1;
241             ($line->{quoter} ? $line->{quotespace}
242 0 0       0 : $line->{prespace}) = "";
243             }
244             }
245              
246             # REDIVIDE INTO PARAGRAPHS
247              
248 5         10 my @paras;
249 5         14 foreach my $chunk ( @chunks )
250             {
251 15         19 my $first = 1;
252 15         20 my $firstfrom;
253 15         25 foreach my $line ( @{$chunk} )
  15         25  
254             {
255 30 100 66     148 if ($first ||
      66        
      66        
256             $line->{quoter} ne $paras[-1]->{quoter} ||
257             $paras[-1]->{separator} ||
258             !$line->{hang}->empty
259             )
260             {
261 17         31 push @paras, $line;
262 17         52 $first = 0;
263 17         43 $firstfrom = length($line->{raw}) - length($line->{text});
264             }
265             else
266             {
267 13         29 my $extraspace = length($line->{raw}) - length($line->{text}) - $firstfrom;
268 13 100       28 $extraspace = 0 if $extraspace < 0;
269 13         36 $paras[-1]->{text} .= "\n" . q{ }x$extraspace . $line->{text};
270 13         36 $paras[-1]->{raw} .= "\n" . $line->{raw};
271             }
272             }
273             }
274              
275             # SELECT PARAS TO HANDLE
276              
277 5         11 my $remainder = "";
278 5 50       16 if ($args{all}) { # STOP AT MAIL TERMINATOR IF $args{mail}
279 5         10 my $lastignored = 1;
280 5         16 for my $index (0..$#paras) {
281 17         46 local $_ = $paras[$index]{raw} . "\n";
282 17         35 $paras[$index]{ignore} = $args{ignore}($lastignored);
283 17   100     56 $lastignored &&= $paras[$index]{ignore};
284 17 50 66     62 next unless $args{mail} && /^--\s*$/;
285 0         0 $remainder = join "\n", map { $_->{raw} } splice @paras, $index;
  0         0  
286 0 0       0 $remainder .= "\n" unless $remainder =~ /\n\z/;
287 0         0 last;
288             }
289             }
290             else { # JUST THE FIRST PARA
291 0         0 $remainder = join "\n", map { $_->{raw} } @paras[1..$#paras];
  0         0  
292 0 0       0 $remainder .= "\n" unless $remainder =~ /\n\z/;
293 0         0 @paras = ( $paras[0] );
294             }
295              
296             # RE-CASE TEXT
297 5 50       14 if ($args{case}) {
298 0         0 foreach my $para ( @paras ) {
299 0 0       0 next if $para->{ignore};
300 0 0       0 if (ref $args{case} eq 'CODE') {
    0          
    0          
    0          
    0          
    0          
301 0         0 $para->{text} = entitle($para->{text}, $args{case});
302             }
303             elsif ($args{case} =~ /upper/i) {
304 0         0 $para->{text} = recase($para->{text}, 'upper');
305             }
306             elsif ($args{case} =~ /lower/i) {
307 0         0 $para->{text} = recase($para->{text}, 'lower');
308             }
309             elsif ($args{case} =~ /title/i) {
310 0         0 $para->{text} = entitle($para->{text}, 0);
311             }
312             elsif ($args{case} =~ /highlight/i) {
313 0         0 $para->{text} = entitle($para->{text}, $STD_HIGHLIGHT_IGNORES);
314             }
315             elsif ($args{case} =~ /sentence(\s*)/i) {
316 0         0 my $trailer = $1;
317 0 0 0     0 $args{squeeze}=0 if $trailer && $trailer ne " ";
318 0         0 ensentence();
319 0         0 $para->{text} =~ s/(\S+(\s+|$))/ensentence($1, $trailer)/ge;
  0         0  
320             }
321 0         0 $para->{text} =~ s/\b([A-Z])[.]/\U$1./gi; # ABBREVS
322             }
323             }
324              
325             # ALIGN QUOTERS
326             # DETERMINE HANGING MARKER TYPE (BULLET, ALPHA, ROMAN, ETC.)
327              
328 5         12 my %sigs;
329 5         7 my $lastquoted = 0;
330 5         8 my $lastprespace = 0;
331 5         14 for my $i ( 0..$#paras )
332             {
333 17         21 my $para = $paras[$i];
334 17 100       44 next if $para->{ignore};
335              
336 13 50       26 if ($para->{quoter})
337             {
338 0 0       0 if ($lastquoted) { $para->{prespace} = $lastprespace }
  0         0  
339 0         0 else { $lastquoted = 1; $lastprespace = $para->{prespace} }
  0         0  
340             }
341             else
342             {
343 13         22 $lastquoted = 0;
344             }
345             }
346              
347             # RENUMBER PARAGRAPHS
348              
349 5         12 for my $para ( @paras ) {
350 17 100       39 next if $para->{ignore};
351 13         40 my $sig = $para->{presig} . $para->{hang}->signature();
352 13         22 push @{$sigs{$sig}{hangref}}, $para;
  13         49  
353             $sigs{$sig}{hangfields} = $para->{hang}->fields()-1
354 13 100       87 unless defined $sigs{$sig}{hangfields};
355             }
356              
357 5         29 while (my ($sig,$val) = each %sigs) {
358 5 50       28 next unless $sig =~ /rom/;
359 0         0 field: for my $field ( 0..$val->{hangfields} )
360             {
361 0         0 my $romlen = 0;
362 0         0 foreach my $para ( @{$val->{hangref}} )
  0         0  
363             {
364 0         0 my $hang = $para->{hang};
365 0         0 my $fieldtype = $hang->field($field);
366             next field
367 0 0 0     0 unless $fieldtype && $fieldtype =~ /rom|let/;
368 0 0       0 if ($fieldtype eq 'let') {
369 0         0 foreach my $para ( @{$val->{hangref}} ) {
  0         0  
370 0         0 $hang->field($field=>'let')
371             }
372             }
373             else {
374 0         0 $romlen += length $hang->val($field);
375             }
376             }
377             # NO ROMAN LETTER > 1 CHAR -> ALPHABETICS
378 0 0       0 if ($romlen <= @{$val->{hangref}}) {
  0         0  
379 0         0 foreach my $para ( @{$val->{hangref}} ) {
  0         0  
380 0         0 $para->{hang}->field($field=>'let')
381             }
382             }
383             }
384             }
385              
386 5         10 my %prev;
387              
388 5         14 for my $para ( @paras ) {
389 17 100       38 next if $para->{ignore};
390 13         32 my $sig = $para->{presig} . $para->{hang}->signature();
391 13 50       33 if ($args{renumber}) {
392 13 50       32 unless ($para->{quoter}) {
393 13         54 $para->{hang}->incr($prev{""}, $prev{$sig});
394             $prev{""} = $prev{$sig} = $para->{hang}
395 13 100       36 unless $para->{hang}->empty;
396             }
397             }
398            
399             # COLLECT MAXIMAL HANG LENGTHS BY SIGNATURE
400              
401 13         35 my $siglen = $para->{hang}->length();
402             $sigs{$sig}{hanglen} = $siglen
403             if ! $sigs{$sig}{hanglen} ||
404 13 100 66     93 $sigs{$sig}{hanglen} < $siglen;
405             }
406              
407             # PROPAGATE MAXIMAL HANG LENGTH
408              
409 5         22 while (my ($sig,$val) = each %sigs)
410             {
411 5         11 foreach (@{$val->{hangref}}) {
  5         17  
412 13         34 $_->{hanglen} = $val->{hanglen};
413             }
414             }
415              
416             # BUILD FORMAT FOR EACH PARA THEN FILL IT
417              
418 5         11 $text = "";
419 5 50 33     30 my $gap = @paras && $paras[0]->{empty} ? 0 : $args{gap};
420 5         15 for my $para ( @paras )
421             {
422 17 100       39 if ($para->{empty}) {
423 5         12 $gap += 1 + ($para->{text} =~ tr/\n/\n/);
424             }
425 17 100       35 if ($para->{ignore}) {
426 4 50       16 $text .= (!$para->{empty} ? "\n"x($args{gap}-$gap > 0 ? ($args{gap}-$gap) : 0) : "") ;
    50          
427 4         9 $text .= $para->{raw};
428 4 50       13 $text .= "\n" unless $para->{raw} =~ /\n\z/;
429             }
430             else {
431             my $leftmargin = $args{left} ? " "x($args{left}-1)
432 13 50       36 : $para->{prespace};
433 13   66     48 my $hlen = $para->{hanglen} || $para->{hang}->length;
434 13 50       42 my $hfield = ($hlen==1 ? '~' : '>'x$hlen);
435 13         24 my @hang;
436 13 100       38 push @hang, $para->{hang}->stringify if $hlen;
437             my $format = $leftmargin
438             . quotemeta($para->{quoter})
439             . $para->{quotespace}
440             . $hfield
441 13         42 . $para->{hangspace};
442 13         46 my $rightslack = int (($args{right}-length $leftmargin)*$Text::Autoformat::widow_slack);
443 13         34 my ($widow_okay, $rightindent, $firsttext, $newtext) = (0,0);
444 13   33     19 do {
445             my $tlen = $args{right}-$rightindent-length($leftmargin
446             . $para->{quoter}
447             . $para->{quotespace}
448             . $hfield
449 13         38 . $para->{hangspace});
450 13 50       41 next if blockquote($text,$para, $format, $tlen, \@hang, \%args);
451             my $tfield = ( $tlen==1 ? '~'
452             : $para->{centred}||$args{_centred} ? '|'x$tlen
453             : $args{justify} eq 'right' ? ']'x$tlen
454             : $args{justify} eq 'full' ? '['x($tlen-2) . ']]'
455 13 50 33     122 : $para->{centred}||$args{_centred} ? '|'x$tlen
    50 33        
    50          
    50          
    50          
456             : '['x$tlen
457             );
458 13         30 my $tryformat = "$format$tfield";
459             $newtext = (!$para->{empty} ? "\n"x($args{gap} > $gap ? ($args{gap}-$gap) : 0) : "")
460             . form( { squeeze=>$args{squeeze}, trim=>1,
461             break=>$args{break},
462             fill => !(!($args{expfill}
463             || $args{impfill} &&
464             !$para->{centred}))
465             },
466             $tryformat, @hang,
467 13 50 33     163 $para->{text});
    100          
468 13   33     8323 $firsttext ||= $newtext;
469 13         61 (my $widow) = $newtext =~ /([^\n]*)$/;
470 13         34 $widow =~ s/^\s+//;
471 13   66     80 $widow_okay = $para->{empty} || length($widow) >= $args{widow};
472             } until $widow_okay || ++$rightindent > $rightslack;
473            
474 13 50       42 $text .= $widow_okay ? $newtext : $firsttext;
475             }
476 17 100       51 $gap = 0 unless $para->{empty};
477             }
478              
479              
480             # RETURN FORMATTED TEXT
481              
482 5 50       16 if ($toSTDOUT) { print STDOUT $text . $remainder; return }
  0         0  
  0         0  
483 5         125 return $text . $remainder;
484             }
485              
486             sub _build_ignore {
487 4     4   7 my $ignore_arg = shift;
488 4         10 my $ig_type = ref $ignore_arg;
489 4         5 my $ignore;
490 4 100       13 if ($ig_type eq 'Regexp') {
    50          
    0          
491 3         4 my $regex = $ignore_arg;
492 3     16   12 $ignore = sub { /$regex/ };
  16         87  
493             } elsif ($ig_type eq 'ARRAY') {
494 1         3 my @elements = map { _build_ignore($_) } @$ignore_arg;
  2         7  
495             $ignore = sub {
496 5 100   5   9 for my $sub (@elements) { return 1 if $sub->(@_) }
  9         14  
497 3         6 return 0;
498 1         6 };
499             }
500             elsif ($ignore_arg =~ /^indent/i) {
501 0 0   0   0 $ignore = sub { ignore_headers(@_) || /$ignore_indent/ };
  0         0  
502             }
503             else {
504 0         0 $ignore = $ignore_arg;
505             }
506 4 50       11 croak "Expected suboutine reference as value for -ignore option"
507             if ref $ignore ne 'CODE';
508 4         10 return $ignore;
509             }
510              
511             my $alpha = qr/[^\W\d_]/;
512             my $notalpha = qr/[\W\d_]/;
513 5     5   1770 my $word = qr/\pL(?:\pL'?)*/;
  5         50  
  5         71  
514             my $upper = qr/[^\Wa-z\d_]/;
515             my $lower = qr/[^\WA-Z\d_]/;
516             my $mixed = qr/$alpha*?(?:$lower$upper|$upper$lower)$alpha*/;
517              
518             sub recase {
519 0     0 0 0 my ($origtext, $case) = @_;
520 0         0 my ($entities, $other_entities, $first, $rest) = @{$casing{$case}};
  0         0  
521              
522 0         0 my $text = "";
523 0         0 my @pieces = split /(&[a-z]+;)/i, $origtext;
524 0 0       0 push @pieces, "" if @pieces % 2;
525 0 0       0 return $text unless @pieces;
526 0         0 local $_ = shift @pieces;
527 0 0       0 if (length $_) {
528 0         0 $entities = $other_entities;
529 0         0 &$first;
530 0         0 $text .= $_;
531             }
532 0 0       0 return $text unless @pieces;
533 0         0 $_ = shift @pieces;
534 0   0     0 $text .= $entities->{$_} || $_;
535 0         0 while (@pieces) {
536 0         0 $_ = shift @pieces; &$rest; $text .= $_;
  0         0  
  0         0  
537 0   0     0 $_ = shift @pieces; $text .= $other_entities->{$_} || $_;
  0         0  
538             }
539 0         0 return $text;
540             }
541              
542             my $alword = qr{(?:\pL|&[a-z]+;)(?:[\pL']|&[a-z]+;)*}i;
543              
544             sub entitle {
545 0     0 0 0 my ($text, $retitler_ref) = @_;
546              
547             # put into lowercase if on stop list, else titlecase
548 0 0       0 $text =~ s{($alword)}
  0         0  
549             { $retitler_ref ? $retitler_ref->($1) : recase($1,'title') }gex;
550 0 0       0  
551             if ($retitler_ref == $STD_HIGHLIGHT_IGNORES) {
552 0         0 # First and final words always capitalized...
  0         0  
553 0         0 $text =~ s/^($alword) /recase($1,'title')/ex;
  0         0  
554             $text =~ s/ ($alword)$/recase($1,'title')/ex;
555              
556 0         0 # treat parethesized portion as a complete title
  0         0  
557 0         0 $text =~ s/\( ($alword) /'('.recase($1,'title')/ex;
  0         0  
558             $text =~ s/($alword) \) /recase($1,'title').')'/ex;
559              
560 0         0 # capitalize first word following colon or semi-colon
  0         0  
561             $text =~ s/ ( [:;] \s+ ) ($alword) /$1 . recase($2,'title')/ex;
562             }
563 0         0  
564             return $text;
565             }
566              
567             my $gen_abbrev = join '|',
568             qw{ etc[.] pp[.] ph[.]?d[.] },
569             '(?:[A-Z][.])(?:[A-Z][.])+',
570             '(^[^a-zA-Z]*([a-z][.])+)';
571              
572             my $term = q{(?:[.]|[!?]+)};
573              
574             my $eos = 1;
575             my $brsent = 0;
576              
577 0 0   0 0 0 sub ensentence {
  0         0  
  0         0  
578 0         0 do { $eos = 1; return } unless @_;
579 0 0       0 my ($str, $trailer) = @_;
580 0         0 if ($str =~ /^([^a-z]*)I[^a-z]*?($term?)[^a-z]*$/i) {
581 0         0 $eos = $2;
582 0         0 $brsent = $1 =~ /^[[(]/;
583             return uc $str
584 0 0       0 }
585 0         0 unless ($str =~ /[a-z].*[A-Z]|[A-Z].*[a-z]/) {
586             $str = lc $str;
587 0 0       0 }
588 0         0 if ($eos) {
  0         0  
589 0         0 $str =~ s/([a-z])/uc $1/ie;
590             $brsent = $str =~ /^[[(]/;
591 0   0     0 }
592             $eos = $str !~ /^($gen_abbrev)[^a-z]*\s/i
593             && $str =~ /[a-z][^a-z]*$term([^a-z]*)\s/
594 0 0 0     0 && !($1=~/[])]/ && !$brsent);
595 0         0 $str =~ s/\s+$/$trailer/ if $eos && $trailer;
596             return $str;
597             }
598              
599             # blockquote($text,$para, $format, $tlen, \@hang, \%args);
600 13     13 0 34 sub blockquote {
601             my ($dummy, $para, $format, $tlen, $hang, $args) = @_;
602              
603 13 50       58 $para->{text} =~
604             / \A(\s*) # $1 - leading whitespace (quotation)
605             (["']|``) # $2 - opening quotemark
606             (.*) # $3 - quotation
607             (''|\2) # $4 closing quotemark
608             \s*?\n # trailing whitespace
609             (\1[ ]+) # $5 - leading whitespace (attribution)
610             (--|-) # $6 - attribution introducer
611             (.*?$) # $7 - attribution line 1
612             ((\5.*?$)*) # $8 - attributions lines 2-N
613             \s*\Z
614             /xsm
615             or return;
616              
617 0           #print "[$1][$2][$3][$4][$5][$6][$7]\n";
618 0           my $indent = length $1;
619 0           my $text = $2.$3.$4;
620 0           my $qindent = length $2;
621 0           my $aindent = length $5;
622 0           my $attribintro = $6;
623 0           my $attrib = $7.$8;
624             $text =~ s/\n/ /g;
625              
626             $_[0] .=
627              
628             form {squeeze=>$args->{squeeze}, trim=>1,
629             fill => $args->{expfill}
630 0           },
631             $format . q{ }x$indent . q{<}x$tlen,
632             @$hang, $text,
633             $format . q{ }x($qindent) . q{[}x($tlen-$qindent),
634             @$hang, $text,
635             {squeeze=>0},
636             $format . q{ } x $aindent . q{>> } . q{[}x($tlen-$aindent-3),
637 0           @$hang, $attribintro, $attrib;
638             return 1;
639             }
640              
641             1;
642              
643             __END__