File Coverage

blib/lib/HTML/Native/Attribute.pm
Criterion Covered Total %
statement 75 92 81.5
branch 19 44 43.1
condition 3 9 33.3
subroutine 18 18 100.0
pod 0 6 0.0
total 115 169 68.0


line stmt bran cond sub pod time code
1             package HTML::Native::Attribute;
2              
3             # Copyright (C) 2011 Michael Brown .
4             #
5             # This program is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as
7             # published by the Free Software Foundation; either version 2 of the
8             # License, or any later version.
9             #
10             # This program is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13             # General Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License
16             # along with this program; if not, write to the Free Software
17             # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18              
19             =head1 NAME
20              
21             HTML::Native::Attribute - An HTML element attribute
22              
23             =head1 SYNOPSIS
24              
25             use HTML::Native;
26              
27             my $elem = HTML::Native->new (
28             a => { class => "active", href => "/home" },
29             "Home"
30             );
31             my $attr = $elem->{class};
32             $attr->{default} = 1;
33             print $attr;
34             # prints "active default"
35              
36              
37             use HTML::Native::Attribute;
38              
39             my $attr = HTML::Native::Attribute->new ( [ qw ( active default ) ] );
40             $attr->{default} = 0;
41             print $attr;
42             # prints "active"
43              
44             =head1 DESCRIPTION
45              
46             An L object represents an HTML element
47             attribute belonging to an L object. It will be created
48             automatically by L as necessary; you probably do B
49             ever need to manually create an L object.
50              
51             You can treat an L object as a magic variable
52             that provides access to the attribute value as either a string:
53              
54             "active default"
55              
56             or as an array:
57              
58             [ "active", "default" ]
59              
60             or as a hash
61              
62             { active => 1, default => 1 }
63              
64             This allows you to always use the most natural way of accessing the
65             attribute value.
66              
67             The underlying stored value for the attribute will be converted
68             between a scalar, a hash and an array as required.
69              
70             =cut
71              
72 1     1   980 use HTML::Entities;
  1         8756  
  1         129  
73 1     1   13 use Scalar::Util qw ( blessed );
  1         7  
  1         79  
74 1     1   6 use Carp;
  1         1  
  1         62  
75 1     1   1607 use HTML::Native::Attribute::ReadOnlyHash;
  1         3  
  1         28  
76 1     1   1629 use HTML::Native::Attribute::ReadOnlyArray;
  1         3  
  1         34  
77 1     1   7 use strict;
  1         10  
  1         28  
78 1     1   4 use warnings;
  1         2  
  1         145  
79              
80             use overload
81 1     1   2119 '""' => sub { my $self = shift; return $self->stringify; },
  1         11  
82 3     3   427 '%{}' => sub { my $self = shift; return $self->hash; },
  3         181  
83 2     2   103 '@{}' => sub { my $self = shift; return $self->array; },
  2         8  
84 1     1   1966 fallback => 1;
  1         4379  
  1         16  
85              
86             sub new {
87 1     1 0 25 my $old = shift;
88 1   33     9 my $class = ref $old || $old;
89 1         3 my $value = shift;
90              
91 1     6   9 my $self = sub { \$value; };
  6         12  
92 1         4 bless $self, $class;
93 1         4 return $self;
94             }
95              
96             =head2 GENERATED HTML (STRINGIFICATION)
97              
98             You can treat the L object as a string in
99             order to obtain the generated HTML. For example:
100              
101             my $elem = HTML::Native->new (
102             a => { class => [ qw ( active default ) ], href => "/home" },
103             "Home"
104             );
105             my $attr = $elem->{class};
106             print $attr;
107             # prints "active default"
108              
109             =head3 FROM A SCALAR
110              
111             If the attribute is currently stored as a scalar, then it will be used
112             verbatim as the stringified value.
113              
114             =head3 FROM AN ARRAY
115              
116             If the attribute is currently stored as an array, then the stringified
117             value will be the space-separated members of the array. For example,
118             if the attribute is currently stored as
119              
120             [ active default ]
121              
122             then the stringified value will be
123              
124             "active default"
125              
126             =head3 FROM A HASH
127              
128             If the attribute is currently stored as a hash, then the stringified
129             value will be the sorted, space-separated keys of the hash
130             corresponding to true values. For example, if the attribute is
131             currently stored as
132              
133             { active => 1, default => 1, error => 0 }
134              
135             then the stringified value will be
136              
137             "active default"
138              
139             =cut
140              
141             sub stringify {
142 1     1 0 3 my $self = shift;
143 1   33     6 my $class = ref $self || $self;
144              
145             # Retrieve current value
146 1         5 my $ref = &$self;
147 1         4 my $value = $$ref;
148              
149             # Value is a code block: execute it and use the result
150 1 50       24 if ( ref $value eq "CODE" ) {
151 0         0 $value = &$value;
152             # If generated value is an object, let it stringify itself
153 0 0       0 return $value."" if blessed ( $value );
154             }
155              
156             # Convert value to a scalar if necessary
157 1 50       5 if ( ref $value ) {
158 1 50       4 if ( ref $value eq "ARRAY" ) {
    0          
159             # Value is an array: use a space-separated list of array members
160 1         4 $value = join ( " ", @$value );
161             } elsif ( ref $value eq "HASH" ) {
162             # Value is a hash: return a space-separated list of sorted hash
163             # keys with true values
164 0         0 $value = join ( " ", sort grep { $value->{$_} } keys %$value );
  0         0  
165             } else {
166 0         0 croak "Cannot convert ".( ref $value )." attribute to SCALAR";
167             }
168             }
169              
170 1 50       11 return ( defined $value ? encode_entities ( $value ) : "" );
171             }
172              
173             =head2 ACCESS AS A HASH
174              
175             You can treat the L object as a hash in order
176             to test or set individual values within the attribute. For example:
177              
178             if ( $elem->{class}->{error} ) {
179             ...
180             }
181              
182             $elem->{class}->{fatal} = 1;
183              
184             This makes sense only for attributes such as C which consist of
185             a set of individual values. It does not make sense to treat an
186             attribute such as C or C as a hash.
187              
188             =head3 FROM A SCALAR
189              
190             If the attribute is currently stored as a scalar, then it will be
191             converted into a hash using the current value as the hash key. For
192             example, if the attribute is currently stored as
193              
194             "active"
195              
196             then it will be converted to the hash
197              
198             { active => 1 }
199              
200             =head3 FROM AN ARRAY
201              
202             If the attribute is currently stored as an array, then it will be
203             converted into a hash using the array members as the hash keys. For
204             example, if the attribute is currently stored as
205              
206             [ "active", "default" ]
207              
208             then it will be converted to the hash
209              
210             { active => 1, default => 1 }
211              
212             Note that this conversion is potentially B, since it will
213             lose information about the order of the array members and will
214             implicitly eliminate any duplicates. You should therefore only use
215             hash access for attributes such as C for which the order of
216             individual values is irrelevant.
217              
218             =cut
219              
220             sub hash {
221 3     3 0 38 my $self = shift;
222 3   33     11 my $class = ref $self || $self;
223              
224             # Retrieve current value
225 3         7 my $ref = &$self;
226 3         5 my $value = $$ref;
227              
228             # Value is a code block: execute it and use the result as a
229             # read-only value
230 3 50       11 if ( ref $value eq "CODE" ) {
231 0         0 $value = &$value;
232             # If generated value is an object, let it convert itself to a hash
233 0 0       0 return \%$value if blessed ( $value );
234             # Prevent modification of current value (i.e. treat result as
235             # read-only)
236 0         0 undef $ref;
237             }
238              
239             # Convert value to a hash if necessary
240 3 100       9 if ( ref $value ne "HASH" ) {
241 2 50       11 if ( ! defined $value ) {
    50          
    50          
242             # Value is undefined: use an empty hash
243 0         0 $value = {};
244             } elsif ( ! ref $value ) {
245             # Value is a scalar: use as a hash key with a true value
246 0         0 $value = { $value => 1 };
247             } elsif ( ref $value eq "ARRAY" ) {
248             # Value is an array: use elements as hash keys with true values
249 2         5 $value = { map { $_ => 1 } @$value };
  4         14  
250             } else {
251 0         0 croak "Cannot convert ".( ref $value )." attribute to HASH";
252             }
253             # Rewrite the current value as the hash (unless read-only)
254 2 50       17 $$ref = $value if $ref;
255             }
256              
257             # Convert to a read-only hash if applicable
258 3 50       8 $value = $self->new_readonly_hash ( $value ) unless $ref;
259              
260 3         10 return $value;
261             }
262              
263             sub new_readonly_hash {
264 3     3 0 5 my $self = shift;
265 3         4 my $value = shift;
266              
267 3         17 return HTML::Native::Attribute::ReadOnlyHash->new ( $value );
268             }
269              
270             =head2 ACCESS AS AN ARRAY
271              
272             You can treat the L object as an array. For
273             example:
274              
275             push @{$elem->{onclick}},
276             "alert('Clicked');",
277             "return false;"
278              
279             push @{$elem->{class}}, "active";
280              
281             =head3 FROM A SCALAR
282              
283             If the attribute is currently stored as a scalar, then it will be
284             converted into an array using the current value as the array member.
285             For example, if the attribute is currently stored as
286              
287             "active"
288              
289             then it will be converted to the array
290              
291             [ "active" ]
292              
293             =head3 FROM A HASH
294              
295             If the attribute is currently stored as a hash, then it will be
296             converted into an array of the sorted keys of the hash corresponding
297             to true values. For example, if the attribute is currently stored as
298              
299             { active => 1, default => 1, error => 0 }
300              
301             then it will be converted to the array
302              
303             [ "active", "default" ]
304              
305             =cut
306              
307             sub array {
308 2     2 0 23 my $self = shift;
309              
310             # Retrieve current value
311 2         5 my $ref = &$self;
312 2         4 my $value = $$ref;
313              
314             # Value is a code block: execute it and use the result as a
315             # read-only value
316 2 50       8 if ( ref $value eq "CODE" ) {
317 0         0 $value = &$value;
318             # If generated value is an object, let it convert itself to an array
319 0 0       0 return \@$value if blessed ( $value );
320             # Prevent modification of current value (i.e. treat result as
321             # read-only)
322 0         0 undef $ref;
323             }
324              
325             # Convert value to an array if necessary
326 2 50       7 if ( ref $value ne "ARRAY" ) {
327 2 50       11 if ( ! defined $value ) {
    50          
    50          
328             # Value is undefined: use an empty array
329 0         0 $value = [];
330             } elsif ( ! ref $value ) {
331             # Value is a scalar: use as an array element
332 0         0 $value = [ $value ];
333             } elsif ( ref $value eq "HASH" ) {
334             # Value is a hash: use sorted hash keys with true values
335 2         7 $value = [ sort grep { $value->{$_} } keys %$value ];
  4         16  
336             } else {
337 0         0 croak "Cannot convert ".( ref $value )." attribute to ARRAY";
338             }
339             # Rewrite the current value as the array (unless read-only)
340 2 50       7 $$ref = $value if $ref;
341             }
342              
343             # Convert to a read-only array if applicable
344 2 50       9 $value = $self->new_readonly_array ( $value ) unless $ref;
345              
346 2         7 return $value;
347             }
348              
349             sub new_readonly_array {
350 2     2 0 4 my $self = shift;
351 2         4 my $value = shift;
352              
353 2         12 return HTML::Native::Attribute::ReadOnlyArray->new ( $value );
354             }
355              
356             =head1 NOTES
357              
358             For attributes such as C that you may want to access as a hash,
359             you should avoid directly storing the value as a space-separated
360             string. For example, do not use:
361              
362             $elem->{class} = "active default";
363              
364             since that would end up being converted into the hash
365              
366             { "active default" => 1 }
367              
368             rather than
369              
370             { active => 1, default => 1 }
371              
372             To store multiple values, use either an array:
373              
374             $elem->{class} = [ qw ( active default ) ];
375              
376             or a hash
377              
378             $elem->{class} = { active => 1, default => 1 };
379              
380             =head1 ADVANCED
381              
382             =head2 DYNAMIC GENERATION
383              
384             You can use anonymous subroutines (closures) to dynamically generate
385             attribute values. For example:
386              
387             my $url;
388             my $elem = HTML::Native->new (
389             a => {
390             class => "active",
391             href => sub { return $url; },
392             },
393             "Home"
394             );
395             $url = "/home";
396             print $elem;
397             # prints "Home"
398              
399             The subroutine can return either a fully-constructed
400             L object, or a value that could be passed to
401             C<< HTML::Native::Attribute->new() >>. For example:
402              
403             sub {
404             return HTML::Native::Attribute::ReadOnly->new (
405             [ active default ]
406             );
407             }
408              
409             or
410              
411             sub {
412             return ( [ active default ] );
413             }
414              
415             A dynamically generated attribute value can still be accessed as a
416             hash or as an array. For example:
417              
418             my $elem = HTML::Native->new (
419             a => {
420             class => sub { return ( [ active default ] ) },
421             href => "/home",
422             },
423             "Home"
424             );
425             print "Active" if $elem->{class}->{active}; # prints "Active"
426              
427             L has no way to inform the anonymous
428             subroutine that its returned value should change. For example:
429              
430             my $attr = HTML::Native::Attribute->new ( sub {
431             my @classes = ( qw ( active default ) );
432             return [ @classes ];
433             } );
434              
435             print $attr; # prints "active default"
436             $attr->{active} = 0; # <-- PROBLEM!
437              
438             The dynamically generated attribute will therefore be marked as a
439             read-only hash or array:
440              
441             $attr->{active} = 0; # will die with an error message
442              
443             B your anonymous subroutine returns a fully-constructed
444             L object, then it should probably use
445             L to ensure this behaviour. For
446             example:
447              
448             sub {
449             return HTML::Native::Attribute::ReadOnly->new (
450             [ active default ]
451             );
452             }
453              
454             =cut
455              
456             1;