package Data::HTMLDumper;
use strict; use warnings;

require Exporter;

our @ISA = qw(Exporter);

our %EXPORT_TAGS = ( 'all' => [ qw(
	Dumper
) ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

our @EXPORT = qw(
	Dumper
);

our $VERSION = '0.07';

use Data::HTMLDumper::Output;
use Data::Dumper ();
use Parse::RecDescent;

our $actions  = new Data::HTMLDumper::Output();
our $Maxdepth;
our $Varname;
our $Sortkeys;

tie $Maxdepth, 'SpecialVars', \$Data::Dumper::Maxdepth;
tie $Varname,  'SpecialVars', \$Data::Dumper::Varname;
tie $Sortkeys, 'SpecialVars', \$Data::Dumper::Sortkeys;

sub actions {
    my $class   = shift;
    my $new_val = shift;

    $actions    = $new_val if (defined $new_val);
    return $actions;
}

#$::RD_HINT = 1;
#$::RD_TRACE = 1;

my $grammar = q{
    { our $actions = sub { return Data::HTMLDumper::actions(); } }
    output : expression(s) { &{$actions}->output($item[1]) }

    expression : SIGIL ID_NAME '=' item ';' {
                     &{$actions}->expression(%item);
                 }
               | <error>

    item : value       COMMA(?) { &{$actions}->item_value ($item{value},
                                                            $arg[0])     }
         | array       COMMA(?) { &{$actions}->item_array ($item{array}) }
         | hash        COMMA(?) { &{$actions}->item_hash  ($item{hash} ) }
         | object      COMMA(?) { &{$actions}->item_object($item{object})}

    array  : '[' item(s) ']'    { &{$actions}->array($item{'item(s)'}) }
           | '[' ']'            { &{$actions}->array_empty()           }

    hash   : '{' pair(s) '}'    { &{$actions}->hash($item{'pair(s)'}) }
           | '{' '}'            { &{$actions}->hash_empty()           }

    pair   : string '=>' item['make_row'] {
        &{$actions}->pair($item{string}, $item{item});
    }

    object : 'bless(' item string ')' {
        &{$actions}->object($item{item}, $item{string});
    }

    value : string | NUMBER | 'undef'
    
    string : TICK text TICK { &{$actions}->string($item{text}) }

    KEY_NAME : /[.\w\d_]+/

    ID_NAME  : /\w[\w\d_]*/

    NUMBER : /[+-]?(\d\.?\d*|\d*.\d+)/

    SIGIL : '$' | '@' | '%'

    text  : text_letter(s)     {local $" = "", "@{$item{'text_letter(s)'}}" }

    text_letter : ESCAPED_TICK
                | NON_TICK
    
    NON_TICK : /[^'\\\]*/

    ESCAPED_TICK : /(\\\')/  { "'" }

    COMMA : ','

    TICK  : "'"
};

my $parse   = new Parse::RecDescent($grammar);

sub Dumper  {
    my $original_output = Data::Dumper::Dumper(@_);
    my $output          = $parse->output($original_output);
    return $output;
}

sub new {
    my $class  = shift;
    my $dumper = Data::Dumper->new(@_);

    return bless {dumper => $dumper}, $class;
}

sub Dump {
    my $invocant = shift;
    my $original_output;
    my $output;

    if (ref $invocant) { $original_output = $invocant->{dumper}->Dump(@_); }
    else               { $original_output = Data::Dumper->Dump(@_);        }

    return $parse->output($original_output);
}

package SpecialVars;

sub TIESCALAR {
    my $class      = shift;
    my $dumper_var = shift;

    return bless $dumper_var, $class;
}

sub FETCH {
    my $self = shift;

    return $$self;
}

sub STORE {
    my $self  = shift;
    my $value = shift;

    $$self = $value;
}

1;
__END__

=head1 NAME

Data::HTMLDumper - Uses Data::Dumper to make HTML tables of structures

=head1 SYNOPSIS

  use Data::HTMLDumper;

  # to take control of the output:
  Data::HTMLDumper->actions($your_action_object);
  # See Data::HTMLDumper::Output.pm for what $your_action_object must do,
  # or see CONTROLLING OUTPUT below for a small example.

  ...

  print Dumper(\%hash, \@list);

  # or to supply names like Data::Dumper->Dump:
  print Data::HTMLDumper->Dump([\%hash, \@list], [qw(hash list)]);

=head1 ABSTRACT

  Data::HTMLDumper turns Data::Dumper output into HTML tables.
  It's for those who like Data::Dumper for quick peeks at their
  structures, but need to display the output in a web browser.

=head1 DESCRIPTION

If you like to use Data::Dumper for quick and dirty pictures of your structures
during development, but you are now developing for the web, this module might
be for you.  It uses Data::Dumper, but formats the results as HTML tables.

The format of the tables changed with the introduction of Parse::RecDescent
with version 0.04.  The new tables are more consistent.

As of version 0.06 the Dumper function handles any number of references.
The object oriented Dump method works like its analog in Data::Dumper.
The rest of the functions (except new) are not yet available (but
see 'SPECIAL VARIABLES' for how to avoid needing some of them).

=head1 SPECIAL VARIABLES

Several special variables are available as of version 0.06.  This include
Maxdepth (to limit how deep the traversal goes), Varname (to let you replace
$VAR2 with $YourPrefix2), and Sortkeys (which sorts hash keys as strings).
Currently you must use these through the following special variables:

    $Data::HTMLDumper::Maxdepth;
    $Data::HTMLDumper::Varname;
    $Data::HTMLDumper::Sortkeys;

Note that to even see the Varname prefix, you must implement your own
callback object, perhaps by subclassing Data::HTMLDumper::Output.

=head1 CONTROLLING OUTPUT

If you need to change the way the tables appear, you can (as of version 0.06)
subclass Data::HTMLDumper::Output to implement your changes.  You must also
tell Data::HTMLDumper to make the change.  Here is a sample similar to
one used in test number 09:

    use Data::HTMLDumper;

    Data::HTMLDumper->actions(MyOutput->new());

    my $data = [qw(some data)];

    print Data::HTMLDumper->Dump([$data], ['data']);

    package MyOutput;

    use base 'Data::HTMLDumper::Output';

    sub expression {
        my $self = shift;
        my %item = @_;

        return "<table border='1'><tr><th>$item{ID_NAME}</th></tr>\n"
             . "$item{item}</table>\n";
    }

This adds a heading row to each table listing the name supplied
to Dump (it would use VAR1 if you called Dumper).

The key is to create your own package (MyOutput above) making it inherit
from Data::HTMLDumper::Output (e.g. via use base).  This saves you having
to implement all of the Output methods (of which there are about 12).

=head2 EXPORT

Dumper

=head1 DEPENDENCIES

Data::Dumper
Parse::RecDescent
Data::HTMLDumper::Output

=head1 BUGS and OMISSIONS

Though Data::Dumper is used, not all (or even most) of its features are
implemented.

These features will be added as developer time permits:

    Seen
    Values
    Names
    Reset

Some of these may be implemented depending on interest:

    Terse
    Deepcopy
    Freezer

Here is a list of features that will never be available (with explanations
of what to do instead):

=over

=item Indent

this affects the appearance of the text output.  To affect the appearance
of the HTML tables (or to do something totally different) implement a
subclass of Data::HTMLDumper::Output

=item Purity

builds fully "eval"able code.  What we're doing here is making pretty HTML
tables.  There is no use to replicating Data::Dumper's ability to restore
data structures.

=item Pad

like indent, this affects the appearance of the output.

=item Useqq

controls how special characters in strings are masked.  HTML requires a
different approach, see Data::HTMLDumper::Output->string for an example
of the kind of work you need to do to have things rendered properly.

=item Toaster

allows objects to control how they are displayed.  Override the callback method
Data::HTMLDumper::Output->object to control object display.  Redispatch
to the object or its class if you like.

=item Quotekeys

controls whether quotes are always used around hash keys.  Data::HTMLDumper
strips these during parsing, whether they appear or not.  You can control
the appearance of hash keys by overriding Data::HTMLDumper::Output->pair.

=item Bless

allows the caller to replace the builtin bless with their own function.
Since we are only concerned with appearance here, you should implement
your own Data::HTMLDumper::Output->object.

=item Useperl

for developers of Data::Dumper to turn off XS use during debugging.

=item Deparse

tries to turn code references back into Perl source with B::Deparse.

=back

Attempts to access these concepts through direct use of Data::Dumper
is not wise.  Doing so will alter the output of Data::Dumper (duh).
That new form will not agree with my grammar and Bad Things will
happen, such as fatal parsing errors.

Starting with version 0.04 Data::HTMLDumper uses Parse::RecDescent instead
of its old regex substitution scheme.  This means that your structure will
produce nothing but an error if my grammar is not good enough.  If that
happens to you, please send me a sample of the structure so that I can
correct the grammar.

Starting with version 0.06 you can call Data::Dumper with multiple arguments,
but the test suite for this is not complete.  If you encounter problems,
please send in samples of what broke.

=head1 SEE ALSO

This module depends on Data::Dumper to do the real work.  Check its
documentation for details about how to call Dumper and Dump.

As of version 0.06 Data::HTMLDumper uses Data::HTMLDumper::Output to
produce the tables.  By subclassing it, or replacing it, you can
take a considerable amount of control over the appearance of the final
output.  You could even produce XML or something else.

=head1 AUTHOR

Phil Crow, E<lt>philcrow2000@yahoo.com<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2003-4 by Phil Crow

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl 5.8.0 itself. 

=head1 CREDITS

Thanks to Dennis Sutch for patches and encouragement to make the module
substantially more robust.

=cut
