#!/usr/bin/perl

# Copyright (C) 2007 John J. Chew, III <jjchew@math.utoronto.ca>
# All Rights Reserved

package JavaScript::Serializable;

# use UNIVERSAL qw(can);

=pod

=head1 NAME

JavaScript::Serializable - provide JavaScript serialization to classes

=head1 SYNOPSIS

  package MyClass;
  our (@ISA);
  push(@ISA, qw(JavaScript::Serializable));
  sub EXPORT_JAVASCRIPT { 
    return ( 'mykey1' => 'jskey1', 'mykey2' => 'jskey2', ... );
    }
  my $x = new MyClass;
  my $javascript = $x->ToJavaScript();
  
=head1 ABSTRACT

This Perl library adds JavaScript serialization to its subclasses.
Class objects must be hash references.
Only those object fields explicitly exported by &EXPORT_JAVASCRIPT
will be serialized.
Field keys may be remapped using &EXPORT_JAVASCRIPT.
Scalar values will be translated as strings or numbers depending on 
what Perl thinks they are.
List and hash reference values in object fields will be iterated
over; other object references must be to other subclasses of 
JavaScript::Serializable.

=head1 DESCRIPTION

=over 4

=cut

sub Quote ($) {
  my $s = shift;
  $s =~ s/(['\\\n])/\\$1/g;
  return "'$s'";
  }

sub ToJavaScript ($) {
  my $this = shift;
  my (%keymap) = $this->EXPORT_JAVASCRIPT();
  my %data;
  while (my ($perlkey, $jskey) = each %keymap) {
    $data{$jskey} = $this->{$perlkey};
    }
  return ToJavaScriptAny(\%data);
  }

=item $js = ToJavaScriptAny($object);

Internal use only

=cut

sub ToJavaScriptAny ($) {
  my $value = shift;
  my $js = '';

  if (!defined $value) 
    { return "undefined"; }
  my $ref = ref($value);
  if ($ref eq '') 
  # looks_like_number thinks '1.' looks like a number, but we want it to be a string to save the '.'
#   { return (looks_like_number $value) ? $value : Quote($value); }
    { return $value =~ /^-?(?:\d+|\d*\.\d+)$/ ? $value : Quote($value); }
  if (UNIVERSAL::can($value, 'ToJavaScript')) {
    return $value->ToJavaScript();
    }
  if ($ref eq 'ARRAY') 
    { return '[' . join(',', map { ToJavaScriptAny($_) } @$value) .  ']'; }
  if ($ref eq 'HASH') {
    return '{' . join(',', map { Quote($_) . ':' . ToJavaScriptAny($value->{$_}) } sort keys %$value) . '}';
    }
  if ($ref =~ /^Ratings::/) {
    return Quote($value->Name());
    }
  die "ToJavaScriptAny(): don't know what to do with $value";
  }

=back

=cut

=head1 BUGS

None reported so far.

=cut

1;
