File Coverage

lib/Pod/PseudoPod/DOM.pm
Criterion Covered Total %
statement 267 270 98.8
branch 34 40 85.0
condition 5 5 100.0
subroutine 60 61 98.3
pod 2 39 5.1
total 368 415 88.6


line stmt bran cond sub pod time code
1             package Pod::PseudoPod::DOM;
2             # ABSTRACT: an object model for Pod::PseudoPod documents
3              
4 26     26   1914367 use strict;
  26         283  
  26         753  
5 26     26   150 use warnings;
  26         50  
  26         735  
6              
7 26     26   12153 use parent 'Pod::PseudoPod';
  26         7693  
  26         142  
8              
9 26     26   988878 use Class::Load;
  26         455682  
  26         1197  
10 26     26   239 use File::Basename;
  26         56  
  26         3207  
11 26     26   13759 use Pod::PseudoPod::DOM::Elements;
  26         135  
  26         45249  
12              
13             sub new
14             {
15 61     61 1 149433 my ($class, %args) = @_;
16 61         267 my $role = delete $args{formatter_role};
17 61         522 my $self = $class->SUPER::new(@_);
18 61         4446 $self->{class_registry} = {};
19 61         277 $self->{formatter_role} = $role;
20 61   100     472 $self->{formatter_args} = $args{formatter_args} || {};
21 61         212 $self->{filename} = $args{filename};
22             ($self->{basefile}) = $self->{filename} =~ m!/?([^/]+)$!
23 61 100       913 if $self->{filename};
24              
25 61         398 Class::Load::load_class( $role );
26 61         2876 $self->accept_targets( $role->accept_targets );
27 61         2341 $self->accept_targets_as_text(
28             qw( author blockquote comment caution
29             editor epigraph example figure important listing literal note
30             production programlisting screen sidebar table tip warning )
31             );
32              
33 61         5642 $self->nbsp_for_S(1);
34 61         866 $self->codes_in_verbatim(1);
35              
36 61         567 return $self;
37             }
38              
39             sub add_link
40             {
41 548     548 0 1835 my ($self, $type, $link) = @_;
42 548         1100 push @{ $self->{Document}->$type }, $link;
  548         20285  
43             }
44              
45             sub parse_string_document
46             {
47 58     58 1 495 my ($self, $document, %args) = @_;
48              
49 58 100       374 if (my $environments = delete $args{emit_environments})
50             {
51 5         16 $self->accept_targets( keys %{ $environments } );
  5         25  
52 5         69 $self->{formatter_args}{emit_environments} = $environments;
53             }
54              
55 58         361 return $self->SUPER::parse_string_document( $document );
56             }
57              
58             sub _treat_Es
59             {
60 441     441   310693 my $self = shift;
61 441         1364 my $formatter = $self->{formatter_role};
62 441 50       5387 return if $formatter->can( 'encode_E_contents' );
63 0         0 return $self->SUPER::_treat_Es( @_ );
64             }
65              
66             sub get_document
67             {
68 174     174 0 9673 my $self = shift;
69 174         2750 return $self->{Document};
70             }
71              
72             sub make
73             {
74 8757     8757 0 26350 my ($self, $type, @args) = @_;
75 8757         18063 my $registry = $self->{class_registry};
76 8757         21104 my $class = $registry->{$type};
77              
78 8757 100       21699 unless ($class)
79             {
80 698         2025 my $name = 'Pod::PseudoPod::DOM::Element::' . $type;
81             $class = $registry->{$type}
82 698         11204 = $name->with_traits( $self->{formatter_role} );
83             }
84              
85 8757         8950414 return $class->new( %{ $self->{formatter_args} }, @args );
  8757         52278  
86             }
87              
88             sub start_Document
89             {
90 58     58 0 16064 my $self = shift;
91              
92             $self->{active_elements} =
93             [
94             $self->{Document} = $self->make( Document => type => 'document',
95             filename => $self->{filename} )
96 58         375 ];
97             }
98              
99             sub end_Document
100             {
101 58     58 0 3240 my $self = shift;
102 58         228 $self->{active_elements} = [];
103 58         327 $self->finish_document;
104             }
105              
106             sub finish_document
107             {
108 58     58 0 149 my $self = shift;
109 58         289 $self->reparent_anchors;
110 58         313 $self->collapse_index_entries;
111             }
112              
113             sub reparent_anchors
114             {
115 58     58 0 201 my $self = shift;
116 58         335 my $document = $self->get_document;
117 58         2287 my $kids = $document->children;
118              
119 58         211 my $anchor_parent;
120             my @spliced_kids;
121              
122 58         241 for my $child (@$kids) {
123 1343 100       4851 if ($child->can_contain_anchor) {
124 225         382 $anchor_parent = $child;
125 225         442 push @spliced_kids, $child;
126 225         447 next;
127             }
128              
129             # an anchor is the only child of a top-level paragraph
130 1118 100       33276 if ($child->type eq 'paragraph') {
131 662         20676 my $grandkids = $child->children;
132 662 100       1758 if (@$grandkids != 1) {
133 228         463 push @spliced_kids, $child;
134 228         494 next;
135             }
136              
137 434 100       13583 if ($grandkids->[0]->type ne 'anchor') {
138 361         692 push @spliced_kids, $child;
139 361         832 next;
140             }
141              
142 73         366 $child = $grandkids->[0];
143             }
144              
145 529 100 100     21497 if ($anchor_parent && $child->type eq 'anchor') {
146 73         2616 $anchor_parent->anchor( $child );
147 73         158 undef $anchor_parent;
148 73         188 next;
149             }
150              
151 456         1114 push @spliced_kids, $child;
152             }
153              
154 58         580 @$kids = @spliced_kids;
155             }
156              
157             sub collapse_index_entries
158             {
159 58     58 0 177 my $self = shift;
160 58         214 my $document = $self->get_document;
161 58         2025 my $kids = $document->children;
162 58         196 my @saved_kids;
163             my @splice_kids;
164              
165             # merge index entries into the next paragraph with visible text
166 58         219 for my $kid (@$kids)
167             {
168 1270 100       37173 if ($kid->type eq 'paragraph')
169             {
170 589 100       1752 unless ($kid->has_visible_kids)
171             {
172 65         182 push @splice_kids, @{ $kid->children };
  65         2206  
173 65         219 next;
174             }
175 524         934 unshift @{ $kid->children }, splice @splice_kids;
  524         16363  
176             }
177              
178 1205         2677 push @saved_kids, $kid;
179             }
180              
181 58         613 @$kids = @saved_kids;
182             }
183              
184             sub start_Verbatim
185             {
186 75     75 0 38126 my $self = shift;
187 75         394 $self->push_element( 'Paragraph', type => 'verbatim' );
188             }
189              
190             sub end_Verbatim
191             {
192 75     75 0 1319 my $self = shift;
193 75         345 $self->reset_to_item( 'Paragraph', type => 'verbatim' );
194             }
195              
196             sub reset_to_document
197             {
198 0     0 0 0 my $self = shift;
199 0         0 $self->{active_elements} = [ $self->{Document} ];
200             }
201              
202             sub push_element
203             {
204 4136     4136 0 8126 my $self = shift;
205 4136         11603 my $child = $self->make( @_ );
206              
207 4136         3261026 $self->{active_elements}[-1]->add_children( $child );
208 4136         8614 push @{ $self->{active_elements } }, $child;
  4136         10985  
209              
210 4136         15111 return $child;
211             }
212              
213             sub push_heading_element
214             {
215 226     226 0 548 my $self = shift;
216 226         860 my $child = $self->push_element( @_ );
217              
218 226         1193 $self->{latest_heading} = $child;
219             }
220              
221             sub push_link_element
222             {
223 450     450 0 2151 my ($self, $class, %args) = @_;
224 450         1193 my $heading = $self->{latest_heading};
225 450         2462 my $child = $self->push_element(
226             $class, heading => $heading, %args
227             );
228              
229 450         2327 $self->add_link( $args{type} => $child );
230             }
231              
232             sub add_element
233             {
234 4488     4488 0 7958 my $self = shift;
235 4488         12322 my $child = $self->make( @_ );
236 4488         3559092 $self->{active_elements}[-1]->add( $child );
237             }
238              
239             sub start_new_element
240             {
241 75     75 0 202 my $self = shift;
242 75         167 push @{ $self->{active_elements} }, $self->make( @_ );
  75         356  
243             }
244              
245             sub reset_to_item
246             {
247 4211     4211 0 14022 my ($self, $type, %attributes) = @_;
248 4211         9082 my $elements = $self->{active_elements};
249 4211         10120 my $class = 'Pod::PseudoPod::DOM::Element::' . $type;
250              
251 4211         13529 while (@$elements)
252             {
253 4211         8202 my $element = pop @$elements;
254 4211 50       18588 next unless $element->isa( $class );
255              
256             # reset iterator
257 4211         9093 my $attrs = keys %attributes;
258              
259 4211         16507 while (my ($attribute, $value) = each %attributes)
260             {
261 3737 50       132003 $attrs-- if $element->$attribute() eq $value;
262             }
263              
264 4211 50       22448 return $element unless $attrs;
265             }
266             }
267              
268             sub start_Z
269             {
270 98     98 0 2914 my $self = shift;
271             my $child = $self->push_element( 'Text::Anchor',
272             type => 'anchor',
273             link => $self->{basefile},
274 98         540 heading => $self->{latest_heading} );
275 98         548 $self->add_link( anchor => $child );
276             }
277              
278             sub end_Z
279             {
280 98     98 0 1931 my $self = shift;
281 98         412 $self->reset_to_item( 'Text::Anchor', type => 'anchor' );
282             }
283              
284             BEGIN
285             {
286 26     26   161 for my $heading ( 0 .. 4 )
287             {
288             my $start_meth = sub
289             {
290 226     226   104667 my $self = shift;
291             $self->push_heading_element( Heading =>
292             level => $heading,
293             type => 'header',
294             filename => $self->{basefile},
295 226         1405 );
296 130         650 };
297              
298             my $end_meth = sub
299             {
300 226     226   4390 my $self = shift;
301 226         955 $self->reset_to_item( Heading => level => $heading );
302 130         443 };
303              
304             do
305 130         214 {
306 26     26   315 no strict 'refs';
  26         69  
  26         4917  
307 130         178 *{ 'start_head' . $heading } = $start_meth;
  130         824  
308 130         227 *{ 'end_head' . $heading } = $end_meth;
  130         581  
309             };
310             }
311              
312 26         167 my %link_types =
313             (
314             X => 'index',
315             L => 'link',
316             A => 'link',
317             );
318              
319 26         269 while (my ($tag, $type) = each %link_types)
320             {
321             my $start_meth = sub
322             {
323 450     450   12470 my $self = shift;
324             $self->push_link_element( 'Text::' . ucfirst $type,
325 450         2972 type => $type, link => $self->{basefile} );
326 78         443 };
327              
328             my $end_meth = sub
329             {
330 450     450   8065 my $self = shift;
331 450         2541 $self->reset_to_item( 'Text::' . ucfirst $type, type => $type );
332 78         281 };
333              
334             do
335 78         153 {
336 26     26   202 no strict 'refs';
  26         66  
  26         5452  
337 78         142 *{ 'start_' . $tag } = $start_meth;
  78         395  
338 78         171 *{ 'end_' . $tag } = $end_meth;
  78         471  
339             };
340             }
341              
342 26         376 my %text_types =
343             (
344             I => 'Italics',
345             C => 'Code',
346             N => 'Footnote',
347             U => 'URL',
348             G => 'Superscript',
349             H => 'Subscript',
350             B => 'Bold',
351             R => 'Italics',
352             F => 'File',
353             E => 'Character',
354             );
355              
356 26         207 while (my ($tag, $type) = each %text_types)
357             {
358             my $start_meth = sub
359             {
360 976     976   25902 my $self = shift;
361 976         5346 $self->push_element( 'Text::' . $type, type => lc $type );
362 260         1026 };
363              
364             my $end_meth = sub
365             {
366 976     976   16610 my $self = shift;
367 976         5542 $self->reset_to_item( 'Text::' . $type, type => lc $type );
368 260         1126 };
369              
370             do
371 260         432 {
372 26     26   217 no strict 'refs';
  26         58  
  26         6988  
373 260         360 *{ 'start_' . $tag } = $start_meth;
  260         1150  
374 260         439 *{ 'end_' . $tag } = $end_meth;
  260         1472  
375             };
376             }
377              
378 26         128 for my $list_type (qw( bullet text block number ))
379             {
380             my $start_list_meth = sub
381             {
382 197     197   66659 my $self = shift;
383 197         1097 $self->push_element( 'List', type => $list_type . '_list' );
384 104         383 };
385              
386             my $end_list_meth = sub
387             {
388 197     197   20755 my $self = shift;
389 197         859 my $list = $self->reset_to_item( 'List',
390             type => $list_type . '_list'
391             );
392 197 50       1775 $list->fixup_list if $list;
393 104         356 };
394              
395             my $start_item_meth = sub
396             {
397 562     562   203562 my ($self, $args) = @_;
398             my @marker = $args->{number}
399             ? (marker => $args->{number})
400 562 100       2346 : ();
401              
402 562         2347 $self->push_element( 'ListItem',
403             type => $list_type . '_item', @marker
404             );
405 104         335 };
406              
407             my $end_item_meth = sub
408             {
409 562     562   9900 my $self = shift;
410 562         2615 $self->reset_to_item( 'ListItem', type => $list_type . '_item' );
411 104         350 };
412              
413             do
414 104         175 {
415 26     26   213 no strict 'refs';
  26         56  
  26         1959  
416 104         151 *{ 'start_over_' . $list_type } = $start_list_meth;
  104         579  
417 104         223 *{ 'end_over_' . $list_type } = $end_list_meth;
  104         392  
418 104         203 *{ 'start_item_' . $list_type } = $start_item_meth;
  104         363  
419 104         215 *{ 'end_item_' . $list_type } = $end_item_meth;
  104         25014  
420             };
421             }
422             }
423              
424             sub handle_text
425             {
426 4488     4488 0 96986 my $self = shift;
427 4488         15263 $self->add_element( 'Text::Plain' => type => 'plaintext', content => $_[0]);
428             }
429              
430             sub start_Para
431             {
432 1078     1078 0 229263 my $self = shift;
433 1078         3952 $self->push_element( Paragraph => type => 'paragraph' );
434             }
435              
436             sub end_Para
437             {
438 1153     1153 0 18274 my $self = shift;
439 1153         4576 $self->reset_to_item( Paragraph => type => 'paragraph' );
440             }
441              
442             sub start_for
443             {
444 161     161 0 49088 my ($self, $flags) = @_;
445 161 100       531 do { $flags->{$_} = '' unless defined $flags->{$_} } for qw( title target );
  322         1201  
446              
447             $self->push_element( Block =>
448             type => 'block',
449             title => $flags->{title},
450 161         814 target => $flags->{target} );
451             }
452              
453             sub end_for
454             {
455 161     161 0 22571 my $self = shift;
456 161         564 my $block = $self->reset_to_item( 'Block' );
457              
458 161 100       6457 if (my $title = $block->title)
459             {
460 51         262 $block->title( $self->fix_title( $title ) );
461             }
462             }
463              
464             sub start_sidebar
465             {
466 49     49 0 11062 my ($self, $flags) = @_;
467 49         263 $self->push_element( Block => type => 'sidebar', title => $flags->{title} );
468             }
469              
470             sub end_sidebar
471             {
472 49     49 0 9477 my $self = shift;
473 49         171 $self->reset_to_item( 'Block' );
474             }
475              
476             sub start_table
477             {
478 24     24 0 5050 my ($self, $flags) = @_;
479 24         155 $self->push_element( Table => 'type' => 'table', title => $flags->{title} );
480             }
481              
482             sub end_table
483             {
484 24     24 0 909 my $self = shift;
485 24         100 my $table = $self->reset_to_item( 'Table' );
486              
487 24 50       1281 if (my $title = $table->title)
488             {
489 24         170 $table->title( $self->fix_title( $title ) );
490             }
491              
492 24         259 $table->fixup;
493             }
494              
495             sub fix_title
496             {
497 75     75 0 301 my ($self, $title) = @_;
498 75         403 my $title_elem = $self->start_new_element(
499             Paragraph => type => 'paragraph' );
500 75         53826 my $tag_regex = qr/([IC]<+\s*.+?\s*>+)/;
501 75         198 my @parts;
502              
503 75         876 for my $part (split /$tag_regex/, $title)
504             {
505 195 100       1211 if ($part =~ /$tag_regex/)
506             {
507 72         531 my ($type, $content) = $part =~ /^([IC])<+\s*(.+?)\s*>+/;
508 72         278 my $start = "start_$type";
509 72         226 my $end = "end_$type";
510 72         384 $self->$start;
511 72         333 $self->handle_text( $content );
512 72         369 $self->$end;
513             }
514             else
515             {
516 123         475 $self->handle_text( $part );
517             }
518             }
519              
520 75         393 return $self->end_Para;
521             }
522              
523             sub start_headrow
524             {
525 24     24 0 5874 my $self = shift;
526 24         122 $self->push_element( TableRow => 'type' => 'headrow' );
527             }
528              
529             sub end_headrow
530             {
531 24     24 0 528 my $self = shift;
532 24         137 $self->reset_to_item( 'TableRow' );
533             }
534              
535             sub start_row
536             {
537 48     48 0 5139 my $self = shift;
538 48         249 $self->push_element( TableRow => 'type' => 'row' );
539             }
540              
541             sub end_row
542             {
543 48     48 0 8757 my $self = shift;
544 48         174 $self->reset_to_item( 'TableRow' );
545             }
546              
547             sub start_cell
548             {
549 144     144 0 28750 my $self = shift;
550 144         520 $self->push_element( TableCell => 'type' => 'cell' );
551             }
552              
553             sub end_cell
554             {
555 144     144 0 2479 my $self = shift;
556 144         483 $self->reset_to_item( 'TableCell' );
557             }
558              
559             sub start_figure
560             {
561 24     24 0 5279 my ($self, $flags) = @_;
562             $self->push_element( Figure => type => 'figure',
563 24         136 caption => $flags->{title} );
564             }
565              
566             sub end_figure
567             {
568 24     24 0 5142 my $self = shift;
569 24         116 $self->reset_to_item( 'Figure' )->fixup_figure;
570             }
571              
572             1;
573              
574             __END__
575              
576             =pod
577              
578             =encoding UTF-8
579              
580             =head1 NAME
581              
582             Pod::PseudoPod::DOM - an object model for Pod::PseudoPod documents
583              
584             =head1 VERSION
585              
586             version 1.20210620.2040
587              
588             =head1 AUTHOR
589              
590             chromatic <chromatic@wgz.org>
591              
592             =head1 COPYRIGHT AND LICENSE
593              
594             This software is copyright (c) 2021 by chromatic.
595              
596             This is free software; you can redistribute it and/or modify it under
597             the same terms as the Perl 5 programming language system itself.
598              
599             =cut