File Coverage

blib/lib/Text/Amuse/InlineElement.pm
Criterion Covered Total %
statement 169 181 93.3
branch 83 100 83.0
condition 5 6 83.3
subroutine 22 23 95.6
pod 15 15 100.0
total 294 325 90.4


line stmt bran cond sub pod time code
1             package Text::Amuse::InlineElement;
2 43     43   242 use strict;
  43         67  
  43         968  
3 43     43   157 use warnings;
  43         88  
  43         745  
4 43     43   152 use utf8;
  43         60  
  43         155  
5 43     43   1048 use Text::Amuse::Utils;
  43         66  
  43         86611  
6              
7             =head1 NAME
8              
9             Text::Amuse::InlineElement - Helper for Text::Amuse
10              
11             =head1 METHODS
12              
13             Everything here is pretty much internal only, underdocumented and
14             subject to change.
15              
16             =head2 new(%args)
17              
18             Constructor. Accepts the following named arguments (which are also
19             accessors)
20              
21             =over 4
22              
23             =item type
24              
25             The element type
26              
27             =item string
28              
29             The raw string
30              
31             =item last_position
32              
33             The offset of the last character in the parsed string
34              
35             =item tag
36              
37             The name of the tag
38              
39             =item fmt
40              
41             C or C
42              
43             =cut
44              
45             sub new {
46 36801     36801 1 94277 my ($class, %args) = @_;
47 36801         89157 my $self = {
48             type => '',
49             string => '',
50             last_position => 0,
51             tag => '',
52             fmt => '',
53             lang => 'en',
54             };
55 36801         88380 foreach my $k (keys %$self) {
56 220806 100       281739 if (defined $args{$k}) {
57 182846         197134 $self->{$k} = $args{$k};
58             }
59 220806         238262 delete $args{$k};
60             }
61 36801 50       55934 die "Extra arguments passed :" . join(" ", %args) if %args;
62 36801 50       50229 die "Missing type for <$self->{string}>" unless $self->{type};
63 36801 50 66     76192 unless ($self->{fmt} eq 'ltx' or $self->{fmt} eq 'html') {
64 0         0 die "Missing format $self->{fmt} for <$self->{string}>"
65             }
66 36801         188145 bless $self, $class;
67             }
68              
69             sub type {
70 395100     395100 1 425092 my ($self, $type) = @_;
71 395100 100       454424 if ($type) {
72 2744         3378 $self->{type} = $type;
73             }
74 395100         708834 return $self->{type};
75             }
76              
77             sub last_position {
78 4628     4628 1 7531 shift->{last_position};
79             }
80              
81             sub string {
82 73824     73824 1 153129 shift->{string};
83             }
84              
85             =item lang
86              
87             The language code.
88              
89             =cut
90              
91             sub lang {
92 13993     13993 1 22370 shift->{lang};
93             }
94              
95             =item append($element)
96              
97             Append the provided string to the self's one and update the
98             last_position.
99              
100             =cut
101              
102             sub append {
103 0     0 1 0 my ($self, $element) = @_;
104 0         0 $self->{string} .= $element->string;
105 0         0 $self->{last_position} = $element->last_position;
106             }
107              
108             sub tag {
109 35215 100   35215 1 45972 if (@_ > 1) {
110 64         89 $_[0]{tag} = $_[1];
111             }
112 35215         66133 $_[0]{tag};
113             }
114              
115             sub fmt {
116 51734     51734 1 110001 shift->{fmt};
117             }
118              
119             =item stringify
120              
121             Main method to get the desired output from the element.
122              
123             =cut
124              
125             sub stringify {
126 32208     32208 1 32857 my $self = shift;
127 32208         43419 my $type = $self->type;
128 32208         41228 my $string = $self->string;
129 32208 100       49331 if ($type eq 'text') {
130 25063 100       32756 if ($self->is_latex) {
    50          
131 11070         16149 $string = $self->escape_tex($string);
132 11070         16915 $string = $self->_ltx_replace_ldots($string);
133 11070         14806 $string = $self->_ltx_replace_slash($string);
134 11070         39593 return $string;
135             }
136             elsif ($self->is_html) {
137 13993 100       17626 if ($self->lang eq 'fr') {
138 41         69 $string = $self->_html_french_punctuation($string);
139 41         68 $string = $self->escape_all_html($string);
140 41         79 $string =~ s/\x{a0}/ /g; # make them visible
141 41         80 $string =~ s/\x{202f}/ /g; # ditto
142 41         174 return $string;
143             }
144             else {
145 13952         19870 return $self->escape_all_html($string);
146             }
147             }
148             else {
149 0         0 die "Not reached";
150             }
151             }
152 7145 100       11483 if ($type eq 'safe') {
153 1583         2766 return $self->verbatim_string($string);
154             }
155 5562 100       8285 if ($type eq 'ruby') {
156 18 50       107 if ($string =~ m/\A\s*(.+?)\s*\|\s*(.+?)\s*<\/ruby>\z/) {
157 18         59 my ($main, $ann) = ($1, $2);
158 18         42 $main = $self->verbatim_string($main);
159 18         38 $ann = $self->verbatim_string($ann);
160 18 100       32 if ($self->is_latex) {
    50          
161 9         57 return sprintf("\\ruby{%s}{%s}", $main, $ann);
162             }
163             elsif ($self->is_html) {
164 9         66 return sprintf("%s%s", $main, $ann);
165             }
166             }
167             }
168 5544 100 100     19494 if ($type eq 'verbatim') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
169 642 50       2532 if ($string =~ /\A(.*)<\/verbatim>\z/s) {
170 642         1329 $string = $1;
171 642         1076 return $self->verbatim_string($string);
172             }
173             else {
174 0         0 die "<$string> doesn't match verbatim!";
175             }
176             }
177             elsif ($type eq 'anchor') {
178 812         927 my $anchor = $string;
179 812         1474 $anchor =~ s/[^A-Za-z0-9-]//g;
180 812 50       1596 die "Bad anchor " . $string unless length($anchor);
181 812 100       1249 if ($self->is_latex) {
    50          
182 371         923 my $label = <<"TEX";
183             \\hyperdef{amuse}{$anchor}{}%
184             \\label{textamuse:$anchor}%
185             TEX
186 371         1615 return $label;
187             }
188             elsif ($self->is_html) {
189 441         2418 return qq{<\/a>\n}
190             }
191             else {
192 0         0 die "Not reached";
193             }
194             }
195             elsif ($type eq 'open' or $type eq 'close') {
196 2526 100       3633 if ($self->tag =~ m/\A\[([a-zA-Z-]+)\]\z/) {
197 44         113 my $iso = $1;
198 44 100       80 if ($self->is_latex) {
    50          
199 22 100       57 if ($type eq 'open') {
200 11         42 my $lang = Text::Amuse::Utils::get_latex_lang($iso);
201 11         84 return "\\foreignlanguage{$lang}{";
202             }
203             else {
204 11         47 return "}";
205             }
206             }
207             elsif ($self->is_html) {
208 22 100       49 if ($type eq 'open') {
209 11         75 return qq{};
210             }
211             else {
212 11         45 return "";
213             }
214             }
215             }
216 2482         3823 my $out = $self->_markup_table->{$self->tag}->{$type}->{$self->fmt};
217 2482 50       16525 die "Missing markup for $self->fmt $type $self->tag" unless $out;
218 2482         8759 return $out;
219             }
220             elsif ($type eq 'nobreakspace') {
221 90 100       199 if ($self->is_latex) {
    50          
222 45         174 return '~';
223             }
224             elsif ($self->is_html) {
225 45         172 return ' '
226             }
227             }
228             elsif ($type eq 'noindent') {
229 32 100       68 if ($self->is_latex) {
230 13         54 return "\\noindent ";
231             }
232             else {
233 19         36 my $leading = '';
234 19 50       72 if ($string =~ m/\A(\s+)/) {
235 0         0 $leading = $1;
236             }
237 19         101 return "$leading
";
238             }
239             }
240             elsif ($type eq 'br') {
241 555 100       851 if ($self->is_latex) {
242 247         804 return "\\forcelinebreak ";
243             }
244             else {
245 308         393 my $leading = '';
246 308 100       849 if ($string =~ m/\A(\s+)/) {
247 84         169 $leading = $1;
248             }
249 308         1259 return "$leading
";
250             }
251             }
252             elsif ($type eq 'bigskip') {
253 94 100       163 if ($self->is_latex) {
254 47         176 return "\n\\bigskip";
255             }
256             else {
257 47         67 my $leading = '';
258 47 100       142 if ($string =~ m/\A(\s+)/) {
259 1         4 $leading = $1;
260             }
261 47         212 return "$leading
";
262             }
263             }
264             elsif ($type eq 'verbatim_code') {
265             # remove the prefixes
266 793 100       5330 warn qq{ is already verbatim! in "$string"\n} if $string =~ m{.+};
267 793 100       3618 if ($string =~ /\A=(.+)=\z/s) {
    100          
    50          
268 628         1398 $string = $1;
269             }
270             elsif ($string =~ /\A(.*)<\/verbatim><\/code>\z/s) {
271 56         163 $string = $1;
272             }
273             elsif ($string =~ /\A(.*)<\/code>\z/s) {
274 109         253 $string = $1;
275             }
276             else {
277 0         0 die "$string doesn't match the pattern!";
278             }
279 793 100       1648 if (length $string) {
280             return $self->_markup_table->{code}->{open}->{$self->fmt}
281             . $self->verbatim_string($string)
282 775         1618 . $self->_markup_table->{code}->{close}->{$self->fmt};
283             }
284             else {
285 18         78 return '';
286             }
287             }
288             else {
289 0         0 die "Unrecognized type " . $type . " for " . $string;
290             }
291             }
292              
293             sub _markup_table {
294             return {
295 4032     4032   80557 'rtl' => {
296             open => {
297             html => '',
298             ltx => "\\RL{",
299             },
300             close => {
301             html => '‎', # LRM (U+200E LEFT-TO-RIGHT MARK)
302             ltx => '}',
303             },
304             },
305             'ltr' => {
306             open => {
307             html => '',
308             ltx => "\\LR{",
309             },
310             close => {
311             html => '‏', # RLM (U+200F RIGHT-TO-LEFT MARK)
312             ltx => '}',
313             },
314             },
315             'em' => {
316             open => {
317             html => '',
318             ltx => "\\emph{"
319             },
320             close => {
321             html => '',
322             ltx => '}',
323             }
324             },
325             'strong' => {
326             open => {
327             html => '',
328             ltx => "\\textbf{"
329             },
330             close => {
331             html => '',
332             ltx => '}',
333             }
334             },
335             'code' => {
336             open => {
337             html => '',
338             ltx => "\\texttt{",
339             },
340             close => {
341             html => '',
342             ltx => '}',
343             }
344             },
345             'strike' => {
346             open => {
347             html => '',
348             ltx => "\\sout{"
349             },
350             close => {
351             html => '',
352             ltx => '}',
353             }
354             },
355             'del' => {
356             open => {
357             html => '',
358             ltx => "\\sout{"
359             },
360             close => {
361             html => '',
362             ltx => '}',
363             }
364             },
365             'sup' => {
366             open => {
367             html => '',
368             ltx => "\\textsuperscript{"
369             },
370             close => {
371             html => '',
372             ltx => '}',
373             }
374             },
375             'sub' => {
376             open => {
377             html => '',
378             ltx => "\\textsubscript{"
379             },
380             close => {
381             html => '',
382             ltx => '}',
383             }
384             },
385             sf => {
386             open => {
387             html => '',
388             ltx => "\\textsf{"
389             },
390             close => {
391             html => '',
392             ltx => '}',
393             }
394             },
395             sc => {
396             open => {
397             html => '',
398             ltx => "\\textsc{"
399             },
400             close => {
401             html => '',
402             ltx => '}',
403             }
404             },
405             };
406             }
407              
408             sub _ltx_replace_ldots {
409 11070     11070   13670 my ($self, $string) = @_;
410 11070         12338 my $ldots = "\\dots{}";
411 11070         14104 $string =~ s/\.{3,4}/$ldots/g ;
412 11070         15910 $string =~ s/\x{2026}/$ldots/g;
413 11070         14866 return $string;
414             }
415              
416             sub _ltx_replace_slash {
417 11070     11070   14548 my ($self, $string) = @_;
418 11070         13944 $string =~ s!/!\\Slash{}!g;
419 11070         13144 return $string;
420             }
421              
422             # https://unicode.org/udhr/n/notes_fra.html
423             # espace fine insécable ; espace justifiante
424             # espace fine insécable ! espace justifiante
425             # espace fine insécable ? espace justifiante
426              
427             # espace mots insécable : espace justifiante
428             # espace mots insécable » espace justifiante
429              
430             # espace justifiante « espace mots insécable
431              
432             # espace justifiante tiret espace justifiante
433             # pas de blanc , espace justifiante
434             # pas de blanc . espace justifiante
435             # espace justifiante ( pas de blanc
436             # espace justifiante [ pas de blanc
437             # pas de blanc ) espace justifiante
438             # pas de blanc ] espace justifiante
439              
440              
441             sub _html_french_punctuation {
442 41     41   61 my ($self, $string) = @_;
443              
444             # try the #
445              
446             # optional space, punct, and then either space or end of line
447 41         95 my $chars = qr{[\x{20}\x{a0}\x{202f}\(\)\[\]\.\,\:«»\;\!\?]};
448 41         74 my $ws = qr{[\x{20}\x{a0}\x{202f}]};
449 41         419 $string =~ s/$ws*([;!?])(?=$chars)/\x{202f}$1/gs;
450 41         255 $string =~ s/$ws*([;!?])$/\x{202f}$1/gms;
451              
452             # ditto
453 41         268 $string =~ s/$ws*([:»])(?=$chars)/\x{a0}$1/gs;
454 41         217 $string =~ s/$ws*([:»])$/\x{a0}$1/gms;
455              
456 41         149 $string =~ s/^«$ws*/«\x{a0}/gms;
457 41         191 $string =~ s/(?<=$chars)«$ws*/«\x{a0}/gs;
458 41         122 return $string;
459             }
460              
461              
462             =item escape_all_html($string)
463              
464             HTML escape
465              
466             =cut
467              
468             sub escape_all_html {
469 15747     15747 1 19758 my ($self, $string) = @_;
470 15747         24841 $string =~ s/&/&/g;
471 15747         18561 $string =~ s/
472 15747         18139 $string =~ s/>/>/g;
473 15747         17193 $string =~ s/"/"/g;
474 15747         17013 $string =~ s/'/'/g;
475 15747         55513 return $string;
476             }
477              
478             =item escape_tex
479              
480             Escape the string for LaTeX output
481              
482             =cut
483              
484             sub escape_tex {
485 12352     12352 1 15450 my ($self, $string) = @_;
486 12352         21256 $string =~ s/\\/\\textbackslash{}/g;
487 12352         14494 $string =~ s/#/\\#/g ;
488 12352         14070 $string =~ s/\$/\\\$/g;
489 12352         15355 $string =~ s/%/\\%/g;
490 12352         15177 $string =~ s/&/\\&/g;
491 12352         14208 $string =~ s/_/\\_/g ;
492 12352         14298 $string =~ s/\{/\\{/g ;
493 12352         14160 $string =~ s/\}/\\}/g ;
494 12352         13863 $string =~ s/\\textbackslash\\\{\\\}/\\textbackslash{}/g;
495 12352         13774 $string =~ s/~/\\textasciitilde{}/g ;
496 12352         13894 $string =~ s/\^/\\^{}/g ;
497 12352         14136 $string =~ s/\|/\\textbar{}/g;
498 12352         18567 return $string;
499             }
500              
501              
502             =item is_latex
503              
504             Shortcut to check if the format is latex
505              
506             =item is_html
507              
508             Shortcut to check if the format is html
509              
510             =cut
511              
512             sub is_latex {
513 29744     29744 1 39335 shift->fmt eq 'ltx';
514             }
515              
516             sub is_html {
517 16264     16264 1 20977 shift->fmt eq 'html';
518             }
519              
520             =item unroll
521              
522             Convert the close_inline open_inline symbols (= and *) into elements
523             an open/close type and the tag properly set.
524              
525             =cut
526              
527             sub unroll {
528 1490     1490 1 1724 my $self = shift;
529 1490         1485 my @new;
530 1490         4991 my %map = (
531             '=' => [qw/code/],
532             '*' => [qw/em/],
533             '**' => [qw/strong/],
534             '***' => [qw/strong em/],
535             );
536 1490 100       2428 if ($self->type eq 'open_inline') {
    50          
537 752         1015 push @new, map { +{ type => 'open', tag => $_ } } @{$map{$self->tag}};
  854         2222  
  752         1257  
538             }
539             elsif ($self->type eq 'close_inline') {
540 738         1568 push @new, map { +{ type => 'close', tag => $_ } } reverse @{$map{$self->tag}};
  840         1912  
  738         1235  
541             }
542             else {
543 0         0 die "unroll can be called only on close_inline/open_inline, not " . $self->type . " " . $self->string;
544             }
545 1490         2355 return map { __PACKAGE__->new(%$_, string => '', fmt => $self->fmt) } @new;
  1694         4242  
546             }
547              
548             =item verbatim_string($string)
549              
550             Escape the string according to the element format
551              
552             =cut
553              
554             sub verbatim_string {
555 3036     3036 1 4571 my ($self, $string) = @_;
556 3036 100       4716 if ($self->is_latex) {
    50          
557 1282         1992 return $self->escape_tex($string);
558             }
559             elsif ($self->is_html) {
560 1754         2770 return $self->escape_all_html($string);
561             }
562             else {
563 0           die "Not reached";
564             }
565             }
566              
567             =back
568              
569             =cut
570              
571             1;