File Coverage

blib/lib/Markdent/Role/HTMLStream.pm
Criterion Covered Total %
statement 82 176 46.5
branch 3 22 13.6
condition 0 3 0.0
subroutine 29 58 50.0
pod 0 44 0.0
total 114 303 37.6


line stmt bran cond sub pod time code
1             package Markdent::Role::HTMLStream;
2              
3 33     33   23176 use strict;
  33         85  
  33         1157  
4 33     33   205 use warnings;
  33         88  
  33         960  
5 33     33   203 use namespace::autoclean;
  33         74  
  33         234  
6              
7             our $VERSION = '0.38';
8              
9 33     33   20034 use HTML::Entities qw( encode_entities );
  33         198412  
  33         3220  
10 33     33   13787 use Markdent::CheckedOutput;
  33         98  
  33         1068  
11 33     33   1298 use Markdent::Types;
  33         77  
  33         309  
12 33     33   856444 use Params::ValidationCompiler qw( validation_for );
  33         502181  
  33         2121  
13              
14 33     33   302 use Moose::Role;
  33         89  
  33         430  
15              
16             with 'Markdent::Role::EventsAsMethods';
17              
18             requires qw( start_document end_document );
19              
20             has _output => (
21             is => 'ro',
22             isa => t('OutputStream'),
23             required => 1,
24             init_arg => 'output',
25             );
26              
27             has _encodable_entities => (
28             is => 'ro',
29             isa => t('Str'),
30             default => q{<>&"\x00-\x09\x11\x12\x14-\x1f},
31             init_arg => 'encodable_entities',
32             );
33              
34             override BUILDARGS => sub {
35             my $self = shift;
36              
37             my $args = super();
38              
39             my $output = $args->{output};
40              
41             # This will blow up soon if there's no output.
42             return $args unless $output;
43              
44             # If the user supplied a non IO::Handle object we won't wrap it.
45             return $args if blessed $output && !$output->isa('IO::Handle');
46              
47             $args->{output} = Markdent::CheckedOutput->new($output);
48              
49             return $args;
50             };
51              
52             {
53             my $validator = validation_for(
54             params => [
55             level => { type => t('HeaderLevel') },
56             ],
57             named_to_list => 1,
58             );
59              
60             sub start_header {
61 3     3 0 9 my $self = shift;
62 3         63 my ($level) = $validator->(@_);
63              
64 3         117 $self->_stream_start_tag( 'h' . $level );
65             }
66             }
67              
68             {
69             my $validator = validation_for(
70             params => [
71             level => { type => t('HeaderLevel') },
72             ],
73             named_to_list => 1,
74             );
75              
76             sub end_header {
77 3     3 0 10 my $self = shift;
78 3         64 my ($level) = $validator->(@_);
79              
80 3         108 $self->_stream_end_tag( 'h' . $level );
81             }
82             }
83              
84             sub start_blockquote {
85 0     0 0 0 my $self = shift;
86              
87 0         0 $self->_stream_start_tag('blockquote');
88             }
89              
90             sub end_blockquote {
91 0     0 0 0 my $self = shift;
92              
93 0         0 $self->_stream_end_tag('blockquote');
94             }
95              
96             sub start_unordered_list {
97 3     3 0 10 my $self = shift;
98              
99 3         14 $self->_stream_start_tag('ul');
100             }
101              
102             sub end_unordered_list {
103 3     3 0 20 my $self = shift;
104              
105 3         14 $self->_stream_end_tag('ul');
106             }
107              
108             sub start_ordered_list {
109 0     0 0 0 my $self = shift;
110              
111 0         0 $self->_stream_start_tag('ol');
112             }
113              
114             sub end_ordered_list {
115 0     0 0 0 my $self = shift;
116              
117 0         0 $self->_stream_end_tag('ol');
118             }
119              
120             sub start_list_item {
121 6     6 0 15 my $self = shift;
122              
123 6         20 $self->_stream_start_tag('li');
124             }
125              
126             sub end_list_item {
127 6     6 0 11 my $self = shift;
128              
129 6         19 $self->_stream_end_tag('li');
130             }
131              
132             {
133             my $validator = validation_for(
134             params => [
135             code => { type => t('Str') },
136             language => {
137             type => t('Str'),
138             optional => 1,
139             },
140             ],
141             named_to_list => 1,
142             );
143              
144             sub code_block {
145 0     0 0 0 my $self = shift;
146 0         0 my ( $code, $language ) = $validator->(@_);
147              
148 0         0 $self->_stream_start_tag('pre');
149              
150 0 0       0 my %class = $language ? ( class => 'language-' . $language ) : ();
151 0         0 $self->_stream_start_tag( 'code', \%class );
152              
153 0         0 $self->_stream_text($code);
154              
155 0         0 $self->_stream_end_tag('code');
156 0         0 $self->_stream_end_tag('pre');
157             }
158             }
159              
160             {
161             my $validator = validation_for(
162             params => [
163             text => { type => t('Str') },
164             ],
165             named_to_list => 1,
166             );
167              
168             sub preformatted {
169 0     0 0 0 my $self = shift;
170 0         0 my ($text) = $validator->(@_);
171              
172 0         0 $self->_stream_start_tag('pre');
173 0         0 $self->_stream_start_tag('code');
174 0         0 $self->_stream_text($text);
175 0         0 $self->_stream_end_tag('code');
176 0         0 $self->_stream_end_tag('pre');
177             }
178             }
179              
180             sub start_paragraph {
181 9     9 0 36 my $self = shift;
182              
183 9         33 $self->_stream_start_tag('p');
184             }
185              
186             sub end_paragraph {
187 7     7 0 17 my $self = shift;
188              
189 7         23 $self->_stream_end_tag('p');
190             }
191              
192             {
193             my $validator = validation_for(
194             params => [
195             caption => {
196             type => t('Str'),
197             optional => 1,
198             },
199             ],
200             named_to_list => 1,
201             );
202              
203             sub start_table {
204 0     0 0 0 my $self = shift;
205 0         0 my ($caption) = $validator->(@_);
206              
207 0         0 $self->_stream_start_tag('table');
208              
209 0 0 0     0 if ( defined $caption && length $caption ) {
210 0         0 $self->_stream_start_tag('caption');
211 0         0 $self->_stream_text($caption);
212 0         0 $self->_stream_end_tag('caption');
213             }
214             }
215             }
216              
217             sub end_table {
218 0     0 0 0 my $self = shift;
219              
220 0         0 $self->_stream_end_tag('table');
221             }
222              
223             sub start_table_header {
224 0     0 0 0 my $self = shift;
225              
226 0         0 $self->_stream_start_tag('thead');
227             }
228              
229             sub end_table_header {
230 0     0 0 0 my $self = shift;
231              
232 0         0 $self->_stream_end_tag('thead');
233             }
234              
235             sub start_table_body {
236 0     0 0 0 my $self = shift;
237              
238 0         0 $self->_stream_start_tag('tbody');
239             }
240              
241             sub end_table_body {
242 0     0 0 0 my $self = shift;
243              
244 0         0 $self->_stream_end_tag('tbody');
245             }
246              
247             sub start_table_row {
248 0     0 0 0 my $self = shift;
249              
250 0         0 $self->_stream_start_tag('tr');
251             }
252              
253             sub end_table_row {
254 0     0 0 0 my $self = shift;
255              
256 0         0 $self->_stream_end_tag('tr');
257             }
258              
259             {
260             my $validator = validation_for(
261             params => [
262             alignment => {
263             type => t('TableCellAlignment'),
264             optional => 1,
265             },
266             colspan => { type => t('PositiveInt') },
267             is_header_cell => { type => t('Bool') },
268             ],
269             named_to_list => 1,
270             );
271              
272             sub start_table_cell {
273 0     0 0 0 my $self = shift;
274 0         0 my ( $alignment, $colspan, $is_header ) = $validator->(@_);
275              
276 0 0       0 my $tag = $is_header ? 'th' : 'td';
277              
278 0         0 my %attr;
279 0 0       0 $attr{style} = "text-align: $alignment"
280             if $alignment;
281              
282 0 0       0 $attr{colspan} = $colspan
283             if $colspan != 1;
284              
285 0         0 $self->_stream_start_tag( $tag, \%attr );
286             }
287             }
288              
289             {
290             my $validator = validation_for(
291             params => [
292             is_header_cell => { type => t('Bool') },
293             ],
294             named_to_list => 1,
295             );
296              
297             sub end_table_cell {
298 0     0 0 0 my $self = shift;
299 0         0 my ($is_header) = $validator->(@_);
300              
301 0 0       0 $self->_stream_end_tag( $is_header ? 'th' : 'td' );
302             }
303             }
304              
305             sub start_emphasis {
306 3     3 0 7 my $self = shift;
307              
308 3         14 $self->_stream_start_tag('em');
309             }
310              
311             sub end_emphasis {
312 3     3 0 8 my $self = shift;
313              
314 3         12 $self->_stream_end_tag('em');
315             }
316              
317             sub start_strong {
318 3     3 0 7 my $self = shift;
319              
320 3         13 $self->_stream_start_tag('strong');
321             }
322              
323             sub end_strong {
324 3     3 0 10 my $self = shift;
325              
326 3         14 $self->_stream_end_tag('strong');
327             }
328              
329             sub start_code {
330 0     0 0 0 my $self = shift;
331              
332 0         0 $self->_stream_start_tag('code');
333             }
334              
335             sub end_code {
336 0     0 0 0 my $self = shift;
337              
338 0         0 $self->_stream_end_tag('code');
339             }
340              
341             {
342             my $validator = validation_for(
343             params => [
344             uri => { type => t('Str') },
345             ],
346             named_to_list => 1,
347             );
348              
349             sub auto_link {
350 0     0 0 0 my $self = shift;
351 0         0 my ($uri) = $validator->(@_);
352              
353 0         0 $self->_stream_start_tag( 'a', { href => $uri } );
354 0         0 $self->_stream_text($uri);
355 0         0 $self->_stream_end_tag('a');
356             }
357             }
358              
359             {
360             my $validator = validation_for(
361             params => {
362             uri => { type => t('Str') },
363             title => {
364             type => t('Str'),
365             optional => 1,
366             },
367             id => {
368             type => t('Str'),
369             optional => 1,
370             },
371             is_implicit_id => {
372             type => t('Bool'),
373             optional => 1,
374             },
375             }
376             );
377              
378             sub start_link {
379 0     0 0 0 my $self = shift;
380 0         0 my %p = $validator->(@_);
381              
382 0         0 delete @p{ grep { !defined $p{$_} } keys %p };
  0         0  
383              
384             $self->_stream_start_tag(
385             'a', {
386             href => $p{uri},
387 0 0       0 exists $p{title} ? ( title => $p{title} ) : (),
388             },
389             );
390             }
391             }
392              
393             sub end_link {
394 0     0 0 0 my $self = shift;
395              
396 0         0 $self->_stream_end_tag('a');
397             }
398              
399             sub line_break {
400 0     0 0 0 my $self = shift;
401              
402 0         0 $self->_stream_start_tag('br');
403             }
404              
405             {
406             my $validator = validation_for(
407             params => [
408             text => { type => t('Str') },
409             ],
410             named_to_list => 1,
411             );
412              
413             sub text {
414 29     29 0 53 my $self = shift;
415 29         567 my ($text) = $validator->(@_);
416              
417 29         774 $self->_stream_text($text);
418             }
419             }
420              
421             {
422             my $validator = validation_for(
423             params => [
424             tag => { type => t('Str') },
425             attributes => { type => t('HashRef') },
426             ],
427             named_to_list => 1,
428             );
429              
430             sub start_html_tag {
431 1     1 0 3 my $self = shift;
432 1         21 my ( $tag, $attributes ) = $validator->(@_);
433              
434 1         50 $self->_stream_start_tag( $tag, $attributes );
435             }
436             }
437              
438             {
439             my $validator = validation_for(
440             params => [
441             text => { type => t('Str') },
442             ],
443             named_to_list => 1,
444             );
445              
446             sub html_comment_block {
447 0     0 0 0 my $self = shift;
448 0         0 my ($text) = $validator->(@_);
449              
450 0         0 $self->_stream_raw( '<!--' . $text . '-->' . "\n" );
451             }
452             }
453              
454             {
455             my $validator = validation_for(
456             params => [
457             text => { type => t('Str') },
458             ],
459             named_to_list => 1,
460             );
461              
462             sub html_comment {
463 0     0 0 0 my $self = shift;
464 0         0 my ($text) = $validator->(@_);
465              
466 0         0 $self->_stream_raw( '<!--' . $text . '-->' );
467             }
468             }
469              
470             {
471             my $validator = validation_for(
472             params => [
473             tag => { type => t('Str') },
474             attributes => { type => t('HashRef') },
475             ],
476             named_to_list => 1,
477             );
478              
479             sub html_tag {
480 0     0 0 0 my $self = shift;
481 0         0 my ( $tag, $attributes ) = $validator->(@_);
482              
483 0         0 $self->_stream_start_tag( $tag, $attributes );
484             }
485             }
486              
487             {
488             my $validator = validation_for(
489             params => [
490             tag => { type => t('Str') },
491             ],
492             named_to_list => 1,
493             );
494              
495             sub end_html_tag {
496 1     1 0 4 my $self = shift;
497 1         21 my ($tag) = $validator->(@_);
498              
499 1         36 $self->_stream_end_tag($tag);
500             }
501             }
502              
503             {
504             my $validator = validation_for(
505             params => [
506             entity => { type => t('Str') },
507             ],
508             named_to_list => 1,
509             );
510              
511             sub html_entity {
512 0     0 0 0 my $self = shift;
513 0         0 my ($entity) = $validator->(@_);
514              
515 0         0 $self->_stream_raw( '&' . $entity . ';' );
516             }
517             }
518              
519             {
520             my $validator = validation_for(
521             params => [
522             html => { type => t('Str') },
523             ],
524             named_to_list => 1,
525             );
526              
527             sub html_block {
528 0     0 0 0 my $self = shift;
529 0         0 my ($html) = $validator->(@_);
530              
531 0         0 $self->_output()->print($html);
532             }
533             }
534              
535             {
536             my $validator = validation_for(
537             params => {
538             alt_text => { type => t('Str') },
539             uri => {
540             type => t('Str'),
541             optional => 1,
542             },
543             title => {
544             type => t('Str'),
545             optional => 1,
546             },
547             id => {
548             type => t('Str'),
549             optional => 1,
550             },
551             is_implicit_id => {
552             type => t('Bool'),
553             optional => 1,
554             },
555             },
556             );
557              
558             sub image {
559 0     0 0 0 my $self = shift;
560 0         0 my %p = $validator->(@_);
561              
562 0         0 delete @p{ grep { !defined $p{$_} } keys %p };
  0         0  
563              
564             $self->_stream_start_tag(
565             'img', {
566             src => $p{uri},
567             ( exists $p{alt_text} ? ( alt => $p{alt_text} ) : () ),
568 0 0       0 ( exists $p{title} ? ( title => $p{title} ) : () ),
    0          
569             },
570             );
571             }
572             }
573              
574             sub horizontal_rule {
575 0     0 0 0 my $self = shift;
576              
577 0         0 $self->_stream_start_tag('hr');
578             }
579              
580             sub _stream_start_tag {
581 32     32   56 my $self = shift;
582 32         57 my $tag = shift;
583 32         51 my $attr = shift;
584              
585             $self->_output->print(
586             '<'
587             . $tag
588             . (
589 32 100       1078 keys %{$attr}
  32         185  
590             ? q{ } . $self->_attributes($attr)
591             : q{}
592             )
593             . '>'
594             );
595             }
596              
597             sub _stream_end_tag {
598 30     30   52 my $self = shift;
599 30         59 my $tag = shift;
600              
601 30         941 $self->_output->print( '</' . $tag . '>' );
602             }
603              
604             sub _stream_text {
605 30     30   58 my $self = shift;
606              
607 30         901 $self->_output->print(
608             encode_entities(
609             shift,
610             $self->_encodable_entities,
611             )
612             );
613             }
614              
615             sub _stream_raw {
616 1     1   2 my $self = shift;
617              
618 1         36 $self->_output->print(shift);
619             }
620              
621             sub _attributes {
622 1     1   2 my $self = shift;
623 1         2 my $attr = shift;
624              
625             return join q{ },
626 2         50 map { $self->_attribute( $_, $attr->{$_} ) }
627 1         2 sort { $a cmp $b } keys %{$attr};
  1         4  
  1         7  
628             }
629              
630             sub _attribute {
631 2     2   4 my $self = shift;
632 2         4 my $key = shift;
633 2         3 my $value = shift;
634              
635 2 50       7 return $key unless defined $value;
636              
637 2         65 return join '=', $key,
638             q{"} . encode_entities(
639             $value,
640             $self->_encodable_entities,
641             ) . q{"};
642             }
643              
644             1;
645              
646             # ABSTRACT: A role for handlers which generate HTML
647              
648             __END__
649              
650             =pod
651              
652             =encoding UTF-8
653              
654             =head1 NAME
655              
656             Markdent::Role::HTMLStream - A role for handlers which generate HTML
657              
658             =head1 VERSION
659              
660             version 0.38
661              
662             =head1 DESCRIPTION
663              
664             This role implements most of the code needed for event receivers which
665             generate a stream of HTML output based on those events.
666              
667             =head1 REQUIRED METHODS
668              
669             This role requires that consuming classes implement two methods, C<<
670             $handler->start_document() >> and C<< $handler->end_document() >>.
671              
672             =head1 ROLES
673              
674             This role does the L<Markdent::Role::EventsAsMethods> and
675             L<Markdent::Role::Handler> roles.
676              
677             =head1 BUGS
678              
679             See L<Markdent> for bug reporting details.
680              
681             Bugs may be submitted at L<https://github.com/houseabsolute/Markdent/issues>.
682              
683             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
684              
685             =head1 SOURCE
686              
687             The source code repository for Markdent can be found at L<https://github.com/houseabsolute/Markdent>.
688              
689             =head1 AUTHOR
690              
691             Dave Rolsky <autarch@urth.org>
692              
693             =head1 COPYRIGHT AND LICENSE
694              
695             This software is copyright (c) 2020 by Dave Rolsky.
696              
697             This is free software; you can redistribute it and/or modify it under
698             the same terms as the Perl 5 programming language system itself.
699              
700             The full text of the license can be found in the
701             F<LICENSE> file included with this distribution.
702              
703             =cut