File Coverage

blib/lib/Parse/Man/DOM.pm
Criterion Covered Total %
statement 177 192 92.1
branch 5 6 83.3
condition 1 2 50.0
subroutine 71 80 88.7
pod 8 12 66.6
total 262 292 89.7


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2011-2022 -- leonerd@leonerd.org.uk
5              
6             package Parse::Man::DOM 0.03;
7              
8 7     7   362229 use v5.14;
  7         91  
9 7     7   33 use warnings;
  7         17  
  7         202  
10              
11 7     7   29 use base qw( Parse::Man );
  7         13  
  7         3349  
12              
13             =head1 NAME
14              
15             C - parse nroff-formatted manpages and return a DOM tree
16              
17             =head1 SYNOPSIS
18              
19             use Parse::Man::DOM;
20              
21             my $parser = Parse::Man::DOM->new;
22              
23             my $document = $parser->from_file( "my_manpage.1" );
24              
25             print "The manpage name is", $document->meta( "name" ), "\n";
26              
27             =head1 DESCRIPTION
28              
29             This subclass of L returns an object tree representing the parsed
30             content of the input file. The returned result will be an object of the
31             C class, which itself will contain other objects
32             nested within it.
33              
34             =cut
35              
36             sub parse
37             {
38 17     17 0 1136 my $self = shift;
39              
40 17         48 local $self->{current_document} = $self->_make( document => );
41              
42 17         76 $self->SUPER::parse( @_ );
43              
44 17         1520 return $self->{current_document};
45             }
46              
47             sub para_TH
48             {
49 2     2 1 111 my $self = shift;
50 2         6 my ( $name, $section ) = @_;
51              
52 2         9 $self->{current_document}->add_meta( $self->_make( metadata => name => $name ) );
53 2         13 $self->{current_document}->add_meta( $self->_make( metadata => section => $section ) );
54             }
55              
56             sub para_SH
57             {
58 2     2 1 108 my $self = shift;
59 2         4 my ( $text ) = @_;
60 2         6 $self->{current_document}->append_para( $self->_make( heading => 1 => $text ) );
61             }
62              
63             sub para_SS
64             {
65 0     0 1 0 my $self = shift;
66 0         0 my ( $text ) = @_;
67 0         0 $self->{current_document}->append_para( $self->_make( heading => 2 => $text ) );
68             }
69              
70             sub para_P
71             {
72 16     16 1 20 my $self = shift;
73 16         25 my ( $opts ) = @_;
74 16         45 $self->{current_document}->append_para( $self->_make( para_plain => $opts, $self->_make( chunklist => ) ) );
75             }
76              
77             sub para_TP
78             {
79 1     1 1 2 my $self = shift;
80 1         3 my ( $opts ) = @_;
81 1         4 $self->{current_document}->append_para( $self->_make( para_term => $opts, $self->_make( chunklist => ), $self->_make( chunklist => ) ) );
82             }
83              
84             sub para_IP
85             {
86 2     2 1 3 my $self = shift;
87 2         4 my ( $opts ) = @_;
88 2         6 $self->{current_document}->append_para( $self->_make( para_indent => $opts, $self->_make( chunklist => ) ) );
89             }
90              
91             sub para_EX
92             {
93 0     0 1 0 my $self = shift;
94 0         0 my ( $opts ) = @_;
95 0         0 $self->{current_document}->append_para( $self->_make( para_example => $opts, $self->_make( chunklist => ) ) );
96             }
97              
98             sub chunk
99             {
100 27     27 1 1030 my $self = shift;
101 27         72 my ( $text, %opts ) = @_;
102 27         63 $self->{current_document}->append_chunk( $self->_make( chunk => $text => $opts{font}, $opts{size} ) );
103             }
104              
105             sub join_para
106             {
107 5     5 0 8 my $self = shift;
108 5         11 $self->{current_document}->append_chunk( $self->_make( linebreak => ) );
109             }
110              
111             sub entity_br
112             {
113 0     0 0 0 my $self = shift;
114 0         0 $self->{current_document}->append_chunk( $self->_make( break => ) );
115             }
116              
117             sub entity_sp
118             {
119 1     1 0 2 my $self = shift;
120 1         3 $self->{current_document}->append_chunk( $self->_make( space => ) );
121             }
122              
123             sub _make
124             {
125 95     95   110 my $self = shift;
126 95         158 my $type = shift;
127 95 50       371 my $code = $self->can( "${type}_class" ) or die "Unable to make a ${type}";
128 95         395 return $code->()->new( @_ );
129             }
130              
131 7     7   56 use constant document_class => "Parse::Man::DOM::Document";
  7         12  
  7         488  
132 7     7   39 use constant metadata_class => "Parse::Man::DOM::Metadata";
  7         15  
  7         392  
133 7     7   39 use constant heading_class => "Parse::Man::DOM::Heading";
  7         12  
  7         387  
134 7     7   41 use constant para_plain_class => "Parse::Man::DOM::Para::Plain";
  7         11  
  7         342  
135 7     7   39 use constant para_term_class => "Parse::Man::DOM::Para::Term";
  7         26  
  7         313  
136 7     7   36 use constant para_indent_class => "Parse::Man::DOM::Para::Indent";
  7         10  
  7         302  
137 7     7   37 use constant para_example_class => "Parse::Man::DOM::Para::Example";
  7         12  
  7         311  
138 7     7   38 use constant chunklist_class => "Parse::Man::DOM::Chunklist";
  7         11  
  7         308  
139 7     7   32 use constant chunk_class => "Parse::Man::DOM::Chunk";
  7         12  
  7         291  
140 7     7   34 use constant space_class => "Parse::Man::DOM::Space";
  7         13  
  7         301  
141 7     7   34 use constant break_class => "Parse::Man::DOM::Break";
  7         10  
  7         483  
142 7     7   42 use constant linebreak_class => "Parse::Man::DOM::Linebreak";
  7         10  
  7         2504  
143              
144             package Parse::Man::DOM::Document;
145              
146             =head1 Parse::Man::DOM::Document
147              
148             Represents the document as a whole.
149              
150             =cut
151              
152             sub new
153             {
154 17     17   31 my $class = shift;
155 17         76 return bless { meta => {}, paras => [] }, $class;
156             }
157              
158             =head2 meta
159              
160             $meta = $document->meta( $key )
161              
162             Returns a C object for the named item of metadata.
163              
164             =over 4
165              
166             =item * name
167              
168             The page name given to the C<.TH> directive.
169              
170             =item * section
171              
172             The section number given to the C<.TH> directive.
173              
174             =back
175              
176             =cut
177              
178             sub meta
179             {
180 3     3   617 my $self = shift;
181 3         6 my ( $name ) = @_;
182 3   50     14 return $self->{meta}{$name} || die "No meta defined for $name";
183             }
184              
185             sub add_meta
186             {
187 4     4   7 my $self = shift;
188 4         7 my ( $meta ) = @_;
189 4         16 $self->{meta}{ $meta->name } = $meta;
190             }
191              
192             =head2 paras
193              
194             @paras = $document->paras
195              
196             Returns a list of C or C or
197             subclass objects, containing the actual page content.
198              
199             =cut
200              
201             sub paras
202             {
203 16     16   337 my $self = shift;
204 16         35 return @{ $self->{paras} };
  16         60  
205             }
206              
207             sub append_para
208             {
209 21     21   32 my $self = shift;
210 21         26 push @{ $self->{paras} }, @_;
  21         65  
211             }
212              
213 33     33   78 sub last_para { shift->{paras}[-1] }
214              
215 33     33   60 sub append_chunk { shift->last_para->append_chunk( @_ ) }
216              
217             package Parse::Man::DOM::Metadata;
218              
219             =head1 Parse::Man::DOM::Metadata
220              
221             Represents a single item of metadata about the page.
222              
223             =cut
224              
225             sub new
226             {
227 4     4   7 my $class = shift;
228 4         18 return bless [ $_[0] => $_[1] ], $class;
229             }
230              
231             =head2 name
232              
233             $name = $metadata->name
234              
235             The string name of the metadata
236              
237             =head2 value
238              
239             $value = $metadata->value
240              
241             The string value of the metadata
242              
243             =cut
244              
245 4     4   119 sub name { shift->[0] }
246 2     2   8 sub value { shift->[1] }
247              
248             package Parse::Man::DOM::Heading;
249 7     7   44 use constant type => "heading";
  7         11  
  7         1379  
250              
251             =head1 Parse::Man::DOM::Heading
252              
253             Represents the contents of a C<.SH> or C<.SS> heading
254              
255             =cut
256              
257             sub new
258             {
259 2     2   3 my $class = shift;
260 2         8 return bless [ $_[0] => $_[1] ], $class;
261             }
262              
263             =head2 level
264              
265             $level = $heading->level
266              
267             The heading level number; 1 for C<.SH>, 2 for C<.SS>
268              
269             =head2 text
270              
271             $text = $heading->text
272              
273             The plain text string of the heading title
274              
275             =cut
276              
277 1     1   1111 sub level { shift->[0] }
278 1     1   5 sub text { shift->[1] }
279              
280             package Parse::Man::DOM::Para;
281              
282             =head1 Parse::Man::DOM::Para
283              
284             Represents a paragraph of formatted text content. Will be one of the following
285             subclasses.
286              
287             =cut
288              
289             =head2 filling
290              
291             $filling = $para->filling
292              
293             Returns true if filling (C<.fi>) is in effect, or false if no-filling (C<.nf>)
294             is in effect.
295              
296             =head2 body
297              
298             $chunklist = $para->body
299              
300             Returns a C to represent the actual content of the
301             paragraph.
302              
303             =head2 indent
304              
305             $indent = $para->indent
306              
307             Returns the indentation size in column count, if defined.
308              
309             =cut
310              
311 4     4   31 sub filling { shift->{filling} }
312 41     41   1573 sub body { shift->{body} }
313 1     1   5 sub indent { shift->{indent} }
314              
315             package Parse::Man::DOM::Para::Plain;
316 7     7   46 use base qw( Parse::Man::DOM::Para );
  7         10  
  7         3310  
317 7     7   56 use constant type => "plain";
  7         28  
  7         1055  
318              
319             =head1 Parse::Man::DOM::Para::Plain
320              
321             Represent a plain (C<.P> or C<.PP>) paragraph.
322              
323             =head2 type
324              
325             $type = $para->type
326              
327             Returns C<"plain">.
328              
329             =cut
330              
331             sub new
332             {
333 18     18   33 my $class = shift;
334 18         28 my ( $opts, $body ) = @_;
335 18         28 return bless { (map { $_ => $opts->{$_} } qw( filling indent )), body => $body }, $class;
  36         141  
336             }
337              
338 27     27   153 sub append_chunk { shift->body->append_chunk( @_ ) }
339              
340             package Parse::Man::DOM::Para::Term;
341 7     7   41 use base qw( Parse::Man::DOM::Para );
  7         13  
  7         1675  
342 7     7   44 use constant type => "term";
  7         10  
  7         1627  
343              
344             =head1 Parse::Man::DOM::Para::Term
345              
346             Represents a term paragraph (C<.TP>).
347              
348             =head2 type
349              
350             $type = $para->type
351              
352             Returns C<"term">.
353              
354             =cut
355              
356             sub new
357             {
358 1     1   2 my $class = shift;
359 1         3 my ( $opts, $term, $definition ) = @_;
360              
361 1         5 return bless { indent => $opts->{indent}, term => $term, definition => $definition }, $class;
362             }
363              
364             =head2 term
365              
366             $chunklist = $para->term
367              
368             Returns a C for the defined term name.
369              
370             =head2 definition
371              
372             $chunklist = $para->definition
373              
374             Returns a C for the defined term definition.
375              
376             =cut
377              
378 2     2   7 sub term { shift->{term} }
379 6     6   15 sub definition { shift->{definition} }
380              
381             sub append_chunk
382             {
383 6     6   7 my $self = shift;
384 6         7 my ( $chunk ) = @_;
385              
386 6 100       33 if( $chunk->isa( "Parse::Man::DOM::Linebreak" ) ) {
    100          
387 2         5 $self->{term_done} = 1;
388             }
389             elsif( !$self->{term_done} ) {
390 1         13 $self->term->append_chunk( $chunk );
391             }
392             else {
393 3         7 $self->definition->append_chunk( $chunk );
394             }
395             }
396              
397             package Parse::Man::DOM::Para::Indent;
398 7     7   44 use base qw( Parse::Man::DOM::Para::Plain );
  7         12  
  7         1888  
399 7     7   45 use constant type => "indent";
  7         12  
  7         1126  
400              
401             =head1 Parse::Man::DOM::Para::Indent
402              
403             Represents an indented paragraph (C<.IP>).
404              
405             =head2 type
406              
407             $type = $para->type
408              
409             Returns C<"indent">.
410              
411             =cut
412              
413             sub new
414             {
415 2     2   4 my $class = shift;
416 2         3 my ( $opts, $body ) = @_;
417 2         10 my $self = $class->SUPER::new( @_ );
418 2         18 $self->{marker} = $opts->{marker};
419 2         6 return $self;
420             }
421              
422             =head2 marker
423              
424             $marker = $para->marker
425              
426             Returns the indentation marker text, if defined.
427              
428             =cut
429              
430 1     1   4 sub marker { shift->{marker} }
431              
432             package Parse::Man::DOM::Para::Example;
433 7     7   43 use base qw( Parse::Man::DOM::Para::Plain );
  7         14  
  7         1691  
434 7     7   43 use constant type => "example";
  7         14  
  7         1866  
435              
436             =head1 Parse::Man::DOM::Para::Example
437              
438             Represents an example paragraph (C<.EX> / C<.EE>).
439              
440             =head2 type
441              
442             $type = $para->type
443              
444             Returns C<"example">.
445              
446             =cut
447              
448             package Parse::Man::DOM::Chunklist;
449              
450             =head1 Parse::Man::DOM::Chunklist
451              
452             Contains a list of C objects to represent paragraph
453             content.
454              
455             =cut
456              
457             sub new
458             {
459 20     20   26 my $class = shift;
460 20         71 return bless { chunks => [ @_ ] }, $class;
461             }
462              
463             =head2 chunks
464              
465             @chunks = $chunklist->chunks
466              
467             Returns a list of C objects.
468              
469             =cut
470              
471 18     18   25 sub chunks { @{ shift->{chunks} } }
  18         98  
472              
473             sub append_chunk
474             {
475 31     31   39 my $self = shift;
476 31         36 push @{ $self->{chunks} }, @_;
  31         191  
477             }
478              
479             package Parse::Man::DOM::Chunk;
480              
481             =head1 Parse::Man::DOM::Chunk
482              
483             Represents a chunk of text with a particular format applied.
484              
485             =cut
486              
487             sub new
488             {
489 27     27   34 my $class = shift;
490 27         88 return bless [ @_ ], $class;
491             }
492              
493 0     0   0 sub is_linebreak { 0 }
494 0     0   0 sub is_space { 0 }
495 0     0   0 sub is_break { 0 }
496              
497             =head2 text
498              
499             $text = $chunk->text
500              
501             The plain string value of the text for this chunk.
502              
503             =head2 font
504              
505             $font = $chunk->font
506              
507             The font name in effect for this chunk. One of C<"R">, C<"B">, C<"I"> or
508             C<"SM">.
509              
510             =head2 size
511              
512             $size = $chunk->size
513              
514             The size of this chunk, relative to the paragraph base of 0.
515              
516             =cut
517              
518 23     23   109 sub text { shift->[0] }
519 12     12   80 sub font { shift->[1] }
520 0     0   0 sub size { shift->[2] }
521              
522             package Parse::Man::DOM::Linebreak;
523 7     7   46 use base qw( Parse::Man::DOM::Chunk );
  7         9  
  7         5558  
524              
525             sub new
526             {
527 5     5   9 my $class = shift;
528 5         12 return bless [], $class;
529             }
530              
531 3     3   12 sub is_linebreak { 1 }
532              
533             package Parse::Man::DOM::Space;
534 7     7   52 use base qw( Parse::Man::DOM::Chunk );
  7         12  
  7         1895  
535              
536             sub new
537             {
538 1     1   3 my $class = shift;
539 1         4 return bless [], $class;
540             }
541              
542 1     1   5 sub is_space { 1 }
543              
544             package Parse::Man::DOM::Break;
545 7     7   53 use base qw( Parse::Man::DOM::Chunk );
  7         13  
  7         2043  
546              
547             sub new
548             {
549 0     0     my $class = shift;
550 0           return bless [], $class;
551             }
552              
553 0     0     sub is_break { 1 }
554              
555             =head1 AUTHOR
556              
557             Paul Evans
558              
559             =cut
560              
561             0x55AA;