package Data::JavaScript;
require 5;
use vars qw(@EXPORT @EXPORT_OK %OPT $VERSION);
%OPT = (JS=>1.3);
$VERSION = 1.13;
@EXPORT = qw(jsdump hjsdump);
@EXPORT_OK = '__quotemeta';
use strict;
require Encode unless $] < 5.007;
sub import{
my $package = shift;
foreach( @_ ){
if(ref($_) eq 'HASH'){
$OPT{JS} = $$_{JS} if exists($$_{JS});
$OPT{UNDEF} = $$_{UNDEF} if exists($$_{UNDEF});
}
}
$OPT{UNDEF} ||= $OPT{JS} > 1.2 ? 'undefined' : q('');
#use (); #imports nothing, as package is not supplied
if( defined $package ){
no strict 'refs';
#Remove options hash
my @import = grep { ! length ref } @_;
if( scalar @import ){
if( grep {/^:all$/} @import ){
@import = (@EXPORT, @EXPORT_OK) }
else{
#only user-specfied subset of @EXPORT, @EXPORT_OK
my $q = qr/@{[join('|', @EXPORT, @EXPORT_OK)]}/;
@import = grep { $_ =~ /$q/ } @import;
}
}
else{
@import = @EXPORT;
}
my $caller = caller;
for my $func (@import) {
*{"$caller\::$func"} = \&$func;
}
}
}
sub hjsdump {
my @res = (qq(),
'', '');
wantarray ? @res : join("\n", @res, "");
}
sub jsdump {
my $sym = shift;
return "var $sym;\n" unless (@_);
my $elem = shift;
my $undef = shift;
my %dict;
my @res = __jsdump($sym, $elem, \%dict, $undef);
$res[0] = "var " . $res[0];
wantarray ? @res : join("\n", @res, "");
}
my $QMver;
if( $] < 5.007 ){
$QMver=<<'EO5';
s<([^ \x21-\x5B\x5D-\x7E]+)>{sprintf(join('', '\x%02X' x length$1), unpack'C*',$1)}ge;
EO5
}
else{
$QMver=<<'EO58';
if( $OPT{JS} >= 1.3 && Encode::is_utf8($_) ){
s<([\x{0080}-\x{fffd}]+)>{sprintf '\u%0*v4X', '\u', $1}ge;
}
{
use bytes;
s<((?:[^ \x21-\x7E]|(?:\\(?!u)))+)>{sprintf '\x%0*v2X', '\x', $1}ge;
}
EO58
}
eval 'sub __quotemeta {local $_ = shift;' . $QMver . <<'EOQM';
#This is kind of ugly/inconsistent output for munged UTF-8
#tr won't work because we need the escaped \ for JS output
s/\\x09/\\t/g;
s/\\x0A/\\n/g;
s/\\x0D/\\r/g;
s/"/\\"/g;
s/\\x5C/\\\\/g;
#Escape for stupid browsers that stop parsing
s%%\\x3C\\x2Fscript\\x3E%g;
return $_;
}
EOQM
sub __jsdump {
my ($sym, $elem, $dict, $undef) = @_;
my $ref;
unless( $ref = ref($elem) ){
unless( defined($elem) ){
return "$sym = @{[defined($undef) ? $undef : $OPT{UNDEF}]};";
}
#Translated from $Regexp::Common::RE{num}{real}
if( $elem =~ /^[+-]?(?:(?=\d|\.)\d*(?:\.\d{0,})?)$/ ){
# (?:[eE][+-]?\d+)?
return qq($sym = "$elem";) if $elem =~ /^0\d+$/;
return "$sym = $elem;";
}
#Fall-back to quoted string
return qq($sym = ") . __quotemeta($elem) . '";';
}
#Circular references
if ($dict->{$elem}) {
return "$sym = " . $dict->{$elem} . ";";
}
$dict->{$elem} = $sym;
#isa over ref in case we're given objects
if( $ref eq 'ARRAY' || UNIVERSAL::isa($elem, 'ARRAY') ){
my @list = ("$sym = new Array;");
my $n = 0;
foreach (@$elem) {
my $newsym = "$sym\[$n]";
push(@list, __jsdump($newsym, $_, $dict, $undef));
$n++;
}
return @list;
}
elsif( $ref eq 'HASH' || UNIVERSAL::isa($elem, 'HASH') ){
my @list = ("$sym = new Object;");
my ($k, $old_k, $v);
foreach $k (sort keys %$elem) {
$k = __quotemeta($old_k=$k);
my $newsym = qq($sym\["$k"]);
push(@list, __jsdump($newsym, $elem->{$old_k}, $dict, $undef));
}
return @list;
}
else{
return "//Unknown reference: $sym=$ref";
}
}
1;
__END__
=head1 NAME
Data::JavaScript - Dump perl data structures into JavaScript code
=head1 SYNOPSIS
use Data::JavaScript; # Use defaults
@code = jsdump('my_array', $array_ref); # Return array for formatting
$code = jsdump('my_object', $hash_ref); # Return convenient string
$html = hjsdump('my_stuff', $reference); # Convenience wrapper
=head1 DESCRIPTION
This module is mainly intended for CGI programming, when a perl script
generates a page with client side JavaScript code that needs access to
structures created on the server.
It works by creating one line of JavaScript code per datum. Therefore,
structures cannot be created anonymously and need to be assigned to
variables. However, this format enables dumping large structures.
The module can output code for different versions of JavaScript.
It currently supports 1.1, 1.3 and you specify the version on the
C