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   1904311 use strict;
  26         287  
  26         756  
5 26     26   134 use warnings;
  26         51  
  26         730  
6              
7 26     26   12533 use parent 'Pod::PseudoPod';
  26         7567  
  26         142  
8              
9 26     26   992085 use Class::Load;
  26         459880  
  26         1189  
10 26     26   221 use File::Basename;
  26         56  
  26         3254  
11 26     26   13876 use Pod::PseudoPod::DOM::Elements;
  26         142  
  26         46876  
12              
13             sub new
14             {
15 61     61 1 145442 my ($class, %args) = @_;
16 61         258 my $role = delete $args{formatter_role};
17 61         506 my $self = $class->SUPER::new(@_);
18 61         4319 $self->{class_registry} = {};
19 61         195 $self->{formatter_role} = $role;
20 61   100     473 $self->{formatter_args} = $args{formatter_args} || {};
21 61         216 $self->{filename} = $args{filename};
22             ($self->{basefile}) = $self->{filename} =~ m!/?([^/]+)$!
23 61 100       1005 if $self->{filename};
24              
25 61         447 Class::Load::load_class( $role );
26 61         3040 $self->accept_targets( $role->accept_targets );
27 61         2268 $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         5505 $self->nbsp_for_S(1);
34 61         873 $self->codes_in_verbatim(1);
35              
36 61         538 return $self;
37             }
38              
39             sub add_link
40             {
41 548     548 0 1728 my ($self, $type, $link) = @_;
42 548         1079 push @{ $self->{Document}->$type }, $link;
  548         19270  
43             }
44              
45             sub parse_string_document
46             {
47 58     58 1 456 my ($self, $document, %args) = @_;
48              
49 58 100       377 if (my $environments = delete $args{emit_environments})
50             {
51 5         13 $self->accept_targets( keys %{ $environments } );
  5         27  
52 5         66 $self->{formatter_args}{emit_environments} = $environments;
53             }
54              
55 58         352 return $self->SUPER::parse_string_document( $document );
56             }
57              
58             sub _treat_Es
59             {
60 441     441   300055 my $self = shift;
61 441         1322 my $formatter = $self->{formatter_role};
62 441 50       4822 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 9142 my $self = shift;
69 174         2826 return $self->{Document};
70             }
71              
72             sub make
73             {
74 8757     8757 0 25237 my ($self, $type, @args) = @_;
75 8757         17338 my $registry = $self->{class_registry};
76 8757         20745 my $class = $registry->{$type};
77              
78 8757 100       20728 unless ($class)
79             {
80 698         1986 my $name = 'Pod::PseudoPod::DOM::Element::' . $type;
81             $class = $registry->{$type}
82 698         10863 = $name->with_traits( $self->{formatter_role} );
83             }
84              
85 8757         8771011 return $class->new( %{ $self->{formatter_args} }, @args );
  8757         50421  
86             }
87              
88             sub start_Document
89             {
90 58     58 0 15495 my $self = shift;
91              
92             $self->{active_elements} =
93             [
94             $self->{Document} = $self->make( Document => type => 'document',
95             filename => $self->{filename} )
96 58         318 ];
97             }
98              
99             sub end_Document
100             {
101 58     58 0 3189 my $self = shift;
102 58         598 $self->{active_elements} = [];
103 58         285 $self->finish_document;
104             }
105              
106             sub finish_document
107             {
108 58     58 0 158 my $self = shift;
109 58         382 $self->reparent_anchors;
110 58         292 $self->collapse_index_entries;
111             }
112              
113             sub reparent_anchors
114             {
115 58     58 0 204 my $self = shift;
116 58         344 my $document = $self->get_document;
117 58         2333 my $kids = $document->children;
118              
119 58         238 my $anchor_parent;
120             my @spliced_kids;
121              
122 58         253 for my $child (@$kids) {
123 1343 100       4714 if ($child->can_contain_anchor) {
124 225         405 $anchor_parent = $child;
125 225         417 push @spliced_kids, $child;
126 225         408 next;
127             }
128              
129             # an anchor is the only child of a top-level paragraph
130 1118 100       32349 if ($child->type eq 'paragraph') {
131 662         19906 my $grandkids = $child->children;
132 662 100       2014 if (@$grandkids != 1) {
133 228         491 push @spliced_kids, $child;
134 228         496 next;
135             }
136              
137 434 100       12813 if ($grandkids->[0]->type ne 'anchor') {
138 361         702 push @spliced_kids, $child;
139 361         768 next;
140             }
141              
142 73         370 $child = $grandkids->[0];
143             }
144              
145 529 100 100     21386 if ($anchor_parent && $child->type eq 'anchor') {
146 73         2538 $anchor_parent->anchor( $child );
147 73         164 undef $anchor_parent;
148 73         220 next;
149             }
150              
151 456         1130 push @spliced_kids, $child;
152             }
153              
154 58         555 @$kids = @spliced_kids;
155             }
156              
157             sub collapse_index_entries
158             {
159 58     58 0 148 my $self = shift;
160 58         237 my $document = $self->get_document;
161 58         2053 my $kids = $document->children;
162 58         184 my @saved_kids;
163             my @splice_kids;
164              
165             # merge index entries into the next paragraph with visible text
166 58         218 for my $kid (@$kids)
167             {
168 1270 100       36819 if ($kid->type eq 'paragraph')
169             {
170 589 100       1777 unless ($kid->has_visible_kids)
171             {
172 65         150 push @splice_kids, @{ $kid->children };
  65         2069  
173 65         208 next;
174             }
175 524         954 unshift @{ $kid->children }, splice @splice_kids;
  524         15970  
176             }
177              
178 1205         2581 push @saved_kids, $kid;
179             }
180              
181 58         531 @$kids = @saved_kids;
182             }
183              
184             sub start_Verbatim
185             {
186 75     75 0 36458 my $self = shift;
187 75         275 $self->push_element( 'Paragraph', type => 'verbatim' );
188             }
189              
190             sub end_Verbatim
191             {
192 75     75 0 1370 my $self = shift;
193 75         279 $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 7961 my $self = shift;
205 4136         11361 my $child = $self->make( @_ );
206              
207 4136         3179667 $self->{active_elements}[-1]->add_children( $child );
208 4136         8463 push @{ $self->{active_elements } }, $child;
  4136         10443  
209              
210 4136         14372 return $child;
211             }
212              
213             sub push_heading_element
214             {
215 226     226 0 517 my $self = shift;
216 226         773 my $child = $self->push_element( @_ );
217              
218 226         1127 $self->{latest_heading} = $child;
219             }
220              
221             sub push_link_element
222             {
223 450     450 0 2203 my ($self, $class, %args) = @_;
224 450         1157 my $heading = $self->{latest_heading};
225 450         2316 my $child = $self->push_element(
226             $class, heading => $heading, %args
227             );
228              
229 450         2137 $self->add_link( $args{type} => $child );
230             }
231              
232             sub add_element
233             {
234 4488     4488 0 7518 my $self = shift;
235 4488         11561 my $child = $self->make( @_ );
236 4488         3483550 $self->{active_elements}[-1]->add( $child );
237             }
238              
239             sub start_new_element
240             {
241 75     75 0 200 my $self = shift;
242 75         189 push @{ $self->{active_elements} }, $self->make( @_ );
  75         360  
243             }
244              
245             sub reset_to_item
246             {
247 4211     4211 0 13427 my ($self, $type, %attributes) = @_;
248 4211         8382 my $elements = $self->{active_elements};
249 4211         9697 my $class = 'Pod::PseudoPod::DOM::Element::' . $type;
250              
251 4211         12934 while (@$elements)
252             {
253 4211         7927 my $element = pop @$elements;
254 4211 50       17302 next unless $element->isa( $class );
255              
256             # reset iterator
257 4211         8616 my $attrs = keys %attributes;
258              
259 4211         16878 while (my ($attribute, $value) = each %attributes)
260             {
261 3737 50       128142 $attrs-- if $element->$attribute() eq $value;
262             }
263              
264 4211 50       21495 return $element unless $attrs;
265             }
266             }
267              
268             sub start_Z
269             {
270 98     98 0 2835 my $self = shift;
271             my $child = $self->push_element( 'Text::Anchor',
272             type => 'anchor',
273             link => $self->{basefile},
274 98         524 heading => $self->{latest_heading} );
275 98         488 $self->add_link( anchor => $child );
276             }
277              
278             sub end_Z
279             {
280 98     98 0 1740 my $self = shift;
281 98         388 $self->reset_to_item( 'Text::Anchor', type => 'anchor' );
282             }
283              
284             BEGIN
285             {
286 26     26   169 for my $heading ( 0 .. 4 )
287             {
288             my $start_meth = sub
289             {
290 226     226   101644 my $self = shift;
291             $self->push_heading_element( Heading =>
292             level => $heading,
293             type => 'header',
294             filename => $self->{basefile},
295 226         1418 );
296 130         693 };
297              
298             my $end_meth = sub
299             {
300 226     226   4084 my $self = shift;
301 226         942 $self->reset_to_item( Heading => level => $heading );
302 130         448 };
303              
304             do
305 130         223 {
306 26     26   338 no strict 'refs';
  26         62  
  26         5273  
307 130         228 *{ 'start_head' . $heading } = $start_meth;
  130         877  
308 130         236 *{ 'end_head' . $heading } = $end_meth;
  130         613  
309             };
310             }
311              
312 26         242 my %link_types =
313             (
314             X => 'index',
315             L => 'link',
316             A => 'link',
317             );
318              
319 26         284 while (my ($tag, $type) = each %link_types)
320             {
321             my $start_meth = sub
322             {
323 450     450   11804 my $self = shift;
324             $self->push_link_element( 'Text::' . ucfirst $type,
325 450         2775 type => $type, link => $self->{basefile} );
326 78         399 };
327              
328             my $end_meth = sub
329             {
330 450     450   7609 my $self = shift;
331 450         2335 $self->reset_to_item( 'Text::' . ucfirst $type, type => $type );
332 78         278 };
333              
334             do
335 78         156 {
336 26     26   213 no strict 'refs';
  26         65  
  26         5673  
337 78         146 *{ 'start_' . $tag } = $start_meth;
  78         456  
338 78         219 *{ 'end_' . $tag } = $end_meth;
  78         531  
339             };
340             }
341              
342 26         428 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         282 while (my ($tag, $type) = each %text_types)
357             {
358             my $start_meth = sub
359             {
360 976     976   24354 my $self = shift;
361 976         5138 $self->push_element( 'Text::' . $type, type => lc $type );
362 260         1037 };
363              
364             my $end_meth = sub
365             {
366 976     976   16098 my $self = shift;
367 976         5603 $self->reset_to_item( 'Text::' . $type, type => lc $type );
368 260         1094 };
369              
370             do
371 260         426 {
372 26     26   216 no strict 'refs';
  26         67  
  26         7200  
373 260         369 *{ 'start_' . $tag } = $start_meth;
  260         1165  
374 260         404 *{ 'end_' . $tag } = $end_meth;
  260         1590  
375             };
376             }
377              
378 26         104 for my $list_type (qw( bullet text block number ))
379             {
380             my $start_list_meth = sub
381             {
382 197     197   65227 my $self = shift;
383 197         971 $self->push_element( 'List', type => $list_type . '_list' );
384 104         391 };
385              
386             my $end_list_meth = sub
387             {
388 197     197   19835 my $self = shift;
389 197         816 my $list = $self->reset_to_item( 'List',
390             type => $list_type . '_list'
391             );
392 197 50       1582 $list->fixup_list if $list;
393 104         374 };
394              
395             my $start_item_meth = sub
396             {
397 562     562   196515 my ($self, $args) = @_;
398             my @marker = $args->{number}
399             ? (marker => $args->{number})
400 562 100       2174 : ();
401              
402 562         2277 $self->push_element( 'ListItem',
403             type => $list_type . '_item', @marker
404             );
405 104         353 };
406              
407             my $end_item_meth = sub
408             {
409 562     562   9654 my $self = shift;
410 562         2464 $self->reset_to_item( 'ListItem', type => $list_type . '_item' );
411 104         307 };
412              
413             do
414 104         170 {
415 26     26   216 no strict 'refs';
  26         64  
  26         2016  
416 104         148 *{ 'start_over_' . $list_type } = $start_list_meth;
  104         600  
417 104         225 *{ 'end_over_' . $list_type } = $end_list_meth;
  104         408  
418 104         175 *{ 'start_item_' . $list_type } = $start_item_meth;
  104         339  
419 104         183 *{ 'end_item_' . $list_type } = $end_item_meth;
  104         26051  
420             };
421             }
422             }
423              
424             sub handle_text
425             {
426 4488     4488 0 93328 my $self = shift;
427 4488         14075 $self->add_element( 'Text::Plain' => type => 'plaintext', content => $_[0]);
428             }
429              
430             sub start_Para
431             {
432 1078     1078 0 220924 my $self = shift;
433 1078         3571 $self->push_element( Paragraph => type => 'paragraph' );
434             }
435              
436             sub end_Para
437             {
438 1153     1153 0 17546 my $self = shift;
439 1153         4302 $self->reset_to_item( Paragraph => type => 'paragraph' );
440             }
441              
442             sub start_for
443             {
444 161     161 0 47874 my ($self, $flags) = @_;
445 161 100       528 do { $flags->{$_} = '' unless defined $flags->{$_} } for qw( title target );
  322         1247  
446              
447             $self->push_element( Block =>
448             type => 'block',
449             title => $flags->{title},
450 161         789 target => $flags->{target} );
451             }
452              
453             sub end_for
454             {
455 161     161 0 21658 my $self = shift;
456 161         531 my $block = $self->reset_to_item( 'Block' );
457              
458 161 100       6308 if (my $title = $block->title)
459             {
460 51         253 $block->title( $self->fix_title( $title ) );
461             }
462             }
463              
464             sub start_sidebar
465             {
466 49     49 0 10658 my ($self, $flags) = @_;
467 49         239 $self->push_element( Block => type => 'sidebar', title => $flags->{title} );
468             }
469              
470             sub end_sidebar
471             {
472 49     49 0 9042 my $self = shift;
473 49         166 $self->reset_to_item( 'Block' );
474             }
475              
476             sub start_table
477             {
478 24     24 0 4697 my ($self, $flags) = @_;
479 24         146 $self->push_element( Table => 'type' => 'table', title => $flags->{title} );
480             }
481              
482             sub end_table
483             {
484 24     24 0 789 my $self = shift;
485 24         95 my $table = $self->reset_to_item( 'Table' );
486              
487 24 50       1127 if (my $title = $table->title)
488             {
489 24         207 $table->title( $self->fix_title( $title ) );
490             }
491              
492 24         248 $table->fixup;
493             }
494              
495             sub fix_title
496             {
497 75     75 0 306 my ($self, $title) = @_;
498 75         412 my $title_elem = $self->start_new_element(
499             Paragraph => type => 'paragraph' );
500 75         52331 my $tag_regex = qr/([IC]<+\s*.+?\s*>+)/;
501 75         214 my @parts;
502              
503 75         836 for my $part (split /$tag_regex/, $title)
504             {
505 195 100       1236 if ($part =~ /$tag_regex/)
506             {
507 72         529 my ($type, $content) = $part =~ /^([IC])<+\s*(.+?)\s*>+/;
508 72         284 my $start = "start_$type";
509 72         216 my $end = "end_$type";
510 72         465 $self->$start;
511 72         316 $self->handle_text( $content );
512 72         353 $self->$end;
513             }
514             else
515             {
516 123         491 $self->handle_text( $part );
517             }
518             }
519              
520 75         375 return $self->end_Para;
521             }
522              
523             sub start_headrow
524             {
525 24     24 0 6195 my $self = shift;
526 24         125 $self->push_element( TableRow => 'type' => 'headrow' );
527             }
528              
529             sub end_headrow
530             {
531 24     24 0 557 my $self = shift;
532 24         146 $self->reset_to_item( 'TableRow' );
533             }
534              
535             sub start_row
536             {
537 48     48 0 4869 my $self = shift;
538 48         190 $self->push_element( TableRow => 'type' => 'row' );
539             }
540              
541             sub end_row
542             {
543 48     48 0 8504 my $self = shift;
544 48         180 $self->reset_to_item( 'TableRow' );
545             }
546              
547             sub start_cell
548             {
549 144     144 0 27486 my $self = shift;
550 144         491 $self->push_element( TableCell => 'type' => 'cell' );
551             }
552              
553             sub end_cell
554             {
555 144     144 0 2367 my $self = shift;
556 144         470 $self->reset_to_item( 'TableCell' );
557             }
558              
559             sub start_figure
560             {
561 24     24 0 4796 my ($self, $flags) = @_;
562             $self->push_element( Figure => type => 'figure',
563 24         140 caption => $flags->{title} );
564             }
565              
566             sub end_figure
567             {
568 24     24 0 4973 my $self = shift;
569 24         111 $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.2004
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