Localize $@ in isa and can in case of modules that mishandle eval {}
[scpubgit/HTML-String.git] / lib / HTML / String / Value.pm
CommitLineData
e1b4b35c 1package HTML::String::Value;
2
3use strictures 1;
586054e0 4use UNIVERSAL::ref;
e1b4b35c 5use Safe::Isa;
6use Data::Munge;
7
e1b4b35c 8use overload
b8aaa17d 9 '""' => '_hsv_escaped_string',
10 '.' => '_hsv_dot',
11 'bool' => '_hsv_is_true',
e1b4b35c 12
e1b4b35c 13 fallback => 1,
14;
15
16sub new {
b8aaa17d 17 if (ref($_[0])) { my $c = shift; return $c->_hsv_unescaped_string->new(@_) }
e1b4b35c 18 my ($class, @raw_parts) = @_;
19
f27b509e 20 my $opts = (ref($raw_parts[-1]) eq 'HASH') ? pop(@raw_parts) : {};
21
51eaef0b 22 my @parts = map {
23 if (ref($_) eq 'ARRAY') {
24 $_
25 } elsif ($_->$_isa(__PACKAGE__)) {
26 @{$_->{parts}}
27 } else {
28 [ $_, 0 ]
29 }
30 } @raw_parts;
e1b4b35c 31
f27b509e 32 my $self = bless { parts => \@parts, %$opts }, $class;
e1b4b35c 33
34 return $self;
35}
36
b8aaa17d 37sub AUTOLOAD {
38 my $invocant = shift;
39 (my $meth = our $AUTOLOAD) =~ s/.*:://;
40 die "No such method ${meth} on ${invocant}"
41 unless ref($invocant);
42 return $invocant->_hsv_unescaped_string->$meth(@_);
43}
44
45sub _hsv_escaped_string {
e1b4b35c 46 my $self = shift;
47
f27b509e 48 if ($self->{ignore}{scalar caller}) {
b8aaa17d 49 return $self->_hsv_unescaped_string;
f27b509e 50 }
51
e1b4b35c 52 return join '', map +(
53 $_->[1]
54 ? byval {
55 s/&/&/g;
56 s/</&lt;/g;
57 s/>/&gt;/g;
58 s/"/&quot;/g;
fc076557 59 s/'/&#39;/g;
e1b4b35c 60 } $_->[0]
61 : $_->[0]
62 ), @{$self->{parts}};
63}
64
b8aaa17d 65sub _hsv_unescaped_string {
e1b4b35c 66 my $self = shift;
67
68 return join '', map $_->[0], @{$self->{parts}};
69}
70
b8aaa17d 71sub _hsv_dot {
e1b4b35c 72 my ($self, $str, $prefix) = @_;
73
74 return $self unless $str;
75
76 my @parts = @{$self->{parts}};
77
78 my @new_parts = (
79 $str->$_isa(__PACKAGE__)
80 ? @{$str->{parts}}
81 : [ $str, 1 ]
82 );
83
84 if ( $prefix ) {
85 unshift @parts, @new_parts;
86 } else {
87 push @parts, @new_parts;
88 }
89
f27b509e 90 return ref($self)->new(@parts, { ignore => $self->{ignore} });
91}
92
b8aaa17d 93sub _hsv_is_true {
f27b509e 94 my ($self) = @_;
d86bdf82 95 return 1 if grep $_, map $_->[0], @{$self->{parts}};
96}
97
ac4c210b 98# we need to local $@ here because some modules (cough, TT, cough)
99# will do a 'die $@ if $@' without realising that it wasn't their eval
100# that set it
101
d86bdf82 102sub isa {
103 my $self = shift;
104 return (
ac4c210b 105 do {
106 local $@;
107 eval { blessed($self) and $self->_hsv_unescaped_string->isa(@_) }
108 }
d86bdf82 109 or $self->SUPER::isa(@_)
110 );
111}
112
113sub can {
114 my $self = shift;
115 return (
ac4c210b 116 do {
117 local $@;
118 eval { blessed($self) and $self->_hsv_unescaped_string->isa(@_) }
119 }
d86bdf82 120 or $self->SUPER::can(@_)
121 );
e1b4b35c 122}
123
586054e0 124sub ref { '' }
125
9c9a7ae5 126sub DESTROY { }
127
e1b4b35c 1281;
d86bdf82 129
130__END__
131
132=head1 NAME
133
134HTML::String::Value - A scalar hiding as a string on behalf of L<HTML::String>
135
136=head1 SYNOPSIS
137
138Usually, you'd create this with L<HTML::String>'s L<HTML::String/html> export
139but:
140
141 my $html = HTML::String::Value->new($do_not_escape_this);
142
143 my $html = HTML::String::Value->new([ $do_not_escape_this, 0 ]);
144
145 my $html = HTML::String::Value->new([ $do_escape_this, 1 ]);
146
147 my $html = HTML::String::Value->new($already_an_html_string_value);
148
149 my $html = HTML::String::Value->new(@an_array_of_any_of_the_above);
150
151 my $html = HTML::String::Value->new(
152 @parts, { ignore => { package_name => 1 } }
153 );
154
155=head1 METHODS
156
157=head2 new
158
159 my $html = HTML::String::Value->new(@parts, \%options?);
160
161Each entry in @parts consists of one of:
162
163 'some text that will not be escaped'
164
165 [ 'some text that will not be escaped', 0 ]
166
882026d2 167 [ 'text that you DO want to be escaped', 1 ]
d86bdf82 168
169 $existing_html_string_value
170
171Currently, the %options hashref contains only:
172
173 (
174 ignore => { 'Package::One' => 1, 'Package::Two' => 1, ... }
175 )
176
177which tells this value object to ignore whether escaping has been requested
178for any particular chunk and instead to provide the unescaped version.
179
180When called on an existing object, does the equivalent of
181
182 $self->_hsv_unescaped_string->new(@args);
183
184to fit in with the "pretending to be a class name" behaviour provided by
185L</AUTOLOAD>.
186
187=head2 _hsv_escaped_string
188
189 $html->_hsv_escaped_string
190
191Returns a concatenation of all parts of this value with those marked for
192escaping escaped, unless the calling package has been specified in the
193C<ignore> option to L</new>.
194
195If the calling package has been marked as ignoring escaping, returns the
196result of L</_hsv_unescaped_string>.
197
198You probably shouldn't be calling this directly.
199
200=head2 _hsv_unescaped_string
201
202 $html->_hsv_unescaped_string
203
204Returns a concatenation of all parts of this value with no escaping performed.
205
206You probably shouldn't be calling this directly.
207
208=head2 _hsv_dot
209
210 $html->_hsv_dot($other_string, $reversed)
211
212Returns a new value object consisting of the two values' parts concatenated
213together (in reverse if C<$reversed> is true).
214
215Unlike L</new>, this method defaults to escaping a bare string provided.
216
217You probably shouldn't be calling this directly.
218
219=head2 _hsv_is_true
220
221 $html->_hsv_is_true
222
223Returns true if any of this value's parts are true.
224
225You probably shouldn't be calling this directly.
226
227=head2 AUTOLOAD
228
229 $html->method_name(@args)
230
231This calls the equivalent of
232
233 $html->_hsv_unescaped_string->method_name(@args)
234
235to allow for class method calls even when the class name has ended up being
236turned into a value object.
237
238=head2 isa
239
240 $html->isa($name)
241
242This method returns true if either the value or the unescaped string are
243isa the supplied C<$name> in order to allow for class method calls even when
244the class name has ended up being turned into a value object.
245
246=head2 can
247
248 $html->can($name)
249
250This method returns a coderef if either the value or the unescaped string
251provides this method; methods on the unescaped string are preferred to allow
252for class method calls even when the class name has ended up being
253turned into a value object.
254
255=head2 ref
256
257 $html->ref
258
259This method always returns C<''>. Since we register ourselves with
260L<UNIVERSAL::ref>, this means that
261
262 ref($html);
263
264will also return C<''>, which means that modules loaded after this one will
265see a value object as being a plain scalar unless they're explicitly checking
266the defined-ness of the return value of C<ref>, which probably means that they
267wanted to spot people cheating like we're trying to.
268
269If you have trouble with things trying to treat a value object as something
270other than a string, try loading L<UNIVERSAL::ref> earlier.
271
272=head2 DESTROY
273
274Overridden to do nothing so that L</AUTOLOAD> doesn't trap it.
275
276=head1 OPERATOR OVERLOADS
277
278=head2 stringification
279
280Stringification is overloaded to call L</_hsv_escaped_string>
281
282=head2 concatenation
283
284Concatentation is overloaded to call L</_hsv_dot>
285
286=head2 boolification
287
288Boolification is overloaded to call L</_hsv_is_true>
289
290=head1 AUTHORS
291
292See L<HTML::String> for authors.
293
294=head1 COPYRIGHT AND LICENSE
295
296See L<HTML::String> for the copyright and license.
297
298=cut