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