File Coverage

blib/lib/String/Tagged/HTML.pm
Criterion Covered Total %
statement 54 56 96.4
branch 19 24 79.1
condition n/a
subroutine 8 8 100.0
pod 2 2 100.0
total 83 90 92.2


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 -- leonerd@leonerd.org.uk
5              
6             package String::Tagged::HTML;
7              
8 4     4   92395 use strict;
  4         138  
  4         160  
9 4     4   21 use warnings;
  4         7  
  4         161  
10              
11 4     4   27 use base qw( String::Tagged );
  4         7  
  4         4928  
12             String::Tagged->VERSION( '0.07' );
13              
14             our $VERSION = '0.01';
15              
16             =head1 NAME
17              
18             C - format HTML output using C
19              
20             =head1 SYNOPSIS
21              
22             use String::Tagged::HTML;
23              
24             my $st = String::Tagged::HTML->new( "An important message" );
25              
26             $st->apply_tag( 3, 9, b => 1 );
27              
28             print $st->as_html( "h1" );
29              
30             =head1 DESCRIPTION
31              
32             This subclass of L provides a method, C, for rendering
33             the string as an HTML fragment, using the tags to provide formatting. For
34             example, the SYNOPSIS example will produce the output
35              
36            

An important message

37              
38             With the exception of tags named C, a tag applied to an extent of the
39             C will be rendered using start and end HTML tags of the
40             same name. If the tag's value is a C reference, then this hash will be
41             used to provide additional attributes for the HTML element.
42              
43             my $str = String::Tagged::HTML->new( "click here" );
44             $str->apply_tag( 6, 4, a => { href => "/see/other.html" } );
45              
46             print $str->as_html( "p" );
47              
48             Z<>
49              
50            

click here

51              
52             If it is not a C reference, then its value ought to be a simple boolean
53             true value, such as C<1>.
54              
55             The special tag named C disables HTML entity escaping over its extent.
56              
57             my $str = String::Tagged::HTML->new( "This is escaped" );
58              
59             my $br = String::Tagged::HTML->new( "
" );
60             $br->apply_tag( 0, $br->length, raw => 1 );
61              
62             print +( $str . $br )->as_html( "p" );
63              
64             Z<>
65              
66            

This <content> is escaped

67              
68             =head2 Tag Nesting
69              
70             Because of the arbitrary way that C tags may be applied, as
71             compared to the strict nesting requirements in HTML, the C method may
72             have to break a single C tag into multiple regions. In the
73             following example, the C tag has been split in two to allow it to overlap
74             correctly with C.
75              
76             my $str = String::Tagged::HTML->new( "bbb b+i iii" );
77             $str->apply_tag( 0, 7, b => 1 );
78             $str->apply_tag( 4, 7, i => 1 );
79              
80             print $str->as_html
81              
82             Z<>
83              
84             bbb b+i iii
85              
86             =cut
87              
88             =head1 CONSTRUCTORS
89              
90             As well as the standard C and C constructors provided by
91             L, the following is provided.
92              
93             =cut
94              
95             =head2 $st = String::Tagged::HTML->new_raw( $str )
96              
97             Returns a new C instance with the C tag applied
98             over its entire length. This convenience is provided for creating objects
99             containing already-rendered HTML fragments.
100              
101             =cut
102              
103             sub new_raw
104             {
105 1     1 1 3 my $class = shift;
106 1         2 my ( $str ) = @_;
107 1         11 return $class->new_tagged( $str, raw => 1 );
108             }
109              
110             =head1 METHODS
111              
112             The following methods are provided in addition to those provided by
113             L.
114              
115             =cut
116              
117             sub _escape_html
118             {
119 24     24   33 my $s = $_[0];
120 24 50       51 $s =~ s/([<>&"'])/$1 eq "<" ? "<" :
  8 100       45  
    100          
    100          
    100          
121             $1 eq ">" ? ">" :
122             $1 eq "&" ? "&" :
123             $1 eq '"' ? """ :
124             $1 eq "'" ? "'" : ""/eg;
125 24         88 $s;
126             }
127              
128             sub _cmp_tag_values
129             {
130 3     3   4 my $self = shift;
131 3         6 my ( $name, $v1, $v2 ) = @_;
132              
133 3 50       5 return ( $v1 == $v2 ) if grep { $name eq $_ } qw( b i u small );
  12         33  
134 0 0       0 return ( $v1->{href} eq $v2->{href} ) if $name eq "a";
135 0         0 die "Unknown tag name $name\n";
136             }
137              
138             =head2 $html = $st->as_html( $element )
139              
140             Returns a string containing an HTML rendering of the current contents of the
141             object. If C<$element> is provided, the output will be wrapped in an element
142             of the given name. If not defined, no outer wrapping will be performed.
143              
144             =cut
145              
146             sub as_html
147             {
148 14     14 1 1721 my $self = shift;
149 14         24 my ( $elem ) = @_;
150              
151 14         19 my $ret = "";
152              
153 14         14 my @tags_in_effect; # of [ $name, $value ]
154              
155             $self->iter_extents_nooverlap(
156             sub {
157 20     20   769 my ( $e, %tags ) = @_;
158              
159             # Look for the first tag that no longer applies, as we'll have to
160             # unwind the entire tag stack to that point
161              
162 20         24 my $i;
163 20         59 for( $i = 0; $i < @tags_in_effect; $i++ ) {
164 5         6 my ( $tag, $value ) = @{ $tags_in_effect[$i] };
  5         9  
165 5 100       12 last if !exists $tags{$tag};
166 3 50       8 last if !$self->_cmp_tag_values( $tag, $value, $tags{$tag} );
167 3         12 delete $tags{$tag};
168             }
169              
170 20         51 while( @tags_in_effect > $i ) {
171 3         5 my ( $tag ) = @{ pop @tags_in_effect };
  3         3  
172 3         9 $ret .= "";
173             }
174              
175             # TODO: Sort these into an optimal order
176 20         45 foreach my $tag ( keys %tags ) {
177 12         16 my $value = $tags{$tag};
178 12 100       31 if( ref $value eq "HASH" ) {
179 3         12 my $attrs = join "", map { qq( $_=") . _escape_html($value->{$_}) . q(") } sort keys %$value;
  3         561  
180 3         22 $ret .= "<$tag$attrs>";
181             }
182             else {
183 9         16 $ret .= "<$tag>";
184             }
185 12         39 push @tags_in_effect, [ $tag, $value ];
186             }
187              
188             $self->iter_substr_nooverlap(
189             sub {
190 23         1206 my ( $str, %tags ) = @_;
191 23 100       68 $ret .= ( $tags{raw} ? $str : _escape_html( $str ) );
192             },
193 20         108 start => $e->start,
194             end => $e->end,
195             );
196             },
197 14         125 except => [qw( raw )],
198             );
199              
200 14         401 while( @tags_in_effect ) {
201 9         11 my ( $tag ) = @{ pop @tags_in_effect };
  9         18  
202 9         31 $ret .= "";
203             }
204              
205 14 100       35 return "<$elem>$ret" if defined $elem;
206 13         67 return "$ret";
207             }
208              
209             =head1 AUTHOR
210              
211             Paul Evans
212              
213             =cut
214              
215             0x55AA;