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   285 use strict;
  43         67  
  43         1107  
3 43     43   192 use warnings;
  43         69  
  43         882  
4 43     43   269 use utf8;
  43         89  
  43         208  
5 43     43   1253 use Text::Amuse::Utils;
  43         66  
  43         100009  
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 36429     36429 1 111282 my ($class, %args) = @_;
47 36429         103293 my $self = {
48             type => '',
49             string => '',
50             last_position => 0,
51             tag => '',
52             fmt => '',
53             lang => 'en',
54             };
55 36429         104406 foreach my $k (keys %$self) {
56 218574 100       314477 if (defined $args{$k}) {
57 180952         227003 $self->{$k} = $args{$k};
58             }
59 218574         268885 delete $args{$k};
60             }
61 36429 50       62088 die "Extra arguments passed :" . join(" ", %args) if %args;
62 36429 50       58415 die "Missing type for <$self->{string}>" unless $self->{type};
63 36429 50 66     90582 unless ($self->{fmt} eq 'ltx' or $self->{fmt} eq 'html') {
64 0         0 die "Missing format $self->{fmt} for <$self->{string}>"
65             }
66 36429         217492 bless $self, $class;
67             }
68              
69             sub type {
70 390516     390516 1 485427 my ($self, $type) = @_;
71 390516 100       506626 if ($type) {
72 2718         4050 $self->{type} = $type;
73             }
74 390516         809467 return $self->{type};
75             }
76              
77             sub last_position {
78 4554     4554 1 9033 shift->{last_position};
79             }
80              
81             sub string {
82 73029     73029 1 175194 shift->{string};
83             }
84              
85             =item lang
86              
87             The language code.
88              
89             =cut
90              
91             sub lang {
92 13859     13859 1 25782 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 52365 if (@_ > 1) {
110 64         141 $_[0]{tag} = $_[1];
111             }
112 35155         75858 $_[0]{tag};
113             }
114              
115             sub fmt {
116 51193     51193 1 125745 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 31850     31850 1 42079 my $self = shift;
127 31850         43060 my $type = $self->type;
128 31850         50022 my $string = $self->string;
129 31850 100       53657 if ($type eq 'text') {
130 24795 100       38688 if ($self->is_latex) {
    50          
131 10936         18880 $string = $self->escape_tex($string);
132 10936         19694 $string = $self->_ltx_replace_ldots($string);
133 10936         18468 $string = $self->_ltx_replace_slash($string);
134 10936         46972 return $string;
135             }
136             elsif ($self->is_html) {
137 13859 100       20449 if ($self->lang eq 'fr') {
138 41         73 $string = $self->_html_french_punctuation($string);
139 41         76 $string = $self->escape_all_html($string);
140 41         94 $string =~ s/\x{a0}/ /g; # make them visible
141 41         82 $string =~ s/\x{202f}/ /g; # ditto
142 41         201 return $string;
143             }
144             else {
145 13818         23184 return $self->escape_all_html($string);
146             }
147             }
148             else {
149 0         0 die "Not reached";
150             }
151             }
152 7055 100       12502 if ($type eq 'safe') {
153 1617         3418 return $self->verbatim_string($string);
154             }
155 5438 100       9812 if ($type eq 'ruby') {
156 18 50       107 if ($string =~ m/\A\s*(.+?)\s*\|\s*(.+?)\s*<\/ruby>\z/) {
157 18         54 my ($main, $ann) = ($1, $2);
158 18         34 $main = $self->verbatim_string($main);
159 18         35 $ann = $self->verbatim_string($ann);
160 18 100       33 if ($self->is_latex) {
    50          
161 9         65 return sprintf("\\ruby{%s}{%s}", $main, $ann);
162             }
163             elsif ($self->is_html) {
164 9         68 return sprintf("%s%s", $main, $ann);
165             }
166             }
167             }
168 5420 100 100     24408 if ($type eq 'verbatim') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
169 548 50       2455 if ($string =~ /\A(.*)<\/verbatim>\z/s) {
170 548         1265 $string = $1;
171 548         1086 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         1173 my $anchor = $string;
179 812         1777 $anchor =~ s/[^A-Za-z0-9-]//g;
180 812 50       1863 die "Bad anchor " . $string unless length($anchor);
181 812 100       1460 if ($self->is_latex) {
    50          
182 371         1167 my $label = <<"TEX";
183             \\hyperdef{amuse}{$anchor}{}%
184             \\label{textamuse:$anchor}%
185             TEX
186 371         2298 return $label;
187             }
188             elsif ($self->is_html) {
189 441         2749 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       3977 if ($self->tag =~ m/\A\[([a-zA-Z-]+)\]\z/) {
197 44         119 my $iso = $1;
198 44 100       90 if ($self->is_latex) {
    50          
199 22 100       55 if ($type eq 'open') {
200 11         43 my $lang = Text::Amuse::Utils::get_latex_lang($iso);
201 11         92 return "\\foreignlanguage{$lang}{";
202             }
203             else {
204 11         49 return "}";
205             }
206             }
207             elsif ($self->is_html) {
208 22 100       55 if ($type eq 'open') {
209 11         139 return qq{};
210             }
211             else {
212 11         52 return "";
213             }
214             }
215             }
216 2478         4704 my $out = $self->_markup_table->{$self->tag}->{$type}->{$self->fmt};
217 2478 50       18855 die "Missing markup for $self->fmt $type $self->tag" unless $out;
218 2478         10009 return $out;
219             }
220             elsif ($type eq 'nobreakspace') {
221 90 100       198 if ($self->is_latex) {
    50          
222 45         178 return '~';
223             }
224             elsif ($self->is_html) {
225 45         187 return ' '
226             }
227             }
228             elsif ($type eq 'noindent') {
229 32 100       72 if ($self->is_latex) {
230 13         58 return "\\noindent ";
231             }
232             else {
233 19         41 my $leading = '';
234 19 50       75 if ($string =~ m/\A(\s+)/) {
235 0         0 $leading = $1;
236             }
237 19         103 return "$leading
";
238             }
239             }
240             elsif ($type eq 'br') {
241 541 100       1012 if ($self->is_latex) {
242 240         925 return "\\forcelinebreak ";
243             }
244             else {
245 301         478 my $leading = '';
246 301 100       932 if ($string =~ m/\A(\s+)/) {
247 83         181 $leading = $1;
248             }
249 301         1348 return "$leading
";
250             }
251             }
252             elsif ($type eq 'bigskip') {
253 88 100       196 if ($self->is_latex) {
254 44         205 return "\n\\bigskip";
255             }
256             else {
257 44         83 my $leading = '';
258 44 100       212 if ($string =~ m/\A(\s+)/) {
259 1         4 $leading = $1;
260             }
261 44         238 return "$leading
";
262             }
263             }
264             elsif ($type eq 'verbatim_code') {
265             # remove the prefixes
266 787 100       7286 warn qq{ is already verbatim! in "$string"\n} if $string =~ m{.+};
267 787 100       4672 if ($string =~ /\A=(.+)=\z/s) {
    100          
    50          
268 628         1791 $string = $1;
269             }
270             elsif ($string =~ /\A(.*)<\/verbatim><\/code>\z/s) {
271 50         197 $string = $1;
272             }
273             elsif ($string =~ /\A(.*)<\/code>\z/s) {
274 109         298 $string = $1;
275             }
276             else {
277 0         0 die "$string doesn't match the pattern!";
278             }
279 787 100       2027 if (length $string) {
280             return $self->_markup_table->{code}->{open}->{$self->fmt}
281             . $self->verbatim_string($string)
282 769         1666 . $self->_markup_table->{code}->{close}->{$self->fmt};
283             }
284             else {
285 18         75 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   97625 '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   15638 my ($self, $string) = @_;
410 10936         14193 my $ldots = "\\dots{}";
411 10936         16994 $string =~ s/\.{3,4}/$ldots/g ;
412 10936         18426 $string =~ s/\x{2026}/$ldots/g;
413 10936         17490 return $string;
414             }
415              
416             sub _ltx_replace_slash {
417 10936     10936   15635 my ($self, $string) = @_;
418 10936         15708 $string =~ s!/!\\Slash{}!g;
419 10936         16072 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   63 my ($self, $string) = @_;
443              
444             # try the #
445              
446             # optional space, punct, and then either space or end of line
447 41         112 my $chars = qr{[\x{20}\x{a0}\x{202f}\(\)\[\]\.\,\:«»\;\!\?]};
448 41         79 my $ws = qr{[\x{20}\x{a0}\x{202f}]};
449 41         450 $string =~ s/$ws*([;!?])(?=$chars)/\x{202f}$1/gs;
450 41         265 $string =~ s/$ws*([;!?])$/\x{202f}$1/gms;
451              
452             # ditto
453 41         291 $string =~ s/$ws*([:»])(?=$chars)/\x{a0}$1/gs;
454 41         229 $string =~ s/$ws*([:»])$/\x{a0}$1/gms;
455              
456 41         164 $string =~ s/^«$ws*/«\x{a0}/gms;
457 41         204 $string =~ s/(?<=$chars)«$ws*/«\x{a0}/gs;
458 41         139 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 15580     15580 1 23356 my ($self, $string) = @_;
470 15580         29157 $string =~ s/&/&/g;
471 15580         21053 $string =~ s/
472 15580         21045 $string =~ s/>/>/g;
473 15580         20251 $string =~ s/"/"/g;
474 15580         20450 $string =~ s/'/'/g;
475 15580         64431 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 17792 my ($self, $string) = @_;
486 12185         23835 $string =~ s/\\/\\textbackslash{}/g;
487 12185         17285 $string =~ s/#/\\#/g ;
488 12185         15570 $string =~ s/\$/\\\$/g;
489 12185         16299 $string =~ s/%/\\%/g;
490 12185         15336 $string =~ s/&/\\&/g;
491 12185         15259 $string =~ s/_/\\_/g ;
492 12185         15929 $string =~ s/\{/\\{/g ;
493 12185         15947 $string =~ s/\}/\\}/g ;
494 12185         15781 $string =~ s/\\textbackslash\\\{\\\}/\\textbackslash{}/g;
495 12185         15407 $string =~ s/~/\\textasciitilde{}/g ;
496 12185         15151 $string =~ s/\^/\\^{}/g ;
497 12185         16121 $string =~ s/\|/\\textbar{}/g;
498 12185         21438 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 29390     29390 1 45323 shift->fmt eq 'ltx';
514             }
515              
516             sub is_html {
517 16097     16097 1 23458 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 2085 my $self = shift;
529 1486         1893 my @new;
530 1486         5829 my %map = (
531             '=' => [qw/code/],
532             '*' => [qw/em/],
533             '**' => [qw/strong/],
534             '***' => [qw/strong em/],
535             );
536 1486 100       2552 if ($self->type eq 'open_inline') {
    50          
537 750         1305 push @new, map { +{ type => 'open', tag => $_ } } @{$map{$self->tag}};
  852         2658  
  750         1300  
538             }
539             elsif ($self->type eq 'close_inline') {
540 736         1139 push @new, map { +{ type => 'close', tag => $_ } } reverse @{$map{$self->tag}};
  838         2244  
  736         1354  
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         2523 return map { __PACKAGE__->new(%$_, string => '', fmt => $self->fmt) } @new;
  1690         4983  
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 2970     2970 1 5501 my ($self, $string) = @_;
556 2970 100       5090 if ($self->is_latex) {
    50          
557 1249         2641 return $self->escape_tex($string);
558             }
559             elsif ($self->is_html) {
560 1721         3268 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;