File Coverage

blib/lib/Text/Autoformat.pm
Criterion Covered Total %
statement 192 323 59.4
branch 88 198 44.4
condition 37 95 38.9
subroutine 19 23 82.6
pod 0 7 0.0
total 336 646 52.0


line stmt bran cond sub pod time code
1             package Text::Autoformat;
2             $Text::Autoformat::VERSION = '1.74';
3 4     4   68513 use 5.006;
  4         12  
4 4     4   18 use strict;
  4         8  
  4         93  
5 4     4   16 use warnings;
  4         17  
  4         104  
6 4     4   21 use Carp;
  4         5  
  4         390  
7              
8             require Exporter;
9              
10 4     4   4000 use Text::Reform qw( form tag break_at break_with break_wrap break_TeX );
  4         29993  
  4         28  
11 4     4   3226 use Text::Autoformat::Hang;
  4         11  
  4         138  
12 4     4   2331 use Text::Autoformat::NullHang;
  4         10  
  4         2668  
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 130 50   130 0 548 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 40 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 4     4   6741 use overload;
  4         4705  
  4         28  
107             sub autoformat # ($text, %args)
108             {
109 4     4 0 988 my ($text,%args,$toSTDOUT);
110              
111 4         13 foreach ( @_ )
112             {
113 7 100 33     52 if (ref eq 'HASH')
    50 33        
114 3         19 { %args = (%args, %$_) }
115             elsif (!defined($text) && !ref || overload::Method($_,'""'))
116 4         11 { $text = "$_" }
117             else {
118 0         0 croak q{Usage: autoformat([text],[{options}])}
119             }
120             }
121              
122 4 50       18 unless (defined $text) {
123 0         0 $text = join("",);
124 0         0 $toSTDOUT = !defined wantarray();
125             }
126              
127 4 100       23 return $text unless $text =~ /\S/;
128              
129 3 100       12 $args{right} = $default_margin unless exists $args{right};
130 3 50       13 $args{justify} = "" unless exists $args{justify};
131 3 50 50     20 $args{widow} = 0 if ($args{justify}||"") =~ /full/;
132 3 50       11 $args{widow} = $default_widow unless exists $args{widow};
133 3 50       11 $args{case} = '' unless exists $args{case};
134 3 100       10 $args{lists} = 1 unless exists $args{lists};
135 3 50       12 $args{squeeze} = 1 unless exists $args{squeeze};
136 3 50       11 $args{gap} = 0 unless exists $args{gap};
137 3 50       20 $args{break} = break_at('-') unless exists $args{break};
138 3         43 $args{impfill} = ! exists $args{fill};
139 3         8 $args{expfill} = $args{fill};
140 3 50       13 $args{tabspace} = 8 unless exists $args{tabspace};
141 3 50       9 $args{renumber} = 1 unless exists $args{renumber};
142 3 50       10 $args{autocentre} = 1 unless exists $args{autocentre};
143 3 50       10 $args{_centred} = 1 if $args{justify} =~ /cent(er(ed)?|red?)/;
144 3   100     21 $args{all} ||= $args{mail};
145              
146             # SPECIAL IGNORANCE...
147 3 100       9 if ($args{ignore}) {
148 2         4 $args{all} = 1;
149 2         6 $args{ignore} = _build_ignore( $args{ignore} );
150             }
151             else {
152 1     1   5 $args{ignore} = sub{0};
  1         3  
153             }
154              
155 3 100       13 if ( $args{mail} ) {
156 1         2 my $ignore = $args{ignore};
157 1 100   7   4 $args{ignore} = sub { $ignore->(@_) || ignore_headers(@_) };
  7         16  
158             }
159            
160             # DETABIFY
161 3         27 my @rawlines = split /\n/, $text;
162 4     4   5693 use Text::Tabs; $tabstop = $args{tabspace};
  4         3102  
  4         18173  
  3         7  
163 3         15 @rawlines = expand(@rawlines);
164              
165             # HANDLE QUOTING CHANGE
166 3 50       199 my $quoter = exists $args{quoter} ? $args{quoter} : $QUOTER;
167 3         9 $quoter =~ s//$QUOTER/g;
168              
169             # PARSE EACH LINE
170              
171 3         5 my $pre = 0;
172 3         5 my @lines;
173 3         8 foreach (@rawlines)
174             {
175 26         73 push @lines, { raw => $_ };
176 26 50       431 s/\A([ \t]*)($quoter?)([ \t]*)//
177             or die "Internal Error ($@) on '$_'";
178 26         67 $lines[-1]{presig} = $lines[-1]{prespace} = defn $1;
179 26         61 $lines[-1]{presig} .= $lines[-1]{quoter} = defn $2;
180 26         53 $lines[-1]{presig} .= $lines[-1]{quotespace} = defn $3;
181              
182 26         102 $lines[-1]{hang} = Text::Autoformat::Hang->new($_, $args{lists});
183              
184 26 50       247 s/([ \t]*)(.*?)(\s*)$//
185             or die "Internal Error ($@) on '$_'";
186 26         84 $lines[-1]{hangspace} = defn $1;
187 26         52 $lines[-1]{text} = defn $2;
188 26   66     104 $lines[-1]{empty} = $lines[-1]{hang}->empty() && $2 !~ /\S/;
189 26         189 $lines[-1]{separator} = $lines[-1]{text} =~ /^$separator$/;
190             }
191              
192             # SUBDIVIDE DOCUMENT INTO COHERENT SUBSECTIONS
193              
194 3         6 my @chunks;
195 3         9 push @chunks, [shift @lines];
196 3         8 foreach my $line (@lines)
197             {
198 23 100 33     204 if ($line->{separator} ||
      33        
      33        
      66        
199             $line->{quoter} ne $chunks[-1][-1]->{quoter} ||
200             $line->{empty} ||
201             @chunks && $chunks[-1][-1]->{empty})
202             {
203 10         24 push @chunks, [$line];
204             }
205             else
206             {
207 13         17 push @{$chunks[-1]}, $line;
  13         34  
208             }
209             }
210              
211              
212              
213             # DETECT CENTRED PARAS
214              
215 3         7 CHUNK: foreach my $chunk ( @chunks )
216             {
217 13 50 66     90 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 3         6 my @paras;
249 3         5 foreach my $chunk ( @chunks )
250             {
251 13         17 my $first = 1;
252 13         16 my $firstfrom;
253 13         18 foreach my $line ( @{$chunk} )
  13         21  
254             {
255 26 100 66     145 if ($first ||
      66        
      33        
256             $line->{quoter} ne $paras[-1]->{quoter} ||
257             $paras[-1]->{separator} ||
258             !$line->{hang}->empty
259             )
260             {
261 13         22 push @paras, $line;
262 13         16 $first = 0;
263 13         27 $firstfrom = length($line->{raw}) - length($line->{text});
264             }
265             else
266             {
267 13         30 my $extraspace = length($line->{raw}) - length($line->{text}) - $firstfrom;
268 13 100       30 $extraspace = 0 if $extraspace < 0;
269 13         42 $paras[-1]->{text} .= "\n" . q{ }x$extraspace . $line->{text};
270 13         42 $paras[-1]->{raw} .= "\n" . $line->{raw};
271             }
272             }
273             }
274              
275             # SELECT PARAS TO HANDLE
276              
277 3         6 my $remainder = "";
278 3 50       9 if ($args{all}) { # STOP AT MAIL TERMINATOR IF $args{mail}
279 3         5 my $lastignored = 1;
280 3         18 for my $index (0..$#paras) {
281 13         31 local $_ = $paras[$index]{raw} . "\n";
282 13         31 $paras[$index]{ignore} = $args{ignore}($lastignored);
283 13   100     46 $lastignored &&= $paras[$index]{ignore};
284 13 50 66     59 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 3 50       11 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 3         6 my %sigs;
329 3         4 my $lastquoted = 0;
330 3         4 my $lastprespace = 0;
331 3         9 for my $i ( 0..$#paras )
332             {
333 13         19 my $para = $paras[$i];
334 13 100       33 next if $para->{ignore};
335              
336 9 50       20 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 9         14 $lastquoted = 0;
344             }
345             }
346              
347             # RENUMBER PARAGRAPHS
348              
349 3         7 for my $para ( @paras ) {
350 13 100       32 next if $para->{ignore};
351 9         36 my $sig = $para->{presig} . $para->{hang}->signature();
352 9         11 push @{$sigs{$sig}{hangref}}, $para;
  9         25  
353             $sigs{$sig}{hangfields} = $para->{hang}->fields()-1
354 9 100       38 unless defined $sigs{$sig}{hangfields};
355             }
356              
357 3         16 while (my ($sig,$val) = each %sigs) {
358 3 50       17 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 3         5 my %prev;
387              
388 3         6 for my $para ( @paras ) {
389 13 100       34 next if $para->{ignore};
390 9         27 my $sig = $para->{presig} . $para->{hang}->signature();
391 9 50       26 if ($args{renumber}) {
392 9 50       24 unless ($para->{quoter}) {
393 9         48 $para->{hang}->incr($prev{""}, $prev{$sig});
394             $prev{""} = $prev{$sig} = $para->{hang}
395 9 50       32 unless $para->{hang}->empty;
396             }
397             }
398            
399             # COLLECT MAXIMAL HANG LENGTHS BY SIGNATURE
400              
401 9         28 my $siglen = $para->{hang}->length();
402             $sigs{$sig}{hanglen} = $siglen
403             if ! $sigs{$sig}{hanglen} ||
404 9 50 33     37 $sigs{$sig}{hanglen} < $siglen;
405             }
406              
407             # PROPAGATE MAXIMAL HANG LENGTH
408              
409 3         14 while (my ($sig,$val) = each %sigs)
410             {
411 3         5 foreach (@{$val->{hangref}}) {
  3         7  
412 9         26 $_->{hanglen} = $val->{hanglen};
413             }
414             }
415              
416             # BUILD FORMAT FOR EACH PARA THEN FILL IT
417              
418 3         6 $text = "";
419 3 50 33     20 my $gap = @paras && $paras[0]->{empty} ? 0 : $args{gap};
420 3         7 for my $para ( @paras )
421             {
422 13 100       28 if ($para->{empty}) {
423 5         13 $gap += 1 + ($para->{text} =~ tr/\n/\n/);
424             }
425 13 100       29 if ($para->{ignore}) {
426 4 50       19 $text .= (!$para->{empty} ? "\n"x($args{gap}-$gap > 0 ? ($args{gap}-$gap) : 0) : "") ;
    50          
427 4         8 $text .= $para->{raw};
428 4 50       14 $text .= "\n" unless $para->{raw} =~ /\n\z/;
429             }
430             else {
431             my $leftmargin = $args{left} ? " "x($args{left}-1)
432 9 50       25 : $para->{prespace};
433 9   33     43 my $hlen = $para->{hanglen} || $para->{hang}->length;
434 9 50       25 my $hfield = ($hlen==1 ? '~' : '>'x$hlen);
435 9         14 my @hang;
436 9 50       19 push @hang, $para->{hang}->stringify if $hlen;
437             my $format = $leftmargin
438             . quotemeta($para->{quoter})
439             . $para->{quotespace}
440             . $hfield
441 9         26 . $para->{hangspace};
442 9         25 my $rightslack = int (($args{right}-length $leftmargin)*$Text::Autoformat::widow_slack);
443 9         17 my ($widow_okay, $rightindent, $firsttext, $newtext) = (0,0);
444 9   33     11 do {
445             my $tlen = $args{right}-$rightindent-length($leftmargin
446             . $para->{quoter}
447             . $para->{quotespace}
448             . $hfield
449 9         25 . $para->{hangspace});
450 9 50       30 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 9 50 33     124 : $para->{centred}||$args{_centred} ? '|'x$tlen
    50 33        
    50          
    50          
    50          
456             : '['x$tlen
457             );
458 9         20 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 9 50 33     115 $para->{text});
    100          
468 9   33     4978 $firsttext ||= $newtext;
469 9         40 (my $widow) = $newtext =~ /([^\n]*)$/;
470 9         21 $widow =~ s/^\s+//;
471 9   66     55 $widow_okay = $para->{empty} || length($widow) >= $args{widow};
472             } until $widow_okay || ++$rightindent > $rightslack;
473            
474 9 50       34 $text .= $widow_okay ? $newtext : $firsttext;
475             }
476 13 100       39 $gap = 0 unless $para->{empty};
477             }
478              
479              
480             # RETURN FORMATTED TEXT
481              
482 3 50       9 if ($toSTDOUT) { print STDOUT $text . $remainder; return }
  0         0  
  0         0  
483 3         96 return $text . $remainder;
484             }
485              
486             sub _build_ignore {
487 4     4   6 my $ignore_arg = shift;
488 4         7 my $ig_type = ref $ignore_arg;
489 4         5 my $ignore;
490 4 100       13 if ($ig_type eq 'Regexp') {
    50          
    0          
491 3         5 my $regex = $ignore_arg;
492 3     16   13 $ignore = sub { /$regex/ };
  16         82  
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   7 for my $sub (@elements) { return 1 if $sub->(@_) }
  9         16  
497 3         8 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       10 croak "Expected suboutine reference as value for -ignore option"
507             if ref $ignore ne 'CODE';
508 4         12 return $ignore;
509             }
510              
511             my $alpha = qr/[^\W\d_]/;
512             my $notalpha = qr/[\W\d_]/;
513 4     4   4301 my $word = qr/\pL(?:\pL'?)*/;
  4         36  
  4         58  
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 $text =~ s{($alword)}
549 0 0       0 { $retitler_ref ? $retitler_ref->($1) : recase($1,'title') }gex;
550              
551 0 0       0 if ($retitler_ref == $STD_HIGHLIGHT_IGNORES) {
552             # First and final words always capitalized...
553 0         0 $text =~ s/^($alword) /recase($1,'title')/ex;
  0         0  
554 0         0 $text =~ s/ ($alword)$/recase($1,'title')/ex;
  0         0  
555              
556             # treat parethesized portion as a complete title
557 0         0 $text =~ s/\( ($alword) /'('.recase($1,'title')/ex;
  0         0  
558 0         0 $text =~ s/($alword) \) /recase($1,'title').')'/ex;
  0         0  
559              
560             # capitalize first word following colon or semi-colon
561 0         0 $text =~ s/ ( [:;] \s+ ) ($alword) /$1 . recase($2,'title')/ex;
  0         0  
562             }
563              
564 0         0 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             sub ensentence {
578 0 0   0 0 0 do { $eos = 1; return } unless @_;
  0         0  
  0         0  
579 0         0 my ($str, $trailer) = @_;
580 0 0       0 if ($str =~ /^([^a-z]*)I[^a-z]*?($term?)[^a-z]*$/i) {
581 0         0 $eos = $2;
582 0         0 $brsent = $1 =~ /^[[(]/;
583 0         0 return uc $str
584             }
585 0 0       0 unless ($str =~ /[a-z].*[A-Z]|[A-Z].*[a-z]/) {
586 0         0 $str = lc $str;
587             }
588 0 0       0 if ($eos) {
589 0         0 $str =~ s/([a-z])/uc $1/ie;
  0         0  
590 0         0 $brsent = $str =~ /^[[(]/;
591             }
592 0   0     0 $eos = $str !~ /^($gen_abbrev)[^a-z]*\s/i
593             && $str =~ /[a-z][^a-z]*$term([^a-z]*)\s/
594             && !($1=~/[])]/ && !$brsent);
595 0 0 0     0 $str =~ s/\s+$/$trailer/ if $eos && $trailer;
596 0         0 return $str;
597             }
598              
599             # blockquote($text,$para, $format, $tlen, \@hang, \%args);
600             sub blockquote {
601 9     9 0 19 my ($dummy, $para, $format, $tlen, $hang, $args) = @_;
602              
603             $para->{text} =~
604 9 50       45 / \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             #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 0           $text =~ s/\n/ /g;
625              
626             $_[0] .=
627              
628             form {squeeze=>$args->{squeeze}, trim=>1,
629             fill => $args->{expfill}
630             },
631 0           $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             @$hang, $attribintro, $attrib;
638 0           return 1;
639             }
640              
641             1;
642              
643             __END__