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 44     44   332 use strict;
  44         114  
  44         1273  
3 44     44   235 use warnings;
  44         100  
  44         1103  
4 44     44   250 use utf8;
  44         106  
  44         231  
5 44     44   1535 use Text::Amuse::Utils;
  44         93  
  44         122939  
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 36431     36431 1 132251 my ($class, %args) = @_;
47 36431         125476 my $self = {
48             type => '',
49             string => '',
50             last_position => 0,
51             tag => '',
52             fmt => '',
53             lang => 'en',
54             };
55 36431         122652 foreach my $k (keys %$self) {
56 218586 100       388524 if (defined $args{$k}) {
57 180962         269811 $self->{$k} = $args{$k};
58             }
59 218586         333227 delete $args{$k};
60             }
61 36431 50       80106 die "Extra arguments passed :" . join(" ", %args) if %args;
62 36431 50       77217 die "Missing type for <$self->{string}>" unless $self->{type};
63 36431 50 66     105687 unless ($self->{fmt} eq 'ltx' or $self->{fmt} eq 'html') {
64 0         0 die "Missing format $self->{fmt} for <$self->{string}>"
65             }
66 36431         264658 bless $self, $class;
67             }
68              
69             sub type {
70 390539     390539 1 589958 my ($self, $type) = @_;
71 390539 100       627019 if ($type) {
72 2718         4485 $self->{type} = $type;
73             }
74 390539         1008594 return $self->{type};
75             }
76              
77             sub last_position {
78 4555     4555 1 10133 shift->{last_position};
79             }
80              
81             sub string {
82 73033     73033 1 205759 shift->{string};
83             }
84              
85             =item lang
86              
87             The language code.
88              
89             =cut
90              
91             sub lang {
92 13860     13860 1 30043 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 35155 100   35155 1 63591 if (@_ > 1) {
110 64         116 $_[0]{tag} = $_[1];
111             }
112 35155         90586 $_[0]{tag};
113             }
114              
115             sub fmt {
116 51201     51201 1 153916 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 31852     31852 1 47242 my $self = shift;
127 31852         52190 my $type = $self->type;
128 31852         60063 my $string = $self->string;
129 31852 100       66819 if ($type eq 'text') {
130 24796 100       46595 if ($self->is_latex) {
    50          
131 10936         21065 $string = $self->escape_tex($string);
132 10936         22383 $string = $self->_ltx_replace_ldots($string);
133 10936         19507 $string = $self->_ltx_replace_slash($string);
134 10936         56424 return $string;
135             }
136             elsif ($self->is_html) {
137 13860 100       24695 if ($self->lang eq 'fr') {
138 41         97 $string = $self->_html_french_punctuation($string);
139 41         94 $string = $self->escape_all_html($string);
140 41         112 $string =~ s/\x{a0}/ /g; # make them visible
141 41         105 $string =~ s/\x{202f}/ /g; # ditto
142 41         236 return $string;
143             }
144             else {
145 13819         26403 return $self->escape_all_html($string);
146             }
147             }
148             else {
149 0         0 die "Not reached";
150             }
151             }
152 7056 100       15355 if ($type eq 'safe') {
153 1617         3831 return $self->verbatim_string($string);
154             }
155 5439 100       11314 if ($type eq 'ruby') {
156 19 50       154 if ($string =~ m/\A\s*(.+?)\s*\|\s*(.+?)\s*<\/ruby>\z/) {
157 19         78 my ($main, $ann) = ($1, $2);
158 19         61 $main = $self->verbatim_string($main);
159 19         46 $ann = $self->verbatim_string($ann);
160 19 100       42 if ($self->is_latex) {
    50          
161 9         101 return sprintf("\\ruby{%s}{%s}", $main, $ann);
162             }
163             elsif ($self->is_html) {
164 10         137 return sprintf("%s%s", $main, $ann);
165             }
166             }
167             }
168 5420 100 100     26140 if ($type eq 'verbatim') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
169 548 50       3227 if ($string =~ /\A(.*)<\/verbatim>\z/s) {
170 548         1586 $string = $1;
171 548         1299 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         1344 my $anchor = $string;
179 812         2091 $anchor =~ s/[^A-Za-z0-9-]//g;
180 812 50       2198 die "Bad anchor " . $string unless length($anchor);
181 812 100       1757 if ($self->is_latex) {
    50          
182 371         1319 my $label = <<"TEX";
183             \\hyperdef{amuse}{$anchor}{}%
184             \\label{textamuse:$anchor}%
185             TEX
186 371         2350 return $label;
187             }
188             elsif ($self->is_html) {
189 441         3425 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 2522 100       4676 if ($self->tag =~ m/\A\[([a-zA-Z-]+)\]\z/) {
197 44         127 my $iso = $1;
198 44 100       104 if ($self->is_latex) {
    50          
199 22 100       57 if ($type eq 'open') {
200 11         40 my $lang = Text::Amuse::Utils::get_latex_lang($iso);
201 11         101 return "\\foreignlanguage{$lang}{";
202             }
203             else {
204 11         57 return "}";
205             }
206             }
207             elsif ($self->is_html) {
208 22 100       60 if ($type eq 'open') {
209 11         77 return qq{};
210             }
211             else {
212 11         62 return "";
213             }
214             }
215             }
216 2478         5006 my $out = $self->_markup_table->{$self->tag}->{$type}->{$self->fmt};
217 2478 50       23212 die "Missing markup for $self->fmt $type $self->tag" unless $out;
218 2478         11925 return $out;
219             }
220             elsif ($type eq 'nobreakspace') {
221 90 100       204 if ($self->is_latex) {
    50          
222 45         198 return '~';
223             }
224             elsif ($self->is_html) {
225 45         216 return ' '
226             }
227             }
228             elsif ($type eq 'noindent') {
229 32 100       88 if ($self->is_latex) {
230 13         70 return "\\noindent ";
231             }
232             else {
233 19         38 my $leading = '';
234 19 50       90 if ($string =~ m/\A(\s+)/) {
235 0         0 $leading = $1;
236             }
237 19         125 return "$leading
";
238             }
239             }
240             elsif ($type eq 'br') {
241 541 100       1125 if ($self->is_latex) {
242 240         1100 return "\\forcelinebreak ";
243             }
244             else {
245 301         544 my $leading = '';
246 301 100       1129 if ($string =~ m/\A(\s+)/) {
247 83         222 $leading = $1;
248             }
249 301         1592 return "$leading
";
250             }
251             }
252             elsif ($type eq 'bigskip') {
253 88 100       289 if ($self->is_latex) {
254 44         257 return "\n\\bigskip";
255             }
256             else {
257 44         100 my $leading = '';
258 44 100       221 if ($string =~ m/\A(\s+)/) {
259 1         4 $leading = $1;
260             }
261 44         280 return "$leading
";
262             }
263             }
264             elsif ($type eq 'verbatim_code') {
265             # remove the prefixes
266 787 100       4931 warn qq{ is already verbatim! in "$string"\n} if $string =~ m{.+};
267 787 100       4914 if ($string =~ /\A=(.+)=\z/s) {
    100          
    50          
268 628         1927 $string = $1;
269             }
270             elsif ($string =~ /\A(.*)<\/verbatim><\/code>\z/s) {
271 50         199 $string = $1;
272             }
273             elsif ($string =~ /\A(.*)<\/code>\z/s) {
274 109         356 $string = $1;
275             }
276             else {
277 0         0 die "$string doesn't match the pattern!";
278             }
279 787 100       2154 if (length $string) {
280             return $self->_markup_table->{code}->{open}->{$self->fmt}
281             . $self->verbatim_string($string)
282 769         1895 . $self->_markup_table->{code}->{close}->{$self->fmt};
283             }
284             else {
285 18         93 return '';
286             }
287             }
288             else {
289 0         0 die "Unrecognized type " . $type . " for " . $string;
290             }
291             }
292              
293             sub _markup_table {
294             return {
295 4016     4016   103441 '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 10936     10936   19549 my ($self, $string) = @_;
410 10936         16215 my $ldots = "\\dots{}";
411 10936         20140 $string =~ s/\.{3,4}/$ldots/g ;
412 10936         22770 $string =~ s/\x{2026}/$ldots/g;
413 10936         20199 return $string;
414             }
415              
416             sub _ltx_replace_slash {
417 10936     10936   19190 my ($self, $string) = @_;
418 10936         19241 $string =~ s!/!\\Slash{}!g;
419 10936         19486 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   75 my ($self, $string) = @_;
443              
444             # try the #
445              
446             # optional space, punct, and then either space or end of line
447 41         120 my $chars = qr{[\x{20}\x{a0}\x{202f}\(\)\[\]\.\,\:«»\;\!\?]};
448 41         92 my $ws = qr{[\x{20}\x{a0}\x{202f}]};
449 41         553 $string =~ s/$ws*([;!?])(?=$chars)/\x{202f}$1/gs;
450 41         332 $string =~ s/$ws*([;!?])$/\x{202f}$1/gms;
451              
452             # ditto
453 41         343 $string =~ s/$ws*([:»])(?=$chars)/\x{a0}$1/gs;
454 41         281 $string =~ s/$ws*([:»])$/\x{a0}$1/gms;
455              
456 41         196 $string =~ s/^«$ws*/«\x{a0}/gms;
457 41         255 $string =~ s/(?<=$chars)«$ws*/«\x{a0}/gs;
458 41         166 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 15583     15583 1 26531 my ($self, $string) = @_;
470 15583         34309 $string =~ s/&/&/g;
471 15583         24492 $string =~ s/
472 15583         24525 $string =~ s/>/>/g;
473 15583         25208 $string =~ s/"/"/g;
474 15583         24688 $string =~ s/'/'/g;
475 15583         76785 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 12185     12185 1 21795 my ($self, $string) = @_;
486 12185         28869 $string =~ s/\\/\\textbackslash{}/g;
487 12185         20717 $string =~ s/#/\\#/g ;
488 12185         19685 $string =~ s/\$/\\\$/g;
489 12185         19789 $string =~ s/%/\\%/g;
490 12185         19397 $string =~ s/&/\\&/g;
491 12185         18910 $string =~ s/_/\\_/g ;
492 12185         19023 $string =~ s/\{/\\{/g ;
493 12185         19844 $string =~ s/\}/\\}/g ;
494 12185         19519 $string =~ s/\\textbackslash\\\{\\\}/\\textbackslash{}/g;
495 12185         19140 $string =~ s/~/\\textasciitilde{}/g ;
496 12185         19253 $string =~ s/\^/\\^{}/g ;
497 12185         18158 $string =~ s/\|/\\textbar{}/g;
498 12185         25520 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 29394     29394 1 52969 shift->fmt eq 'ltx';
514             }
515              
516             sub is_html {
517 16101     16101 1 27041 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 1486     1486 1 2316 my $self = shift;
529 1486         2125 my @new;
530 1486         6048 my %map = (
531             '=' => [qw/code/],
532             '*' => [qw/em/],
533             '**' => [qw/strong/],
534             '***' => [qw/strong em/],
535             );
536 1486 100       2900 if ($self->type eq 'open_inline') {
    50          
537 750         1346 push @new, map { +{ type => 'open', tag => $_ } } @{$map{$self->tag}};
  852         2977  
  750         1423  
538             }
539             elsif ($self->type eq 'close_inline') {
540 736         1287 push @new, map { +{ type => 'close', tag => $_ } } reverse @{$map{$self->tag}};
  838         2491  
  736         1396  
541             }
542             else {
543 0         0 die "unroll can be called only on close_inline/open_inline, not " . $self->type . " " . $self->string;
544             }
545 1486         2841 return map { __PACKAGE__->new(%$_, string => '', fmt => $self->fmt) } @new;
  1690         5233  
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 2972     2972 1 6204 my ($self, $string) = @_;
556 2972 100       6469 if ($self->is_latex) {
    50          
557 1249         2902 return $self->escape_tex($string);
558             }
559             elsif ($self->is_html) {
560 1723         3747 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;