File Coverage

blib/lib/Data/JavaScript.pm
Criterion Covered Total %
statement 98 108 90.7
branch 34 46 73.9
condition 9 15 60.0
subroutine 10 11 90.9
pod 2 2 100.0
total 153 182 84.0


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