Commit | Line | Data |
e1b4b35c |
1 | package HTML::String::Value; |
2 | |
3 | use strictures 1; |
586054e0 |
4 | use UNIVERSAL::ref; |
e1b4b35c |
5 | use Safe::Isa; |
6 | use Data::Munge; |
7 | |
e1b4b35c |
8 | use overload |
b8aaa17d |
9 | '""' => '_hsv_escaped_string', |
10 | '.' => '_hsv_dot', |
11 | 'bool' => '_hsv_is_true', |
e1b4b35c |
12 | |
e1b4b35c |
13 | fallback => 1, |
14 | ; |
15 | |
16 | sub 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 |
37 | sub 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 | |
45 | sub _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/</</g; |
57 | s/>/>/g; |
58 | s/"/"/g; |
fc076557 |
59 | s/'/'/g; |
e1b4b35c |
60 | } $_->[0] |
61 | : $_->[0] |
62 | ), @{$self->{parts}}; |
63 | } |
64 | |
b8aaa17d |
65 | sub _hsv_unescaped_string { |
e1b4b35c |
66 | my $self = shift; |
67 | |
68 | return join '', map $_->[0], @{$self->{parts}}; |
69 | } |
70 | |
b8aaa17d |
71 | sub _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 |
93 | sub _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 |
102 | sub 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 | |
113 | sub 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 |
124 | sub ref { '' } |
125 | |
9c9a7ae5 |
126 | sub DESTROY { } |
127 | |
e1b4b35c |
128 | 1; |
d86bdf82 |
129 | |
130 | __END__ |
131 | |
132 | =head1 NAME |
133 | |
134 | HTML::String::Value - A scalar hiding as a string on behalf of L<HTML::String> |
135 | |
136 | =head1 SYNOPSIS |
137 | |
138 | Usually, you'd create this with L<HTML::String>'s L<HTML::String/html> export |
139 | but: |
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 | |
161 | Each 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 | |
171 | Currently, the %options hashref contains only: |
172 | |
173 | ( |
174 | ignore => { 'Package::One' => 1, 'Package::Two' => 1, ... } |
175 | ) |
176 | |
177 | which tells this value object to ignore whether escaping has been requested |
178 | for any particular chunk and instead to provide the unescaped version. |
179 | |
180 | When called on an existing object, does the equivalent of |
181 | |
182 | $self->_hsv_unescaped_string->new(@args); |
183 | |
184 | to fit in with the "pretending to be a class name" behaviour provided by |
185 | L</AUTOLOAD>. |
186 | |
187 | =head2 _hsv_escaped_string |
188 | |
189 | $html->_hsv_escaped_string |
190 | |
191 | Returns a concatenation of all parts of this value with those marked for |
192 | escaping escaped, unless the calling package has been specified in the |
193 | C<ignore> option to L</new>. |
194 | |
195 | If the calling package has been marked as ignoring escaping, returns the |
196 | result of L</_hsv_unescaped_string>. |
197 | |
198 | You probably shouldn't be calling this directly. |
199 | |
200 | =head2 _hsv_unescaped_string |
201 | |
202 | $html->_hsv_unescaped_string |
203 | |
204 | Returns a concatenation of all parts of this value with no escaping performed. |
205 | |
206 | You probably shouldn't be calling this directly. |
207 | |
208 | =head2 _hsv_dot |
209 | |
210 | $html->_hsv_dot($other_string, $reversed) |
211 | |
212 | Returns a new value object consisting of the two values' parts concatenated |
213 | together (in reverse if C<$reversed> is true). |
214 | |
215 | Unlike L</new>, this method defaults to escaping a bare string provided. |
216 | |
217 | You probably shouldn't be calling this directly. |
218 | |
219 | =head2 _hsv_is_true |
220 | |
221 | $html->_hsv_is_true |
222 | |
223 | Returns true if any of this value's parts are true. |
224 | |
225 | You probably shouldn't be calling this directly. |
226 | |
227 | =head2 AUTOLOAD |
228 | |
229 | $html->method_name(@args) |
230 | |
231 | This calls the equivalent of |
232 | |
233 | $html->_hsv_unescaped_string->method_name(@args) |
234 | |
235 | to allow for class method calls even when the class name has ended up being |
236 | turned into a value object. |
237 | |
238 | =head2 isa |
239 | |
240 | $html->isa($name) |
241 | |
242 | This method returns true if either the value or the unescaped string are |
243 | isa the supplied C<$name> in order to allow for class method calls even when |
244 | the class name has ended up being turned into a value object. |
245 | |
246 | =head2 can |
247 | |
248 | $html->can($name) |
249 | |
250 | This method returns a coderef if either the value or the unescaped string |
251 | provides this method; methods on the unescaped string are preferred to allow |
252 | for class method calls even when the class name has ended up being |
253 | turned into a value object. |
254 | |
255 | =head2 ref |
256 | |
257 | $html->ref |
258 | |
259 | This method always returns C<''>. Since we register ourselves with |
260 | L<UNIVERSAL::ref>, this means that |
261 | |
262 | ref($html); |
263 | |
264 | will also return C<''>, which means that modules loaded after this one will |
265 | see a value object as being a plain scalar unless they're explicitly checking |
266 | the defined-ness of the return value of C<ref>, which probably means that they |
267 | wanted to spot people cheating like we're trying to. |
268 | |
269 | If you have trouble with things trying to treat a value object as something |
270 | other than a string, try loading L<UNIVERSAL::ref> earlier. |
271 | |
272 | =head2 DESTROY |
273 | |
274 | Overridden to do nothing so that L</AUTOLOAD> doesn't trap it. |
275 | |
276 | =head1 OPERATOR OVERLOADS |
277 | |
278 | =head2 stringification |
279 | |
280 | Stringification is overloaded to call L</_hsv_escaped_string> |
281 | |
282 | =head2 concatenation |
283 | |
284 | Concatentation is overloaded to call L</_hsv_dot> |
285 | |
286 | =head2 boolification |
287 | |
288 | Boolification is overloaded to call L</_hsv_is_true> |
289 | |
290 | =head1 AUTHORS |
291 | |
292 | See L<HTML::String> for authors. |
293 | |
294 | =head1 COPYRIGHT AND LICENSE |
295 | |
296 | See L<HTML::String> for the copyright and license. |
297 | |
298 | =cut |