File Coverage

blib/lib/Text/LineFold.pm
Criterion Covered Total %
statement 146 180 81.1
branch 70 122 57.3
condition 17 41 41.4
subroutine 13 13 100.0
pod 4 4 100.0
total 250 360 69.4


line stmt bran cond sub pod time code
1             #-*- perl -*-
2              
3             package Text::LineFold;
4             require 5.008;
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             Text::LineFold - Line Folding for Plain Text
11              
12             =head1 SYNOPSIS
13              
14             use Text::LineFold;
15             $lf = Text::LineFold->new();
16            
17             # Fold lines
18             $folded = $lf->fold($string, 'PLAIN');
19             $indented = $lf->fold(' ' x 8, ' ' x 4, $string);
20              
21             # Unfold lines
22             $unfolded = $lf->unfold($string, 'FIXED');
23              
24             =head1 DESCRIPTION
25              
26             Text::LineFold folds or unfolds lines of plain text.
27             As it mainly focuses on plain text e-mail messages,
28             RFC 3676 flowed format is also supported.
29              
30             =cut
31              
32             ### Pragmas:
33 3     3   2833 use strict;
  3         7  
  3         101  
34 3     3   18 use vars qw($VERSION @EXPORT_OK @ISA $Config);
  3         6  
  3         186  
35              
36             ### Exporting:
37 3     3   17 use Exporter;
  3         6  
  3         157  
38              
39             ### Inheritance:
40             our @ISA = qw(Exporter Unicode::LineBreak);
41              
42             ### Other modules:
43 3     3   18 use Carp qw(croak carp);
  3         5  
  3         163  
44 3     3   1624 use Encode qw(is_utf8);
  3         31002  
  3         220  
45 3     3   1544 use MIME::Charset;
  3         16072  
  3         243  
46 3     3   1478 use Unicode::LineBreak qw(:all);
  3         9  
  3         9874  
47              
48             ### Globals
49              
50             ### The package Version
51             our $VERSION = '2018.012';
52              
53             ### Public Configuration Attributes
54             our $Config = {
55             ### %{$Unicode::LineBreak::Config},
56             Charset => 'UTF-8',
57             Language => 'XX',
58             OutputCharset => undef,
59             TabSize => 8,
60             };
61              
62             ### Privates
63              
64             my %FORMAT_FUNCS = (
65             'FIXED' => sub {
66             my $self = shift;
67             my $action = shift;
68             my $str = shift;
69             if ($action =~ /^so[tp]/) {
70             $self->{_} = {};
71             $self->{_}->{'ColMax'} = $self->config('ColMax');
72             $self->config('ColMax' => 0) if $str =~ /^>/;
73             } elsif ($action eq "") {
74             $self->{_}->{line} = $str;
75             } elsif ($action eq "eol") {
76             return $self->config('Newline');
77             } elsif ($action =~ /^eo/) {
78             if (length $self->{_}->{line} and $self->config('ColMax')) {
79             $str = $self->config('Newline').$self->config('Newline');
80             } else {
81             $str = $self->config('Newline');
82             }
83             $self->config('ColMax' => $self->{_}->{'ColMax'});
84             delete $self->{_};
85             return $str;
86             }
87             undef;
88             },
89             'FLOWED' => sub { # RFC 3676
90             my $self = shift;
91             my $action = shift;
92             my $str = shift;
93             if ($action eq 'sol') {
94             if ($self->{_}->{prefix}) {
95             return $self->{_}->{prefix}.' '.$str;
96             }
97             } elsif ($action =~ /^so/) {
98             $self->{_} = {};
99             if ($str =~ /^(>+)/) {
100             $self->{_}->{prefix} = $1;
101             } else {
102             $self->{_}->{prefix} = '';
103             }
104             } elsif ($action eq "") {
105             if ($str =~ /^(?: |From )/
106             or $str =~ /^>/ and !length $self->{_}->{prefix}) {
107             return $self->{_}->{line} = ' ' . $str;
108             }
109             $self->{_}->{line} = $str;
110             } elsif ($action eq 'eol') {
111             $str = ' ' if length $str;
112             return $str.' '.$self->config('Newline');
113             } elsif ($action =~ /^eo/) {
114             if (length $self->{_}->{line} and !length $self->{_}->{prefix}) {
115             $str = ' '.$self->config('Newline').$self->config('Newline');
116             } else {
117             $str = $self->config('Newline');
118             }
119             delete $self->{_};
120             return $str;
121             }
122             undef;
123             },
124             'PLAIN' => sub {
125             return $_[0]->config('Newline') if $_[1] =~ /^eo/;
126             undef;
127             },
128             );
129              
130             =head2 Public Interface
131              
132             =over 4
133              
134             =item new ([KEY => VALUE, ...])
135              
136             I.
137             About KEY => VALUE pairs see config method.
138              
139             =back
140              
141             =cut
142              
143             sub new {
144 8     8 1 6571 my $class = shift;
145 8         58 my $self = bless __PACKAGE__->SUPER::new(), $class;
146 8         28 $self->config(@_);
147 8         22 $self;
148             }
149              
150             =over 4
151              
152             =item $self->config (KEY)
153              
154             =item $self->config ([KEY => VAL, ...])
155              
156             I.
157             Get or update configuration. Following KEY => VALUE pairs may be specified.
158              
159             =over 4
160              
161             =item Charset => CHARSET
162              
163             Character set that is used to encode string.
164             It may be string or L object.
165             Default is C<"UTF-8">.
166              
167             =item Language => LANGUAGE
168              
169             Along with Charset option, this may be used to define language/region
170             context.
171             Default is C<"XX">.
172             See also L option.
173              
174             =item Newline => STRING
175              
176             String to be used for newline sequence.
177             Default is C<"\n">.
178              
179             =item OutputCharset => CHARSET
180              
181             Character set that is used to encode result of fold()/unfold().
182             It may be string or L object.
183             If a special value C<"_UNICODE_"> is specified, result will be Unicode string.
184             Default is the value of Charset option.
185              
186             =item TabSize => NUMBER
187              
188             Column width of tab stops.
189             When 0 is specified, tab stops are ignored.
190             Default is 8.
191              
192             =item BreakIndent
193              
194             =item CharMax
195              
196             =item ColMax
197              
198             =item ColMin
199              
200             =item ComplexBreaking
201              
202             =item EAWidth
203              
204             =item HangulAsAL
205              
206             =item LBClass
207              
208             =item LegacyCM
209              
210             =item Prep
211              
212             =item Urgent
213              
214             See L.
215              
216             =back
217              
218             =back
219              
220             =cut
221              
222             sub config {
223 86     86 1 126 my $self = shift;
224 86         214 my @opts = qw{Charset Language OutputCharset TabSize};
225 86         149 my %opts = map { (uc $_ => $_) } @opts;
  344         823  
226 86         173 my $newline = undef;
227              
228             # Get config.
229 86 100       180 if (scalar @_ == 1) {
230 78 50       160 if ($opts{uc $_[0]}) {
231 0         0 return $self->{$opts{uc $_[0]}};
232             }
233 78         189 return $self->SUPER::config($_[0]);
234             }
235              
236             # Set config.
237 8         16 my @o = ();
238 8         24 while (scalar @_) {
239 4         7 my $k = shift;
240 4         6 my $v = shift;
241 4 50       14 if ($opts{uc $k}) {
    50          
242 0         0 $self->{$opts{uc $k}} = $v;
243             } elsif (uc $k eq uc 'Newline') {
244 0         0 $newline = $v;
245             } else {
246 4         13 push @o, $k => $v;
247             }
248             }
249 8 100       23 $self->SUPER::config(@o) if scalar @o;
250              
251             # Character set and language assumed.
252 8 50       182 if (ref $self->{Charset} eq 'MIME::Charset') {
253 0         0 $self->{_charset} = $self->{Charset};
254             } else {
255 8   33     71 $self->{Charset} ||= $Config->{Charset};
256 8         51 $self->{_charset} = MIME::Charset->new($self->{Charset});
257             }
258 8         1405 $self->{Charset} = $self->{_charset}->as_string;
259 8   33     97 my $ocharset = uc($self->{OutputCharset} || $self->{Charset});
260 8 50 33     51 $ocharset = MIME::Charset->new($ocharset)
261             unless ref $ocharset eq 'MIME::Charset' or $ocharset eq '_UNICODE_';
262 8 50       584 unless ($ocharset eq '_UNICODE_') {
263 8         48 $self->{_charset}->encoder($ocharset);
264 8         176 $self->{OutputCharset} = $ocharset->as_string;
265             }
266 8   33     114 $self->{Language} = uc($self->{Language} || $Config->{Language});
267              
268             ## Context
269             $self->SUPER::config(Context =>
270             context(Charset => $self->{Charset},
271 8         55 Language => $self->{Language}));
272              
273             ## Set sizing method.
274             $self->SUPER::config(Sizing => sub {
275 2328     2328   5694 my ($self, $cols, $pre, $spc, $str) = @_;
276              
277 2328         6098 my $tabsize = $self->{TabSize};
278 2328         8030 my $spcstr = $spc.$str;
279 2328         6484 $spcstr->pos(0);
280 2328   100     55561 while (!$spcstr->eos and $spcstr->item->lbc == LB_SP) {
281 1016         4523 my $c = $spcstr->next;
282 1016 100       4670 if ($c eq "\t") {
283 10 50       272 $cols += $tabsize - $cols % $tabsize if $tabsize;
284             } else {
285 1006         25929 $cols += $c->columns;
286             }
287             }
288 2328         34125 return $cols + $spcstr->substr($spcstr->pos)->columns;
289 8         66 });
290              
291             ## Classify horizontal tab as line breaking class SP.
292 8         244 $self->SUPER::config(LBClass => [ord("\t") => LB_SP]);
293             ## Tab size
294 8 50       41 if (defined $self->{TabSize}) {
295 0 0       0 croak "Invalid TabSize option" unless $self->{TabSize} =~ /^\d+$/;
296 0         0 $self->{TabSize} += 0;
297             } else {
298 8         28 $self->{TabSize} = $Config->{TabSize};
299             }
300              
301             ## Newline
302 8 50       53 if (defined $newline) {
303 0 0       0 $newline = $self->{_charset}->decode($newline)
304             unless is_utf8($newline);
305 0         0 $self->SUPER::config(Newline => $newline);
306             }
307             }
308              
309             =over 4
310              
311             =item $self->fold (STRING, [METHOD])
312              
313             =item $self->fold (INITIAL_TAB, SUBSEQUENT_TAB, STRING, ...)
314              
315             I.
316             fold() folds lines of string STRING and returns it.
317             Surplus SPACEs and horizontal tabs at end of line are removed,
318             newline sequences are replaced by that specified by Newline option
319             and newline is appended at end of text if it does not exist.
320             Horizontal tabs are treated as tab stops according to TabSize option.
321              
322             By the first style, following options may be specified for METHOD argument.
323              
324             =over 4
325              
326             =item C<"FIXED">
327              
328             Lines preceded by C<"E"> won't be folded.
329             Paragraphs are separated by empty line.
330              
331             =item C<"FLOWED">
332              
333             C<"Format=Flowed; DelSp=Yes"> formatting defined by RFC 3676.
334              
335             =item C<"PLAIN">
336              
337             Default method. All lines are folded.
338              
339             =back
340              
341             Second style is similar to L.
342             All lines are folded.
343             INITIAL_TAB is inserted at beginning of paragraphs and SUBSEQUENT_TAB
344             at beginning of other broken lines.
345              
346             =back
347              
348             =cut
349              
350             # Special breaking characters: VT, FF, NEL, LS, PS
351             my $special_break = qr/([\x{000B}\x{000C}\x{0085}\x{2028}\x{2029}])/os;
352              
353             sub fold {
354 13     13 1 5358 my $self = shift;
355 13         24 my $str;
356              
357 13 100       41 if (2 < scalar @_) {
358 2   50     6 my $initial_tab = shift || '';
359 2 50       16 $initial_tab = $self->{_charset}->decode($initial_tab)
360             unless is_utf8($initial_tab);
361 2   50     36 my $subsequent_tab = shift || '';
362 2 50       10 $subsequent_tab = $self->{_charset}->decode($subsequent_tab)
363             unless is_utf8($subsequent_tab);
364 2         22 my @str = @_;
365              
366             ## Decode and concat strings.
367 2         13 $str = shift @str;
368 2 50       12 $str = $self->{_charset}->decode($str) unless is_utf8($str);
369 2         30 foreach my $s (@str) {
370 0 0 0     0 next unless defined $s and length $s;
371              
372 0 0       0 $s = $self->{_charset}->decode($s) unless is_utf8($s);
373 0 0       0 unless (length $str) {
    0          
374 0         0 $str = $s;
375 0 0       0 } elsif ($str =~ /(\s|$special_break)$/ or
376             $s =~ /^(\s|$special_break)/) {
377 0         0 $str .= $s;
378             } else {
379 0 0       0 $str .= ' ' if $self->breakingRule($str, $s) == INDIRECT;
380 0         0 $str .= $s;
381             }
382             }
383              
384             ## Set format method.
385             $self->SUPER::config(Format => sub {
386 96     96   185 my $self = shift;
387 96         138 my $event = shift;
388 96         208 my $str = shift;
389 96 100       242 if ($event =~ /^eo/) { return $self->config('Newline'); }
  32         96  
390 64 100       143 if ($event =~ /^so[tp]/) { return $initial_tab.$str; }
  14         452  
391 50 100       100 if ($event eq 'sol') { return $subsequent_tab.$str; }
  18         212  
392 32         181 undef;
393 2         11 });
394             } else {
395 11         27 $str = shift;
396 11   50     35 my $method = uc(shift || '');
397 11 50 33     53 return '' unless defined $str and length $str;
398              
399             ## Decode string.
400 11 50       90 $str = $self->{_charset}->decode($str) unless is_utf8($str);
401              
402             ## Set format method.
403             $self->SUPER::config(Format => $FORMAT_FUNCS{$method} ||
404 11   33     293 $FORMAT_FUNCS{'PLAIN'});
405             }
406              
407             ## Do folding.
408 13         30 my $result = '';
409 13         339 foreach my $s (split $special_break, $str) {
410 13 50       216 if ($s =~ $special_break) {
411 0         0 $result .= $s;
412             } else {
413 13         1158 $result .= $self->break($str);
414             }
415             }
416              
417             ## Encode result.
418 13 50       119 if ($self->{OutputCharset} eq '_UNICODE_') {
419 0         0 return $result;
420             } else {
421 13         90 return $self->{_charset}->encode($result);
422             }
423             }
424              
425             =over 4
426              
427             =item $self->unfold (STRING, METHOD)
428              
429             Conjunct folded paragraphs of string STRING and returns it.
430              
431             Following options may be specified for METHOD argument.
432              
433             =over 4
434              
435             =item C<"FIXED">
436              
437             Default method.
438             Lines preceded by C<"E"> won't be conjuncted.
439             Treat empty line as paragraph separator.
440              
441             =item C<"FLOWED">
442              
443             Unfold C<"Format=Flowed; DelSp=Yes"> formatting defined by RFC 3676.
444              
445             =item C<"FLOWEDSP">
446              
447             Unfold C<"Format=Flowed; DelSp=No"> formatting defined by RFC 3676.
448              
449             =begin comment
450              
451             =item C<"OBSFLOWED">
452              
453             Unfold C<"Format=Flowed> formatting defined by (obsoleted) RFC 2646
454             as well as possible.
455              
456             =end comment
457              
458             =back
459              
460             =back
461              
462             =cut
463              
464             sub unfold {
465 8     8 1 5520 my $self = shift;
466 8         17 my $str = shift;
467 8 50 33     46 return '' unless defined $str and length $str;
468              
469             ## Get format method.
470 8   50     26 my $method = uc(shift || 'FIXED');
471 8 50       53 $method = 'FIXED' unless $method =~ /^(?:FIXED|FLOWED|FLOWEDSP|OBSFLOWED)$/;
472 8         18 my $delsp = $method eq 'FLOWED';
473              
474             ## Decode string and canonizalize newline.
475 8 50       82 $str = $self->{_charset}->decode($str) unless is_utf8($str);
476 8         213 $str =~ s/\r\n|\r/\n/g;
477              
478             ## Do unfolding.
479 8         16 my $result = '';
480 8         222 foreach my $s (split $special_break, $str) {
481 8 50 66     169 if ($s eq '') {
    50 33        
    100          
    50          
482 0         0 next;
483             } elsif ($s =~ $special_break) {
484 0         0 $result .= $s;
485 0         0 next;
486             } elsif ($method eq 'FIXED') {
487 3         24 pos($s) = 0;
488 3         17 while ($s !~ /\G\z/cg) {
489 48 100       549 if ($s =~ /\G\n/cg) {
    100          
    100          
    50          
    50          
    0          
    0          
490 6         13 $result .= $self->config('Newline');
491             } elsif ($s =~ /\G(.+)\n\n/cg) {
492 15         55 $result .= $1.$self->config('Newline');
493             } elsif ($s =~ /\G(>.*)\n/cg) {
494 9         26 $result .= $1.$self->config('Newline');
495             } elsif ($s =~ /\G(.+)\n(?=>)/cg) {
496 0         0 $result .= $1.$self->config('Newline');
497             } elsif ($s =~ /\G(.+?)( *)\n(?=(.+))/cg) {
498 18         68 my ($l, $s, $n) = ($1, $2, $3);
499 18         34 $result .= $l;
500 18 50       70 if ($n =~ /^ /) {
    50          
    50          
501 0         0 $result .= $self->config('Newline');
502             } elsif (length $s) {
503 0         0 $result .= $s;
504             } elsif (length $l) {
505 18 100       387 $result .= ' '
506             if $self->breakingRule($l, $n) == INDIRECT;
507             }
508             } elsif ($s =~ /\G(.+)\n/cg) {
509 0         0 $result .= $1.$self->config('Newline');
510             } elsif ($s =~ /\G(.+)/cg) {
511 0         0 $result .= $1.$self->config('Newline');
512 0         0 last;
513             }
514             }
515             } elsif ($method eq 'FLOWED' or $method eq 'FLOWEDSP' or
516             $method eq 'OBSFLOWED') {
517 5         11 my $prefix = undef;
518 5         37 pos($s) = 0;
519 5         23 while ($s !~ /\G\z/cg) {
520 96 100       708 if ($s =~ /\G(>+) ?(.*?)( ?)\n/cg) {
    50          
    0          
521 26         80 my ($p, $l, $s) = ($1, $2, $3);
522 26 100       56 unless (defined $prefix) {
    100          
523 17         40 $result .= $p.' '.$l;
524 0         0 } elsif ($p ne $prefix) {
525 1         5 $result .= $self->config('Newline');
526 1         13 $result .= $p.' '.$l;
527             } else {
528 8         16 $result .= $l;
529             }
530 26 100       66 unless (length $s) {
531 17         35 $result .= $self->config('Newline');
532 17         61 $prefix = undef;
533             } else {
534 9         17 $prefix = $p;
535 9 100       33 $result .= $s unless $delsp;
536             }
537             } elsif ($s =~ /\G ?(.*?)( ?)\n/cg) {
538 70         181 my ($l, $s) = ($1, $2);
539 70 50       134 unless (defined $prefix) {
    100          
540 30         59 $result .= $l;
541 0         0 } elsif ('' ne $prefix) {
542 0         0 $result .= $self->config('Newline');
543 0         0 $result .= $l;
544             } else {
545 40         62 $result .= $l;
546             }
547 70 100       134 unless (length $s) {
548 30         61 $result .= $self->config('Newline');
549 30         108 $prefix = undef;
550             } else {
551 40 100       72 $result .= $s unless $delsp;
552 40         110 $prefix = '';
553             }
554             } elsif ($s =~ /\G ?(.*)/cg) {
555 0         0 $result .= $1.$self->config('Newline');
556 0         0 last;
557             }
558             }
559             }
560             }
561             ## Encode result.
562 8 50       41 if ($self->{OutputCharset} eq '_UNICODE_') {
563 0         0 return $result;
564             } else {
565 8         38 return $self->{_charset}->encode($result);
566             }
567             }
568              
569             =head1 BUGS
570              
571             Please report bugs or buggy behaviors to developer.
572              
573             CPAN Request Tracker:
574             L.
575              
576             =head1 VERSION
577              
578             Consult $VERSION variable.
579              
580             =head1 SEE ALSO
581              
582             L, L.
583              
584             =head1 AUTHOR
585              
586             Copyright (C) 2009-2012 Hatuka*nezumi - IKEDA Soji .
587              
588             This program is free software; you can redistribute it and/or modify it
589             under the same terms as Perl itself.
590              
591             =cut
592              
593             1;