File Coverage

blib/lib/Data/JavaScript.pm
Criterion Covered Total %
statement 98 108 90.7
branch 34 46 73.9
condition 7 12 58.3
subroutine 10 11 90.9
pod 2 2 100.0
total 151 179 84.3


line stmt bran cond sub pod time code
1             package Data::JavaScript; ## no critic (PodSpelling)
2              
3 9     9   2094162 use Modern::Perl;
  9         61  
  9         75  
4 9     9   6390 use Readonly;
  9         36906  
  9         497  
5 9     9   143 use Scalar::Util 'reftype';
  9         22  
  9         5300  
6              
7             our $VERSION = q/1.14/;
8              
9             # Exporter
10             Readonly our @EXPORT => qw(jsdump hjsdump);
11             Readonly our @EXPORT_OK => '__quotemeta';
12             Readonly our %EXPORT_TAGS => (
13               all => [ @EXPORT, @EXPORT_OK ],
14               compat => [@EXPORT],
15             );
16              
17             # Magic numbers
18             Readonly my $MIN_ENCODE_REQUIRE_BREAKPOINT => 5.007;
19             Readonly my $JSCOMPAT_DEFAULT_VERSION => 1.3;
20             Readonly my $JSCOMPAT_UNDEFINED_MISSING => 1.2;
21              
22             # This is a context variable which holds on to configs.
23             my %opt = ( JS => $JSCOMPAT_DEFAULT_VERSION ); # TODO: This is super out-dated.
24              
25             if ( !$] < $MIN_ENCODE_REQUIRE_BREAKPOINT ) { require Encode; }
26              
27             sub import {
28 9     9   82   my ( $package, @args ) = @_;
29              
30             # Let's get the stuff we're going to import
31 9         21   my @explicit_imports = ();
32 9         17   my @import = ();
33 9         26   my %allowable = map { $_ => 1 } ( @EXPORT, @EXPORT_OK );
  27         242  
34              
35 9         33   for my $arg (@args) {
36 8 100       56     if ( ref $arg eq 'HASH' ) {
    50          
37 2 100       11       if ( exists $arg->{JS} ) { $opt{JS} = $arg->{JS}; }
  1         5  
38 2 100       8       if ( exists $arg->{UNDEF} ) { $opt{UNDEF} = $arg->{UNDEF}; }
  1         2  
39                 }
40                 elsif ( not ref $arg ) {
41 6         20       push @explicit_imports, $arg;
42                 }
43               }
44 9 100 33     65   $opt{UNDEF} ||= $opt{JS} > $JSCOMPAT_UNDEFINED_MISSING ? 'undefined' : q('');
45              
46             #use (); #imports nothing, as package is not supplied
47 9 50       101   if ( defined $package ) {
  0         0  
48              
49 9 100       31     if ( scalar @explicit_imports ) {
50              
51             # Run through the explicitly exported symbols
52 6         12       for my $explicit_import (@explicit_imports) {
53              
54             # Looks like a tag
55 6 50       21         if ( substr( $explicit_import, 0, 1 ) eq q/:/ ) {
    0          
56 6         18           my $tag = substr $explicit_import, 1;
57              
58             # Only do things for the actually exported tags.
59 6 50       24           if ( not exists $EXPORT_TAGS{$tag} ) { next; }
  0         0  
60 6         72           push @import, @{ $EXPORT_TAGS{$tag} };
  6         25  
61                     }
62              
63             # Not a tag
64                     elsif ( exists $allowable{$explicit_import} ) {
65              
66             #only user-specfied subset of @EXPORT, @EXPORT_OK
67 0         0           push @import, $explicit_import;
68                     }
69                   }
70                 }
71                 else {
72 3         8       @import = @EXPORT;
73                 }
74              
75 9         180     my $caller = caller;
76 9     9   75     no strict 'refs'; ## no critic (ProhibitNoStrict)
  9         20  
  9         774  
77 9         20     for my $func (@import) {
78 23         37       *{"$caller\::$func"} = \&{$func};
  23         108  
  23         51  
79                 }
80 9     9   63     use strict 'refs';
  9         20  
  9         4547  
81               }
82              
83 9         16270   return;
84             }
85              
86             sub hjsdump {
87 0     0 1 0   my @input = @_;
88              
89 0         0   my @res = (
90                 qq(<script type="text/javascript" language="JavaScript$opt{JS}" />),
91                 '<!--', jsdump(@input), '// -->', '</script>',
92               );
93 0 0       0   return wantarray ? @res : join qq/\n/, @res, q//;
94             }
95              
96             sub jsdump {
97 21     21 1 3347   my ( $sym, @input ) = @_;
98              
99 21 50       86   return "var $sym;\n" if ( not scalar @input );
100 21         58   my ( $elem, $undef ) = @input;
101 21         39   my %dict = ();
102 21         66   my @res = __jsdump( $sym, $elem, \%dict, $undef );
103 21         71   $res[0] = qq/var $res[0]/;
104 21 100       192   return wantarray ? @res : join qq/\n/, @res, q//;
105             }
106              
107             ## RJEWKno critic (RequireCheckingReturnValueOfEval, RequireInterpolationOfMetachars, ProhibitStringyEval)
108             sub __quotemeta {
109 32     32   6166   my ($input) = @_;
110              
111             ## ENCODER!
112 32 50       134   if ( $] < $MIN_ENCODE_REQUIRE_BREAKPOINT ) {
113 0         0     $input =~ s{
114             ([^ \x21-\x5B\x5D-\x7E]+)
115             }{
116 0         0 sprintf(join('', '\x%02X' x length$1), unpack'C*',$1)
117             }gexsm;
118               }
119               else {
120 32 100 100     262     if ( $opt{JS} >= $JSCOMPAT_DEFAULT_VERSION && Encode::is_utf8($input) ) {
121 4         67       $input =~ s{
122             ([\x{0080}-\x{fffd}]+)
123             }{
124 7         42 sprintf '\u%0*v4X', '\u', $1
125             }gexms;
126                 }
127              
128                 {
129 9     9   77       use bytes;
  9         20  
  9         73  
  32         244  
130 32         154       $input =~ s{
131             ((?:[^ \x21-\x7E]|(?:\\(?!u)))+)
132             }{
133 12         90 sprintf '\x%0*v2X', '\x', $1
134             }gexms;
135                 }
136              
137               }
138              
139             #This is kind of ugly/inconsistent output for munged UTF-8
140             #tr won't work because we need the escaped \ for JS output
141 32         80   $input =~ s/\\x09/\\t/gxms;
142 32         64   $input =~ s/\\x0A/\\n/gxms;
143 32         55   $input =~ s/\\x0D/\\r/gxms;
144 32         59   $input =~ s/"/\\"/gxms;
145 32         59   $input =~ s/\\x5C/\\\\/gxms;
146              
147             #Escape </script> for stupid browsers that stop parsing
148 32         56   $input =~ s{</script>}{\\x3C\\x2Fscript\\x3E}gxms;
149              
150 32         125   return $input;
151             }
152              
153             sub __jsdump {
154 48     48   110   my ( $sym, $elem, $dict, $undef ) = @_;
155 48         84   my $ref = ref $elem;
156              
157 48 100       108   if ( not $ref ) {
158 38 100       91     if ( not defined $elem ) {
159 4 100       11       return qq/$sym = @{[defined($undef) ? $undef : $opt{UNDEF}]};/;
  4         28  
160                 }
161              
162             #Translated from $Regexp::Common::RE{num}{real}
163 34 100       217     if ( $elem =~ /^[+-]?(?:(?=\d|[.])\d*(?:[.]\d{0,})?)$/xsm ) {
164              
165             # (?:[eE][+-]?\d+)?
166 12 100       38       if ( $elem =~ /^0\d+$/xsm ) {
167 1         6         return qq/$sym = "$elem";/;
168                   }
169 11         172       return qq/$sym = $elem;/;
170                 }
171              
172             #Fall-back to quoted string
173 22         71     return qq/$sym = "/ . __quotemeta($elem) . q/";/;
174               }
175              
176             #Circular references
177 10 50       29   if ( $dict->{$elem} ) {
178 0         0     return qq/$sym = $dict->{$elem};/;
179               }
180 10         31   $dict->{$elem} = $sym;
181              
182             #isa over ref in case we're given objects
183 10 100 66     67   if ( $ref eq 'ARRAY' || reftype $elem eq 'ARRAY' ) {
    50 33        
184 6         23     my @list = ("$sym = new Array;");
185 6         13     my $n = 0;
186 6         11     foreach my $one ( @{$elem} ) {
  6         14  
187 18         44       my $newsym = "$sym\[$n]";
188 18         64       push @list, __jsdump( $newsym, $one, $dict, $undef );
189 18         42       $n++;
190                 }
191 6         30     return @list;
192               }
193               elsif ( $ref eq 'HASH' || reftype $elem eq 'HASH' ) {
194 4         15     my @list = ("$sym = new Object;");
195 4         6     foreach my $k ( sort keys %{$elem} ) {
  4         22  
196 9         23       my $old_k;
197 9         22       $k = __quotemeta( $old_k = $k );
198 9         23       my $newsym = qq($sym\["$k"]);
199 9         27       push @list, __jsdump( $newsym, $elem->{$old_k}, $dict, $undef );
200                 }
201 4         16     return @list;
202               }
203               else {
204 0               return "//Unknown reference: $sym=$ref";
205               }
206             }
207              
208             1;
209             ## no critic (RequirePodSections)
210             __END__
211            
212             =head1 NAME
213            
214             Data::JavaScript - Dump perl data structures into JavaScript code
215            
216             =head1 SYNOPSIS
217            
218             use Data::JavaScript; # Use defaults
219            
220             @code = jsdump('my_array', $array_ref); # Return array for formatting
221             $code = jsdump('my_object', $hash_ref); # Return convenient string
222             $html = hjsdump('my_stuff', $reference); # Convenience wrapper
223            
224             =head1 DESCRIPTION
225            
226             This module is mainly intended for CGI programming, when a perl script
227             generates a page with client side JavaScript code that needs access to
228             structures created on the server.
229            
230             It works by creating one line of JavaScript code per datum. Therefore,
231             structures cannot be created anonymously and need to be assigned to
232             variables. However, this format enables dumping large structures.
233            
234             The module can output code for different versions of JavaScript.
235             It currently supports 1.1, 1.3 and you specify the version on the
236             C<use> line like so:
237            
238             use Data::JavaScript {JS=>1.3}; # The new default
239             use Data::JavaScript {JS=>1.1}; # Old (pre module v1.10) format
240            
241             JavaScript 1.3 contains support for UTF-8 and a native C<undefined> datatype.
242             Earlier versions support neither, and will default to an empty string C<''>
243             for undefined values. You may define your own default--for either version--at
244             compile time by supplying the default value on the C<use> line:
245            
246             use Data::JavaScript {JS=>1.1, UNDEF=>'null'};
247            
248             Other useful values might be C<0>, C<null>, or C<NaN>.
249            
250             =head1 EXPORT
251            
252             In addition, althought the module no longer uses Exporter, it heeds its
253             import conventions; C<qw(:all>), C<()>, etc.
254            
255             =over
256            
257             =item jsdump('name', \$reference, [$undef]);
258            
259             The first argument is required, the name of JavaScript object to create.
260            
261             The second argument is required, a hashref or arrayref.
262             Structures can be nested, circular referrencing is supported (experimentally).
263            
264             The third argument is optional, a scalar whose value is to be used en lieu
265             of undefined values when dumping a structure.
266            
267             When called in list context, the function returns a list of lines.
268             In scalar context, it returns a string.
269            
270             =item hjsdump('name', \$reference, [$undef]);
271            
272             hjsdump is identical to jsdump except that it wraps the content in script tags.
273            
274             =back
275            
276             =head1 EXPORTABLE
277            
278             =over
279            
280             =item __quotemeta($str)
281            
282             This function escapes non-printable and Unicode characters (where possible)
283             to promote playing nice with others.
284            
285             =back
286            
287             =head1 CAVEATS
288            
289             Previously, the module eval'd any data it received that looked like a number;
290             read: real, hexadecimal, octal, or engineering notations. It now passes all
291             non-decimal values through as strings. You will need to C<eval> on the client
292             or server side if you wish to use other notations as numbers. This is meant
293             to protect people who store ZIP codes with leading 0's.
294            
295             Unicode support requires perl 5.8 or later. Older perls will gleefully escape
296             the non-printable portions of any UTF-8 they are fed, likely munging it in
297             the process as far as JavaScript is concerned. If this turns out to be a
298             problem and there is sufficient interest it may be possible to hack-in UTF-8
299             escaping for older perls.
300            
301             =head1 LICENSE
302            
303             =over
304            
305             =item * Thou shalt not claim ownership of unmodified materials.
306            
307             =item * Thou shalt not claim whole ownership of modified materials.
308            
309             =item * Thou shalt grant the indemnity of the provider of materials.
310            
311             =item * Thou shalt use and dispense freely without other restrictions.
312            
313             =back
314            
315             Or if you truly insist, you may use and distribute this under ther terms
316             of Perl itself (GPL and/or Artistic License).
317            
318             =head1 SEE ALSO
319            
320             L<Data::JavaScript::LiteObject>, L<Data::JavaScript::Anon>, L<CGI::AJAX|CGI::Ajax>
321            
322             =head1 AUTHOR
323            
324             Maintained by Jerrad Pierce <jpierce@cpan.org>
325            
326             Created by Ariel Brosh <schop cpan.org>.
327             Inspired by WDDX.pm JavaScript support.
328            
329             =cut
330