File Coverage

blib/lib/Text/Amuse/Document.pm
Criterion Covered Total %
statement 672 687 97.8
branch 255 288 88.5
condition 81 92 88.0
subroutine 72 73 98.6
pod 19 19 100.0
total 1099 1159 94.8


line stmt bran cond sub pod time code
1             package Text::Amuse::Document;
2              
3 46     46   263098 use strict;
  46         126  
  46         974  
4 46     46   159 use warnings;
  46         66  
  46         816  
5 46     46   14177 use Text::Amuse::Element;
  46         104  
  46         1156  
6 46     46   15537 use Text::Amuse::Utils;
  46         93  
  46         1004  
7 46     46   216 use File::Spec;
  46         74  
  46         1140  
8             use constant {
9 46         4172 IMAJOR => 1,
10             IEQUAL => 0,
11             IMINOR => -1,
12 46     46   201 };
  46         67  
13              
14 46     46   18757 use Data::Dumper;
  46         206489  
  46         262566  
15              
16             =head1 NAME
17              
18             Text::Amuse::Document - core parser for L (internal)
19              
20             =head1 SYNOPSIS
21              
22             The module is used internally by L, so everything here is
23             pretty much internal only (and underdocumented). The useful stuff is
24             accessible via the L class.
25              
26             =head1 METHODS
27              
28             =over 4
29              
30             =item new(file => $filename, include_paths => \@paths )
31              
32             =cut
33              
34             sub new {
35 736     736 1 45217 my $class = shift;
36 736         1011 my %args;
37 736         5728 my $self = {
38             _raw_footnotes => {},
39             _current_footnote_indent => 0,
40             _current_footnote_number => undef,
41             _current_footnote_stack => [],
42             _list_element_pile => [],
43             _list_parsing_output => [],
44             _bidi_document => 0,
45             _has_ruby => 0,
46             include_paths => [],
47             included_files => [],
48             _other_doc_language_codes => [],
49             };
50 736 50       2361 if (@_ % 2 == 0) {
    0          
51 736         2701 %args = @_;
52             }
53             elsif (@_ == 1) {
54 0         0 $args{file} = shift;
55             }
56             else {
57 0         0 die "Wrong arguments! The constructor accepts only a filename\n";
58             }
59 736 50       11401 if (-f $args{file}) {
60 736         2851 $self->{filename} = $args{file};
61             } else {
62 0         0 die "Wrong argument! $args{file} doesn't exist!\n"
63             }
64 736 100       1787 if ($args{include_paths}) {
65 4         4 my @includes;
66 4 50       11 if (ref($args{include_paths}) eq 'ARRAY') {
67 4         5 push @includes, @{$args{include_paths}};
  4         8  
68             }
69             else {
70 0         0 push @includes, $args{include_paths};
71             }
72 4 100       7 $self->{include_paths} = [ grep { length($_) && -d $_ } @includes ];
  7         87  
73             }
74 736 100       1664 $self->{debug} = 1 if $args{debug};
75 736         2959 bless $self, $class;
76             }
77              
78              
79             =item include_paths
80              
81             The return the list of directories where the included files need to be searched.
82              
83             =item included_files
84              
85             The return the list of files actually included.
86              
87             =cut
88              
89             sub include_paths {
90 664     664 1 875 return @{ shift->{include_paths} };
  664         2414  
91             }
92              
93             sub included_files {
94 7     7 1 9 return @{ shift->{included_files} };
  7         36  
95             }
96              
97             sub _add_to_included_files {
98 2     2   6 my ($self, @files) = @_;
99 2         3 push @{shift->{included_files}}, @files;
  2         6  
100             }
101              
102             sub _list_index_map {
103             # numerals
104 1080     1080   1349 my $self = shift;
105 1080 100       2070 unless ($self->{_list_index_map}) {
106 98         330 my %map = map { $_ => $_ } (1..200); # never seen lists so long
  19600         31129  
107             # this is a bit naif but will do. Generated with Roman module. We
108             # support them to 89, otherwise you have to use i. i. i.
109              
110 98         2648 my @romans = (qw/i ii iii iv v vi vii viii ix x xi xii xiii
111             xiv xv xvi xvii xviii xix xx xxi xxii xxiii
112             xxiv xxv xxvi xxvii xxviii xxix xxx xxxi
113             xxxii xxxiii xxxiv xxxv xxxvi xxxvii xxxviii
114             xxxix xl xli xlii xliii xliv xlv xlvi xlvii
115             xlviii xlix l li lii liii liv lv lvi lvii
116             lviii lix lx lxi lxii lxiii lxiv lxv lxvi
117             lxvii lxviii lxix lxx lxxi lxxii lxxiii lxxiv
118             lxxv lxxvi lxxvii lxxviii lxxix lxxx lxxxi
119             lxxxii lxxxiii lxxxiv lxxxv lxxxvi lxxxvii
120             lxxxviii lxxxix/);
121 98         583 my @alpha = ('a'..'z');
122             # we will need to take care of 'i', 'x', 'v', 'l', which can be both alpha or roman
123 98         279 foreach my $list (\@alpha, \@romans) {
124 196         257 my $lcount = 0;
125 196         303 foreach my $letter (@$list) {
126 11270         9697 $lcount++;
127 11270         14166 $map{$letter} = $lcount;
128 11270         17044 $map{uc($letter)} = $lcount;
129             }
130             }
131 98         661 $self->{_list_index_map} = \%map;
132             }
133 1080         1668 return $self->{_list_index_map};
134             }
135              
136              
137             sub _debug {
138 3730     3730   735525 my $self = shift;
139 3730         5885 my @args = @_;
140 3730 100 66     11846 if ((@args) && $self->{debug}) {
141 42         7023 print join("\n", @args), "\n";
142             }
143             }
144              
145              
146             =item filename
147              
148             Return the filename of the processed file
149              
150             =cut
151              
152             sub filename {
153 680     680 1 834 my $self = shift;
154             return $self->{filename}
155 680         1215 }
156              
157             =item attachments
158              
159             Return the list of the filenames of the attached files, as linked.
160             With an optional argument, store that file in the list.
161              
162              
163             =cut
164              
165             sub attachments {
166 422     422 1 612 my ($self, $arg) = @_;
167 422 100       842 unless (defined $self->{_attached_files}) {
168 24         60 $self->{_attached_files} = {};
169             }
170 422 100       660 if (defined $arg) {
171 420         760 $self->{_attached_files}->{$arg} = 1;
172 420         600 return;
173             }
174             else {
175 2         3 return sort(keys %{$self->{_attached_files}});
  2         12  
176             }
177             }
178              
179             =item bidi_document
180              
181             Return true if the document uses a bidirectionl marker.
182              
183             =item set_bidi_document
184              
185             Internal, set the bidi flag on.
186              
187             =item has_ruby
188              
189             Return true if the document uses the ruby
190              
191             =item set_has_ruby
192              
193             Internal, set the ruby flag on.
194              
195             =cut
196              
197             sub bidi_document {
198 36     36 1 131 shift->{_bidi_document};
199             }
200              
201             sub set_bidi_document {
202 131     131 1 220 shift->{_bidi_document} = 1;
203             }
204              
205             sub set_has_ruby {
206 18     18 1 40 shift->{_has_ruby} = 1;
207             }
208              
209             sub has_ruby {
210 0     0 1 0 shift->{_has_ruby};
211             }
212              
213              
214              
215             =item language_code
216              
217             The language code of the document. This method will looks into the
218             header of the document, searching for the keys C or C,
219             defaulting to C.
220              
221             =item other_language_codes
222              
223             Same as above, but for other languages declared with the experimental
224             tag C<<[en>>
225              
226             =item language
227              
228             Same as above, but returns the human readable version, notably used by
229             Babel, Polyglossia, etc.
230              
231             =item other_languages
232              
233             Same as above, for the other languages
234              
235             =cut
236              
237             sub _language_mapping {
238 787     787   2359 return Text::Amuse::Utils::language_mapping();
239             }
240              
241             sub language_code {
242 1039     1039 1 1484 my $self = shift;
243 1039 100       2217 unless (defined $self->{_doc_language_code}) {
244 627         1666 my %header = $self->raw_header;
245 627   100     2966 my $lang = $header{lang} || $header{language} || "en";
246 627         823 my $real = "en";
247             # check if language exists;
248 627 100       1184 if ($self->_language_mapping->{$lang}) {
249 602         1098 $real = $lang;
250             }
251 627         10827 $self->{_doc_language_code} = $real;
252             }
253 1039         3027 return $self->{_doc_language_code};
254             }
255              
256             sub language {
257 64     64 1 74 my $self = shift;
258 64 50       137 unless (defined $self->{_doc_language}) {
259 64         89 my $lc = $self->language_code;
260             # guaranteed not to return undef
261 64         87 $self->{_doc_language} = $self->_language_mapping->{$lc};
262             }
263 64         1090 return $self->{_doc_language};
264             }
265              
266             sub other_language_codes {
267 161     161 1 215 my $self = shift;
268 161         177 my @out = @{ $self->{_other_doc_language_codes} };
  161         363  
269 161 100       723 return @out ? \@out : undef;
270             }
271              
272             sub other_languages {
273 34     34 1 47 my $self = shift;
274 34         56 my $map = $self->_language_mapping;
275 34 100       68 my @out = map { $map->{$_} } @{ $self->other_language_codes || [] };
  5         11  
  34         60  
276 34 100       546 return @out ? \@out : undef;
277             }
278              
279             sub _add_to_other_language_codes {
280 62     62   123 my ($self, $lang) = @_;
281 62 50       148 return unless $lang;
282 62         142 $lang = lc($lang);
283 62 100       149 if ($self->_language_mapping->{$lang}) {
284 61 100       167 if ($lang ne $self->language_code) {
285 60 100       89 unless (grep { $_ eq $lang } @{ $self->other_language_codes || [] }) {
  114 100       265  
  60         130  
286 11         20 push @{$self->{_other_doc_language_codes}}, $lang;
  11         31  
287 11         23 return $lang;
288             }
289             }
290             }
291             else {
292 1         46 warn "Unknown language $lang";
293             }
294 51         1199 return 'en';
295             }
296              
297             =item parse_directives
298              
299             Return an hashref with the directives found in the document.
300              
301             =cut
302              
303             sub parse_directives {
304 19     19 1 26 my $self = shift;
305 19         37 my ($directives, $body) = $self->_parse_body_and_directives(directives_only => 1);
306 19         70 return $directives;
307             }
308              
309              
310             sub _parse_body_and_directives {
311 679     679   1302 my ($self, %options) = @_;
312 679         1420 my $file = $self->filename;
313 679 50   22   23418 open (my $fh, "<:encoding(UTF-8)", $file) or die "Couldn't open $file! $!\n";
  22         152  
  22         35  
  22         329  
314              
315 679         223797 my $in_meta = 1;
316 679         1505 my ($lastdirective, %directives, @body);
317 679         0 my @directives_array;
318             RAWLINE:
319 679         22165 while (my $line = <$fh>) {
320             # EOL
321 20028         38649 $line =~ s/\r\n/\n/gs;
322 20028         21121 $line =~ s/\r/\n/gs;
323             # TAB
324 20028         20969 $line =~ s/\t/ /g;
325             # trailing
326 20028         31687 $line =~ s/ +$//mg;
327              
328 20028 100       23650 if ($in_meta) {
329             # reset the directives on blank lines
330 2250 100       7862 if ($line =~ m/^\s*$/s) {
    100          
    100          
331 637         781 $lastdirective = undef;
332             } elsif ($line =~ m/^\#([A-Za-z0-9_-]+)(\s+(.+))?$/s) {
333 848         2619 my ($dir, $material) = ($1, $3);
334              
335             # remove underscore and dashes from directive names to
336             # keep compatibility with Emacs Muse, so e.g.
337             # #disable-tables will be parsed as directive, not as
338             # a line.
339              
340 848         1679 $dir =~ s/[_-]//g;
341 848 100       1887 unless (length($dir)) {
342 24         1696 warn "$file: Found empty directive $line, it will be removed\n";
343             }
344 848 100       1881 if (exists $directives{$dir}) {
345 17         1019 warn "$file: Overwriting directive '$dir' $directives{$dir} with $line\n";
346             }
347 848 100       1505 if (defined $material) {
348 789         1869 $directives{$dir} = $material;
349             }
350             else {
351 59         145 $directives{$dir} = '';
352             }
353 848         2152 push @directives_array, [ $dir, $directives{$dir} ];
354              
355 848         1583 $lastdirective = $dir;
356             } elsif ($lastdirective) {
357 86         181 $directives{$lastdirective} .= $line;
358 86         127 $directives_array[-1][1] .= $line;
359             } else {
360 679         957 $in_meta = 0
361             }
362             }
363 20028 100       25533 if ($in_meta) {
    100          
364 1571         3655 next RAWLINE;
365             }
366             elsif ($options{directives_only}) {
367 19         30 last RAWLINE;
368             }
369             else {
370 18438         41634 push @body, $line;
371             }
372             }
373 679         2019 push @body, "\n"; # append a newline
374 679         7696 close $fh;
375              
376             # before returning, let's clean the %directives from EOLs and from
377             # empty ones, e.g. #---------------------
378 679         1568 delete $directives{''};
379              
380 679         2226 foreach my $key (keys %directives) {
381 819         3887 $directives{$key} =~ s/\s+/ /gs;
382 819         2839 $directives{$key} =~ s/\s+\z//gs;
383 819         1903 $directives{$key} =~ s/\A\s+//gs;
384             }
385 679         3530 return (\%directives, \@body, \@directives_array);
386             }
387              
388             sub _split_body_and_directives {
389 660     660   836 my $self = shift;
390 660         1394 my ($directives, $body, $dir_array) = $self->_parse_body_and_directives;
391              
392 660 100       1969 if (my @include_paths = $self->include_paths) {
393             # rescan the body and do the inclusion
394 4         6 my @full_body;
395             LINE:
396 4         6 foreach my $l (@$body) {
397 38 100       99 if ($l =~ m/^#include\s+(.+?)\s*$/) {
398 11 100       21 if (my $lines = $self->_resolve_include($1, \@include_paths)) {
399 2         5 push @full_body, @$lines;
400 2         4 next LINE;
401             }
402             }
403 36         53 push @full_body, $l;
404             }
405 4         9 $body = \@full_body;
406             }
407 660         1175 $self->{raw_body} = $body;
408 660         1033 $self->{raw_header} = $directives;
409 660         1331 $self->{directives_array} = $dir_array;
410             }
411              
412             sub _resolve_include {
413 11     11   27 my ($self, $filename, $include_paths) = @_;
414 11         88 my ($volume, $directories, $file) = File::Spec->splitpath($filename);
415 11         56 my @dirs = grep { length $_ } File::Spec->splitdir($directories);
  71         115  
416             # if hidden files or traversals are passed, bail out.
417 11 100       19 if (grep { /^\./ } @dirs, $file) {
  66         101  
418 6         363 warn "Directory traversal or hidden file found in included $filename!";
419 6         39 return;
420             }
421             # if we have slash (unix) or backslash (windows), it's not good
422 5 100       6 if (grep { /[\/\\]/ } @dirs, $file) {
  8         20  
423 2         12 warn "Invalid file or directory name (slashes?) found in included $filename!";
424 2         9 return;
425             }
426             # just in case
427 3 100       6 return unless $file;
428              
429             # the base directory are set by the object, not by the user, so
430             # they are considered safe.
431 2         3 my @out;
432              
433             INCLUDEFILE:
434 2         3 foreach my $base (@$include_paths) {
435 2         15 my $final = File::Spec->catfile($base, @dirs, $file);
436 2 50 33     116 if (-e $final && -T $final) {
437 2 50       54 open (my $fh, "<:encoding(UTF-8)", $final) or die "Couldn't open $final! $!\n";
438 2         123 while (my $line = <$fh>) {
439 6         20 $line =~ s/\r\n/\n/gs;
440 6         8 $line =~ s/\r/\n/gs;
441             # TAB
442 6         6 $line =~ s/\t/ /g;
443             # trailing
444 6         10 $line =~ s/ +$//mg;
445 6         27 push @out, $line;
446             }
447 2         18 close $fh;
448 2         8 $self->_add_to_included_files($final);
449 2         6 last INCLUDEFILE;
450             }
451             }
452 2 50       4 if (@out) {
453 2         7 return \@out;
454             }
455             else {
456 0         0 return;
457             }
458             }
459              
460             =item raw_header
461              
462             Accessor to the raw header of the muse file. The header is returned as
463             hash, with key/value pairs. Please note: NOT an hashref.
464              
465             =cut
466              
467             sub raw_header {
468 799     799 1 1029 my $self = shift;
469 799 100       1563 unless (defined $self->{raw_header}) {
470 641         1538 $self->_split_body_and_directives;
471             }
472 799         1005 return %{$self->{raw_header}}
  799         2762  
473             }
474              
475             =item raw_body
476              
477             Accessor to the raw body of the muse file. The body is returned as a
478             list of lines.
479              
480             =item directives_array
481              
482             This is very similar to raw_header, but store them in an array, so the
483             header can be rewritten.
484              
485             =cut
486              
487             sub raw_body {
488 643     643 1 826 my $self = shift;
489 643 100       1236 unless (defined $self->{raw_body}) {
490 17         49 $self->_split_body_and_directives;
491             }
492 643         763 return @{$self->{raw_body}}
  643         4027  
493             }
494              
495             sub directives_array {
496 2     2 1 4 my $self = shift;
497 2 50       6 unless (defined $self->{directives_array}) {
498 2         6 $self->_split_body_and_directives;
499             }
500 2         2 return @{$self->{directives_array}}
  2         5  
501             }
502              
503             sub _parse_body {
504 632     632   844 my $self = shift;
505 632         1599 $self->_debug("Parsing body");
506              
507             # be sure to start with a null block and reset the state
508 632         1769 my @parsed = ($self->_construct_element(""));
509 632         1300 $self->_current_el(undef);
510              
511 632         1286 foreach my $l ($self->raw_body) {
512             # if doesn't return anything, the thing got merged
513 18944 100       26245 if (my $el = $self->_construct_element($l)) {
514 12651         20751 push @parsed, $el;
515             }
516             }
517 632         5254 $self->_debug(Dumper(\@parsed));
518              
519             # turn the versep into verse now that the merging is done
520 632         10502 foreach my $el (@parsed) {
521 13283 100       18317 if ($el->type eq 'versep') {
522 64         141 $el->type('verse');
523             }
524             }
525             # turn the direction switching into proper open/close blocks
526             {
527 632         931 my $current_direction = '';
  632         952  
528 632         2234 my %dirs = (
529             '<<<' => 'rtl',
530             '>>>' => 'ltr',
531             );
532 632         1082 foreach my $el (@parsed) {
533 13283 100       17071 if ($el->type eq 'bidimarker') {
534 45         110 $self->set_bidi_document;
535 45 50       82 my $dir = $dirs{$el->block} or die "Invalid bidimarker " . $el->block;
536 45 100 100     140 if ($current_direction and $current_direction ne $dir) {
537 15         40 $el->type('stopblock');
538 15         32 $el->block($current_direction);
539 15         36 $current_direction = '';
540             }
541             else {
542 30 100       612 warn "Direction already set to $current_direction!" if $current_direction;
543 30         96 $el->type('startblock');
544 30         65 $el->block($dir);
545 30         57 $current_direction = $dir;
546             }
547             }
548             }
549             }
550 632         1922 $self->_reset_list_parsing_output;
551             LISTP:
552 632         1451 while (@parsed) {
553 13283         14816 my $el = shift @parsed;
554 13283 100 100     19575 if ($el->type eq 'li' or $el->type eq 'dd') {
    100          
    100          
555 1448 100       2217 if ($self->_list_pile_count) {
    100          
556             # indentation is major, open a new level
557 1106 100       1584 if (_indentation_kinda_major($el, $self->_list_pile_last_element)) {
558 212         365 $self->_list_open_new_list_level($el);
559             }
560             else {
561             # close the lists until we get the right level
562 894         1645 $self->_list_close_until_indentation($el);
563 894 50       1474 if ($self->_list_pile_count) { # continue if open
564 894 100 100     1278 if ($self->_list_element_is_same_kind_as_in_list($el) and
565             $self->_list_element_is_a_progression($el)) {
566 780         1217 $self->_list_continuation($el);
567             }
568             else {
569 114         195 my $top = $self->_list_pile_last_element;
570 114   100     187 while ($self->_list_pile_count and
571             _indentation_kinda_equal($top, $self->_list_pile_last_element)) {
572             # empty the pile until the indentation drops.
573 228         402 $self->_close_list_level;
574             }
575             # and open a new level
576 114         215 $self->_list_open_new_list_level($el);
577             }
578             }
579             else { # if by chance, we emptied all, start anew.
580 0         0 $self->_list_open_new_list_level($el);
581             }
582             }
583             }
584             # no list pile, this is the first element
585             elsif ($self->_list_element_can_be_first($el)) {
586 320         678 $self->_list_open_new_list_level($el);
587             }
588             else {
589             # reparse and should become quote/center/right
590 22         56 $self->_append_element_to_list_parsing_output($self->_reparse_nolist($el));
591 22         124 next LISTP; # call next to avoid being mangled.
592             }
593 1426         2849 $el->become_regular;
594             }
595             elsif ($el->type eq 'regular') {
596             # the type is regular: It can only close or continue
597 3035         5042 $self->_list_close_until_indentation($el);
598 3035 100       3789 if ($self->_list_pile_count) {
599 185         300 $el->become_regular;
600             }
601             }
602             elsif ($el->type ne 'null') { # something else: close the pile
603 2154         2871 $self->_list_flush;
604             }
605 13261         17886 $self->_append_element_to_list_parsing_output($el);
606             }
607             # end of input, flush what we have.
608 632         1380 $self->_list_flush;
609              
610             # now we use parsed as output
611 632         1442 $self->_flush_current_footnote;
612 632         733 my @out;
613 632         834 my $elnum = 0;
614 632         803 while (@{$self->_list_parsing_output}) {
  18452         20228  
615 17820         16475 my $el = shift @{$self->_list_parsing_output};
  17820         18534  
616 17820         16394 $elnum++;
617 17820         28948 $el->_set_element_number($elnum);
618 17820 100 100     21940 if ($el->type eq 'footnote' or $el->type eq 'secondary_footnote') {
    100          
619 575         960 $self->_register_footnote($el);
620             }
621             elsif (my $fn_indent = $self->_current_footnote_indent) {
622 905 100 66     1264 if ($el->type eq 'null') {
    100 100        
623 661         939 push @parsed, $el;
624             }
625             elsif ($el->can_be_regular and
626             $el->indentation and
627             _kinda_equal($el->indentation, $fn_indent)) {
628 98         110 push @{$self->_current_footnote_stack}, Text::Amuse::Element->new($self->_parse_string("
\n")), $el;
  98         145  
629             }
630             else {
631 146         299 $self->_flush_current_footnote;
632 146         241 push @parsed, $el;
633             }
634             }
635             else {
636 16340         20059 push @parsed, $el;
637             }
638             }
639 632         1291 $self->_flush_current_footnote;
640              
641             # unroll the quote/center/right blocks
642 632         1254 while (@parsed) {
643 17147         17400 my $el = shift @parsed;
644 17147 100       23634 if ($el->can_be_regular) {
645 173         343 my $open = $self->_create_block(open => $el->block, $el->indentation);
646 173         395 my $close = $self->_create_block(close => $el->block, $el->indentation);
647 173         423 $el->block("");
648 173         365 push @out, $open, $el, $close;
649             }
650             else {
651 16974         24086 push @out, $el;
652             }
653             }
654              
655 632         824 my @pile;
656 632         1191 while (@out) {
657 17493         18686 my $el = shift @out;
658 17493 100 100     24433 if ($el->type eq 'startblock') {
    100          
    100          
659 2466         3791 push @pile, $self->_create_block(close => $el->block, $el->indentation);
660 2466         4572 $self->_debug("Pushing " . $el->block);
661 2466 50       4101 die "Uh?\n" unless $el->block;
662             }
663             elsif ($el->type eq 'stopblock') {
664 2465         2582 my $exp = pop @pile;
665 2465 100 66     4700 unless ($exp and $exp->block eq $el->block) {
666 25         51 warn "Couldn't retrieve " . $el->block . " from the pile\n";
667             # put it back
668 25 50       127 push @pile, $exp if $exp;
669             # so what to do here? just removed it
670 25         171 next;
671             }
672             }
673             elsif (@pile and $el->should_close_blocks) {
674              
675 31         51 my @carry_on;
676 31         89 my %close_rtl = map { $_ => 1 } (qw/h1 h2 h3 h4 h5 h6 newpage/);
  217         338  
677              
678 31         91 while (@pile) {
679 31         49 my $block = pop @pile;
680 31 100 100     66 if (($block->block eq 'rtl' || $block->block eq 'ltr') and !$close_rtl{$el->type}) {
      100        
681 15         37 push @carry_on, $block;
682             }
683             else {
684 16         38 warn "Forcing the closing of " . $block->block . "\n";
685 16         105 push @parsed, $block;
686             }
687             }
688 31         96 push @pile, reverse @carry_on;
689             }
690 17468         26110 push @parsed, $el;
691             }
692             # do we still have things into the pile?
693 632         1202 while (@pile) {
694 10         30 push @parsed, pop @pile;
695             }
696 632         1658 return \@parsed;
697             }
698              
699             =item elements
700              
701             Return the list of the elements which compose the body, once they have
702             properly parsed and packed. Footnotes are removed. (To get the
703             footnotes use the accessor below).
704              
705             =cut
706              
707             sub elements {
708 1018     1018 1 1571 my $self = shift;
709 1018 100       2223 unless (defined $self->{_parsed_document}) {
710 632         1329 $self->{_parsed_document} = $self->_parse_body;
711             }
712 1018 50       2206 if (defined wantarray) {
713 1018         1255 return @{$self->{_parsed_document}};
  1018         6021  
714             }
715             else {
716 0         0 return;
717             }
718             }
719              
720             =item get_footnote
721              
722             Accessor to the internal footnotes hash. You can access the footnote
723             with a numerical argument or even with a string like [123]
724              
725             =cut
726              
727             sub get_footnote {
728 1951     1951 1 2940 my ($self, $arg) = @_;
729 1951 100       3273 return undef unless $arg;
730 1950 50       8972 if ($arg =~ m/(\{[1-9][0-9]*\}|\[[1-9][0-9]*\])/) {
731 1950         4197 $arg = $1;
732             }
733             else {
734 0         0 return undef;
735             }
736 1950 100       3303 if (exists $self->_raw_footnotes->{$arg}) {
737 1674         2313 return $self->_raw_footnotes->{$arg};
738             }
739 276         824 else { return undef }
740             }
741              
742             sub _raw_footnotes {
743 4774     4774   4880 my $self = shift;
744 4774         12196 return $self->{_raw_footnotes};
745             }
746              
747             sub _current_footnote_stack {
748 2528     2528   4331 return shift->{_current_footnote_stack};
749             }
750              
751             sub _current_footnote_number {
752 2613     2613   2769 my $self = shift;
753 2613 100       3593 if (@_) {
754 2560         2921 $self->{_current_footnote_number} = shift;
755             }
756 2613         2910 return $self->{_current_footnote_number};
757             }
758              
759             sub _current_footnote_indent {
760 19805     19805   19393 my $self = shift;
761 19805 100       24023 if (@_) {
762 2560         2962 $self->{_current_footnote_indent} = shift;
763             }
764 19805         24104 return $self->{_current_footnote_indent};
765             }
766              
767              
768              
769             sub _parse_string {
770 19756     19756   25793 my ($self, $l, %opts) = @_;
771 19756 50       27370 die unless defined $l;
772 19756         34326 my %element = (
773             rawline => $l,
774             raw_without_anchors => $l,
775             );
776 19756 100       37143 if ($l =~ m/\A
777             (\s*)
778             (\#([A-Za-z][A-Za-z0-9-]+)\x{20}*)
779             (.*)
780             \z
781             /sx) {
782 420         980 $element{anchor} = $3;
783 420         869 $l = $1 . $4;
784 420         560 $element{raw_without_anchors} = $l;
785             }
786 19756         50094 my $blockre = qr{(
787             biblio |
788             play |
789             comment |
790             verse |
791             center |
792             right |
793             example |
794             quote
795             )}x;
796              
797             # null line is default, do nothing
798 19756 100       50648 if ($l =~ m/^[\n\t ]*$/s) {
799             # do nothing, already default
800 8027         10987 $element{removed} = $l;
801 8027         29234 return %element;
802             }
803 11729 100       42236 if ($l =~ m!^(<($blockre)>\s*)$!s) {
804 399         765 $element{type} = "startblock";
805 399         908 $element{removed} = $1;
806 399         673 $element{block} = $2;
807 399         1907 return %element;
808             }
809 11330 100       21411 if ($l =~ m/^((\<\<\<|\>\>\>)\s*)$/s) {
810             # here turn them into language switch
811 45         95 $element{type} = "bidimarker";
812 45         107 $element{removed} = $1;
813 45         93 $element{block} = $2;
814 45         247 return %element;
815             }
816 11285 100       16901 if ($l =~ m/^(
817             (
818             \<
819             (\/?)
820             \[
821             ([a-zA-Z-]+)
822             \]
823             \>
824             )
825             \s*
826             )$/sx) {
827 15         70 my ($all, $full, $close, $lang) = ($1, $2, $3, $4);
828 15 100       48 $element{type} = $close ? "stopblock" : "startblock";
829 15         33 $element{language} = $lang;
830 15         27 $element{removed} = $l;
831 15         54 $self->_add_to_other_language_codes($lang);
832 15         208 $element{block} = "languageswitch";
833 15         106 return %element;
834             }
835 11270 100       15303 if ($l =~ m/^(\{\{\{)\s*$/s) {
836 75         181 $element{type} = "startblock";
837 75         119 $element{removed} = $l;
838 75         151 $element{block} = 'example';
839 75         114 $element{style} = '{{{}}}';
840 75         413 return %element;
841             }
842 11195 100       15478 if ($l =~ m/^(\}\}\})\s*$/s) {
843 75         157 $element{type} = "stopblock";
844 75         116 $element{removed} = $l;
845 75         116 $element{block} = 'example';
846 75         102 $element{style} = '{{{}}}';
847 75         361 return %element;
848             }
849 11120 100       29251 if ($l =~ m!^(\s*)$!s) {
850 374         698 $element{type} = "stopblock";
851 374         850 $element{removed} = $1;
852 374         686 $element{block} = $2;
853 374         1700 return %element;
854             }
855             # headers
856 10746 100       18115 if ($l =~ m!^((\*{1,5}) )(.+)$!s) {
857 795         2450 $element{type} = "h" . length($2);
858 795         1476 $element{removed} = $1;
859 795         1317 $element{string} = $3;
860 795         4411 return %element;
861             }
862 9951 100       14574 if ($l =~ m/^(\> )(.*)/s) {
863 185         445 $element{string} = $2;
864 185         304 $element{removed} = $1;
865 185         281 $element{type} = "versep";
866 185         878 return %element;
867             }
868 9766 100       13191 if ($l =~ m/^(\>)$/s) {
869 35         69 $element{string} = "\n";
870 35         50 $element{removed} = ">";
871 35         51 $element{type} = "versep";
872 35         158 return %element;
873             }
874 9731 100 100     23107 if ($l =~ m/^(\x{20}+)/s and $l =~ m/\|/) {
875 547         863 $element{type} = "table";
876 547         716 $element{string} = $l;
877 547         2247 return %element;
878             }
879             # line starting with pipe, gh-markdown style
880 9184 100       13799 if ($l =~ m/^\|+(\x{20}+|\+)/s) {
881 30         46 $element{type} = "table";
882 30         43 $element{string} = $l;
883 30         115 return %element;
884             }
885 9154 100       12645 if ($l =~ m/^(\;)(\x{20}+(.*))?$/s) {
886 93         231 $element{removed} = $1;
887 93         162 $element{string} = $3;
888 93 100       198 unless (defined ($element{string})) {
889 12         18 $element{string} = '';
890             }
891 93         126 $element{type} = "inlinecomment";
892 93         455 return %element;
893             }
894 9061 100       13758 if ($l =~ m/^((\[([1-9][0-9]*)\])\x{20}+)(.+)$/s) {
895 469         830 $element{type} = "footnote";
896 469         1147 $element{removed} = $1;
897 469         778 $element{footnote_symbol} = $2;
898 469         793 $element{footnote_number} = $3;
899 469         754 $element{footnote_index} = $3;
900 469         898 $element{string} = $4;
901 469         3113 return %element;
902             }
903 8592 100       12384 if ($l =~ m/^((\{([1-9][0-9]*)\})\x{20}+)(.+)$/s) {
904 143         243 $element{type} = "secondary_footnote";
905 143         307 $element{removed} = $1;
906 143         217 $element{footnote_symbol} = $2;
907 143         232 $element{footnote_number} = $3;
908 143         302 $element{footnote_index} = 'b'. $3;
909 143         261 $element{string} = $4;
910 143         902 return %element;
911             }
912 8449 100       12118 if ($l =~ m/^((\x{20}{6,})((\*\x{20}?){5})\s*)$/s) {
913 44         76 $element{type} = "newpage";
914 44         92 $element{removed} = $2;
915 44         71 $element{string} = $3;
916 44         207 return %element;
917             }
918 8405 100       12645 if ($l =~ m/\A
919             (\x{20}+) # 1. initial space and indentation
920             (.+) # 2. desc title
921             (\x{20}+) # 3. space
922             (\:\:) # 4 . separator
923             ((\x{20}?)(.*)) # 5 6. space 7. text
924             \z
925             /xs) {
926 153         283 $element{block} = 'dl';
927 153         217 $element{type} = 'dd';
928 153         354 $element{string} = $7;
929 153         288 $element{attribute} = $2;
930 153         236 $element{attribute_type} = 'dt';
931 153         555 $element{removed} = $1 . $2 . $3 . $4 . $6;
932 153         317 $element{indentation} = length($1) + 2;
933 153         212 $element{start_list_index} = 1;
934 153         1048 return %element;
935             }
936 8252 100       13058 if (!$opts{nolist}) {
937 8230 100       14466 if ($l =~ m/^((\x{20}+)\-\x{20}+)(.*)/s) {
938 483         784 $element{type} = "li";
939 483         993 $element{removed} = $1;
940 483         862 $element{string} = $3;
941 483         664 $element{block} = "ul";
942 483         866 $element{indentation} = length($2) + 2;
943 483         590 $element{start_list_index} = 1;
944 483         2866 return %element;
945             }
946 7747 100       13376 if ($l =~ m/^((\x{20}+) # leading space and type $1
947             ( # the type $2
948             [0-9]+ |
949             [a-zA-Z] |
950             [ixvl]+ |
951             [IXVL]+
952             )
953             \. # single dot
954             \x{20}+) # space
955             (.*) # the string itself $4
956             /sx) {
957 1064         3395 my ($remove, $whitespace, $prefix, $text) = ($1, $2, $3, $4);
958              
959             # validate roman numbers, so we don't end up with random strings
960 1064 50       2064 if (my $list_index = $self->_get_start_list_index($prefix)) {
961 1064         1511 $element{type} = "li";
962 1064         1468 $element{removed} = $remove;
963 1064         1381 $element{string} = $text;
964 1064         1886 my $list_type = $self->_identify_list_type($prefix);
965 1064         2127 $element{indentation} = length($whitespace) + 2;
966 1064         1402 $element{block} = $list_type;
967 1064         1282 $element{start_list_index} = $list_index;
968 1064         6443 return %element;
969             }
970             }
971             }
972 6705 100       9544 if ($l =~ m/^(\x{20}{20,})([^ ].+)$/s) {
973 65         127 $element{block} = "right";
974 65         98 $element{type} = "regular";
975 65         155 $element{removed} = $1;
976 65         120 $element{string} = $2;
977 65         379 return %element;
978             }
979 6640 100       10134 if ($l =~ m/^(\x{20}{6,})([^ ].+)$/s) {
980 488         784 $element{block} = "center";
981 488         692 $element{type} = "regular";
982 488         993 $element{removed} = $1;
983 488         785 $element{string} = $2;
984 488         2461 return %element;
985             }
986 6152 100       9522 if ($l =~ m/^(\x{20}{2,})([^ ].+)$/s) {
987 966         1475 $element{block} = "quote";
988 966         1311 $element{type} = "regular";
989 966         2162 $element{removed} = $1;
990 966         1682 $element{string} = $2;
991 966         4981 return %element;
992             }
993             # anything else is regular
994 5186         6824 $element{type} = "regular";
995 5186         6430 $element{string} = $l;
996 5186         21257 return %element;
997             }
998              
999              
1000             sub _identify_list_type {
1001 1064     1064   1696 my ($self, $list_type) = @_;
1002 1064         1216 my $type;
1003 1064 100       4134 if ($list_type =~ m/\A[0-9]+\z/) {
    100          
    100          
    100          
    50          
1004 392         504 $type = "oln";
1005             } elsif ($list_type =~ m/\A[ixvl]+\z/) {
1006 166         264 $type = "oli";
1007             } elsif ($list_type =~ m/\A[IXVL]+\z/) {
1008 79         138 $type = "olI";
1009             } elsif ($list_type =~ m/\A[a-z]\z/) {
1010 274         422 $type = "ola";
1011             } elsif ($list_type =~ m/\A[A-Z]\z/) {
1012 153         225 $type = "olA";
1013             } else {
1014 0         0 die "$list_type unrecognized, fix your code\n";
1015             }
1016 1064         1403 return $type;
1017             }
1018              
1019             sub _get_start_list_index {
1020 1069     1069   1643 my ($self, $prefix) = @_;
1021 1069         2011 my $map = $self->_list_index_map;
1022 1069 100       2408 if (exists $map->{$prefix}) {
1023 1067         2414 return $map->{$prefix};
1024             }
1025             else {
1026 2         137 warn "$prefix doesn't map exactly to a list index!\n";
1027 2         13 return 0;
1028             }
1029             }
1030              
1031             sub _list_element_can_be_first {
1032 342     342   584 my ($self, $el) = @_;
1033             # every dd can be the first
1034 342 100       686 return 1 if $el->type eq 'dd';
1035 300 50       615 return unless $el->type eq 'li';
1036             # first element, can't be too indented
1037 300 100       612 if ($el->indentation > 8) {
1038 22         43 return 0;
1039             }
1040             else {
1041 278         526 return $el->start_list_index;
1042             }
1043             }
1044              
1045             sub _current_el {
1046 33603     33603   34008 my $self = shift;
1047 33603 100       43617 if (@_) {
1048 13971         16166 $self->{_current_el} = shift;
1049             }
1050 33603         37505 return $self->{_current_el};
1051             }
1052              
1053             sub _reparse_nolist {
1054 22     22   42 my ($self, $element) = @_;
1055 22         49 my %args = $self->_parse_string($element->rawline, nolist => 1);
1056 22         85 my $el = Text::Amuse::Element->new(%args);
1057 22 50       52 if ($el->type eq 'regular') {
1058 22         65 return $el;
1059             }
1060             else {
1061 0         0 die "Reparsing of " . $element->rawline . " led to " . $el->type;
1062             }
1063             }
1064              
1065             sub _construct_element {
1066 19632     19632   27964 my ($self, $line) = @_;
1067 19632         24257 my $current = $self->_current_el;
1068 19632         28827 my %args = $self->_parse_string($line);
1069 19632         56074 my $element = Text::Amuse::Element->new(%args);
1070              
1071 19632 100 100     45711 if ($current and ($current->type eq 'null' or $current->type eq 'startblock') and $current->anchors) {
      100        
      100        
1072             # previous element is null, carry on
1073 188         335 $current->move_anchors_to($element);
1074             }
1075 19632 100 100     31276 if ($element->type eq 'null' and
      100        
      100        
1076             $element->anchors and
1077             $current->type ne 'null' and
1078             $current->type ne 'example') {
1079             # incoming has anchors
1080 108         237 $element->move_anchors_to($current);
1081             # null element with anchors. it was fully merged, so return
1082 108         515 return;
1083             }
1084              
1085             # catch the examples, comments and the verse in bloks.
1086             # is greedy, and will stop only at another or
1087             # at the end of input. Same is true for verse and comments.
1088              
1089 19524         26442 foreach my $block (qw/example comment verse/) {
1090 54051 100 100     93079 if ($current && $current->type eq $block) {
    100          
1091 2347 100       3869 if ($element->is_stop_element($current)) {
1092 320         743 $self->_current_el(undef);
1093 320         659 return Text::Amuse::Element->new(type => 'null',
1094             removed => $element->rawline,
1095             anchors => [ $element->anchors ],
1096             rawline => $element->rawline);
1097             }
1098             else {
1099             # remove inlined comments from verse environments
1100 2027 100 100     2929 if ($current->type eq 'verse' and
1101             $element->type eq 'inlinecomment') {
1102             }
1103             else {
1104 2021         3326 $current->append($element);
1105             }
1106 2027         8288 return;
1107             }
1108             }
1109             elsif ($element->is_start_block($block)) {
1110 356         957 $current = Text::Amuse::Element->new(type => $block,
1111             style => $element->style,
1112             anchors => [ $element->anchors ],
1113             removed => $element->rawline,
1114             raw_without_anchors => $element->raw_without_anchors,
1115             rawline => $element->rawline);
1116 356         933 $self->_current_el($current);
1117 356         1742 return $current;
1118             }
1119             }
1120             # Pack the lines
1121 16821 100 100     34329 if ($current && $current->can_append($element)) {
1122             # print "Packing " . Dumper($element) . ' into ' . Dumper($current);
1123 4158         8546 $current->append($element);
1124 4158         18633 return;
1125             }
1126              
1127 12663         23431 $self->_current_el($element);
1128 12663         28302 return $element;
1129             }
1130              
1131             sub _create_block {
1132 7218     7218   10195 my ($self, $open_close, $block, $indentation) = @_;
1133 7218 50 33     15777 die unless $open_close && $block;
1134 7218         7120 my $type;
1135 7218 100       10732 if ($open_close eq 'open') {
    50          
1136 2376         2609 $type = 'startblock';
1137             }
1138             elsif ($open_close eq 'close') {
1139 4842         4962 $type = 'stopblock';
1140             }
1141             else {
1142 0         0 die "$open_close is not a valid op";
1143             }
1144 7218         7017 my $removed = '';
1145 7218 50       9646 if ($indentation) {
1146 7218         9451 $removed = ' ' x $indentation;
1147             }
1148 7218         12071 return Text::Amuse::Element->new(block => $block,
1149             type => $type,
1150             removed => $removed);
1151             }
1152              
1153             sub _opening_blocks {
1154 1426     1426   1961 my ($self, $el) = @_;
1155 1426         1525 my @out;
1156 1426 100 66     3419 if ($el->attribute && $el->attribute_type) {
1157 131         267 @out = ($self->_create_block(open => $el->attribute_type, $el->indentation),
1158             Text::Amuse::Element->new(type => 'dt', string => $el->attribute),
1159             $self->_create_block(close => $el->attribute_type, $el->indentation));
1160             }
1161 1426         2648 push @out, $self->_create_block(open => $el->type, $el->indentation);
1162 1426         2516 return @out;
1163             }
1164              
1165             sub _closing_blocks {
1166 1426     1426   1944 my ($self, $el) = @_;
1167 1426         2359 my @out = ($self->_create_block(close => $el->type, $el->indentation));
1168 1426         2079 return @out;
1169             }
1170             sub _opening_blocks_new_level {
1171 646     646   818 my ($self, $el) = @_;
1172 646         1158 my @out = ($self->_create_block(open => $el->block, $el->indentation),
1173             $self->_opening_blocks($el));
1174 646 50       1266 if (my $list_index = $el->start_list_index) {
1175 646         1345 $out[0]->start_list_index($list_index);
1176 646         1057 $out[1]->start_list_index($list_index);
1177             }
1178 646         1099 return @out;
1179             }
1180             sub _closing_blocks_new_level {
1181 646     646   857 my ($self, $el) = @_;
1182 646         1198 my @out = ($self->_create_block(close => $el->block, $el->indentation),
1183             $self->_closing_blocks($el));
1184 646         989 return @out;
1185             }
1186              
1187             sub _indentation_kinda_minor {
1188 1737     1737   2193 return _indentation_compare(@_) == IMINOR;
1189             }
1190              
1191             sub _indentation_kinda_major {
1192 1106     1106   1598 return _indentation_compare(@_) == IMAJOR;
1193             }
1194              
1195             sub _indentation_kinda_equal {
1196 271     271   345 return _indentation_compare(@_) == IEQUAL;
1197             }
1198              
1199             sub _kinda_equal {
1200 106     106   193 return _compare_tolerant(@_) == IEQUAL;
1201             }
1202              
1203             sub _indentation_compare {
1204 3114     3114   3452 my ($first, $second) = @_;
1205 3114         4340 my $one_indent = $first->indentation;
1206 3114         4080 my $two_indent = $second->indentation;
1207 3114         3610 return _compare_tolerant($one_indent, $two_indent);
1208             }
1209              
1210             sub _compare_tolerant {
1211 3232     3232   3533 my ($one_indent, $two_indent) = @_;
1212             # tolerance is zero if one of them is 0
1213 3232         3055 my $tolerance = 0;
1214 3232 100 100     6647 if ($one_indent && $two_indent) {
1215 2871         2897 $tolerance = 1;
1216             }
1217 3232         3246 my $diff = $one_indent - $two_indent;
1218 3232 100       4904 if ($diff - $tolerance > 0) {
    100          
1219 286         725 return IMAJOR;
1220             }
1221             elsif ($diff + $tolerance < 0) {
1222 767         1948 return IMINOR;
1223             }
1224             else {
1225 2179         5666 return IEQUAL;
1226             }
1227             }
1228              
1229              
1230             sub _list_element_is_same_kind_as_in_list {
1231 894     894   1067 my ($self, $el) = @_;
1232 894         1113 my $list = $self->_list_element_pile;
1233 894         1418 my $find = $el->block;
1234 894         1039 my $found = 0;
1235 894         1656 for (my $i = $#$list; $i >= 0; $i--) {
1236 1788         2620 my $block = $list->[$i]->block;
1237 1788 100 100     4428 next if ($block eq 'li' or $block eq 'dd');
1238 894 100       1407 if ($block eq $find) {
1239 824         820 $found = 1;
1240             }
1241 894         961 last;
1242             }
1243 894         2038 return $found;
1244             }
1245              
1246             sub _register_footnote {
1247 575     575   812 my ($self, $el) = @_;
1248 575         982 my $fn_num = $el->footnote_symbol;
1249 575 50       934 if (defined $fn_num) {
1250 575 50       914 if ($self->_raw_footnotes->{$fn_num}) {
1251 0         0 warn "Overwriting footnote number $fn_num!\n";
1252             }
1253 575         1028 $self->_flush_current_footnote;
1254 575         1070 $self->_current_footnote_indent($el->indentation);
1255 575         943 $self->_current_footnote_number($fn_num);
1256 575         761 $self->_raw_footnotes->{$fn_num} = $el;
1257             }
1258             else {
1259 0         0 die "Something is wrong here! <" . $el->removed . ">"
1260             . $el->string . "!";
1261             }
1262             }
1263              
1264             sub _flush_current_footnote {
1265 1985     1985   2275 my $self = shift;
1266 1985 100       1987 if (@{$self->_current_footnote_stack}) {
  1985         2877  
1267 53         98 my $footnote = $self->get_footnote($self->_current_footnote_number);
1268 53 50       121 die "Missing current footnote to append " . Dumper($self->_current_footnote_stack) unless $footnote;
1269 53         69 while (@{$self->_current_footnote_stack}) {
  249         338  
1270 196         203 my $append = shift @{$self->_current_footnote_stack};
  196         514  
1271 196         326 $footnote->append($append);
1272             }
1273             }
1274 1985         3771 $self->_current_footnote_indent(0);
1275 1985         2974 $self->_current_footnote_number(undef)
1276             }
1277              
1278             # list parsing
1279              
1280             sub _list_element_pile {
1281 22722     22722   38014 return shift->{_list_element_pile};
1282             }
1283              
1284             sub _list_parsing_output {
1285 52273     52273   82271 return shift->{_list_parsing_output};
1286             }
1287              
1288             sub _list_pile_count {
1289 13498     13498   12735 my $self = shift;
1290 13498         11982 return scalar(@{$self->_list_element_pile});
  13498         14845  
1291             }
1292              
1293             sub _list_pile_last_element {
1294 4832     4832   4817 my $self = shift;
1295 4832         5309 return $self->_list_element_pile->[-1];
1296             }
1297              
1298             sub _reset_list_parsing_output {
1299 632     632   899 my $self = shift;
1300 632         1365 $self->{_list_parsing_output} = [];
1301             }
1302              
1303             sub _list_open_new_list_level {
1304 646     646   940 my ($self, $el) = @_;
1305 646         719 push @{$self->_list_parsing_output}, $self->_opening_blocks_new_level($el);
  646         836  
1306 646         1217 my @pile = $self->_closing_blocks_new_level($el);
1307 646 50       1095 if (my $list_index = $el->start_list_index) {
1308 646         1269 $_->start_list_index($list_index) for @pile;
1309             }
1310 646         788 push @{$self->_list_element_pile}, @pile;
  646         898  
1311             }
1312              
1313             sub _list_continuation {
1314 780     780   1133 my ($self, $el) = @_;
1315 780         1069 my $current = $self->_list_pile_last_element->start_list_index + 1;
1316 780         866 push @{$self->_list_parsing_output}, pop @{$self->_list_element_pile}, $self->_opening_blocks($el);
  780         1000  
  780         925  
1317 780         1444 my @pile = $self->_closing_blocks($el);
1318 780 50       1323 if (my $list_index = $el->start_list_index) {
1319 780         1418 $_->start_list_index($current) for @pile;
1320             }
1321 780         838 push @{$self->_list_element_pile}, @pile;
  780         1054  
1322             }
1323              
1324             sub _close_list_level {
1325 1292     1292   1328 my $self = shift;
1326 1292         1230 push @{$self->_list_parsing_output}, pop @{$self->_list_element_pile};
  1292         1499  
  1292         1431  
1327             }
1328              
1329             sub _append_element_to_list_parsing_output {
1330 13283     13283   14898 my ($self, $el) = @_;
1331 13283         12284 push @{$self->_list_parsing_output}, $el;
  13283         15150  
1332             }
1333              
1334             sub _list_close_until_indentation {
1335 3929     3929   4585 my ($self, $el) = @_;
1336 3929   100     5148 while ($self->_list_pile_count and
1337             _indentation_kinda_minor($el, $self->_list_pile_last_element)) {
1338 658         1021 $self->_close_list_level;
1339             }
1340             }
1341              
1342             sub _list_flush {
1343 2786     2786   2979 my $self = shift;
1344 2786         3520 while ($self->_list_pile_count) {
1345 406         532 $self->_close_list_level;
1346             }
1347             }
1348              
1349             sub _list_element_is_a_progression {
1350 824     824   1121 my ($self, $el) = @_;
1351             # not defined, not needed.
1352 824         1080 my $last = $self->_list_pile_last_element->start_list_index;
1353 824         1275 my $current = $el->start_list_index;
1354             # no index from one or another, we can't compare
1355 824 50 33     3041 if (!$last or !$current) {
    100 66        
1356 0         0 return 1;
1357             }
1358             elsif ($last > 0 and $current > 1) {
1359 317 100       560 if (($current - $last) == 1) {
1360 273         572 return 1;
1361             }
1362             else {
1363 44         112 return 0;
1364             }
1365             }
1366             else {
1367 507         1072 return 1;
1368             }
1369             }
1370              
1371             =back
1372              
1373             =cut
1374              
1375             1;