HTML-AutoTag

 view release on metacpan or  search on metacpan

lib/HTML/AutoTag.pm  view on Meta::CPAN

package HTML::AutoTag;
use 5.006;
use strict;
use warnings FATAL => 'all';
our $VERSION = '1.08';

use HTML::Entities;
use Tie::Hash::Attribute;

our( $INDENT, $NEWLINE, $LEVEL, $ENCODE, $ENCODES, $SORTED );

sub new {
    my $self = shift;
    my $args = {@_};
    $ENCODE  = defined $args->{encode}  ? $args->{encode}  : exists $args->{encodes};
    $ENCODES = defined $args->{encodes} ? $args->{encodes} : '';
    $INDENT  = defined $args->{indent}  ? $args->{indent}  : '';
    $NEWLINE = defined $args->{indent}  ? "\n" : '';
    $LEVEL   = $args->{level} || 0;
    $SORTED  = $args->{sorted};
    bless {}, $self;
}

sub tag {
    my $self = shift;
    my %args = @_;
    my $attr = $args{attr};

    return '' unless defined $args{tag};

    my $attr_str;
    if (grep ref($_), values %$attr) {
        # complex attrs use a tied hash
        unless (grep /^-/, keys %$attr) {
            tie my %attr, 'Tie::Hash::Attribute', sorted => $SORTED;
            %attr = %$attr;
            $attr = \%attr;
        }
    } else {
        # simple attrs can bypass being tied
        $attr_str = '';
        my @keys = $SORTED ? sort keys %$attr : keys %$attr;
        for my $key (@keys) {
            $attr_str .= ' ' . Tie::Hash::Attribute::_key( $key ) . '="' . Tie::Hash::Attribute::_val( $attr->{$key} ) . '"';
        }
    }

    # emtpy tag
    unless (defined $args{cdata}) {
        return ( $INDENT x $LEVEL )
            . "<$args{tag}"
            . ( defined( $attr_str ) ? $attr_str : scalar( %$attr ) )
            . " />$NEWLINE"
        ;
    }

    my $cdata;
    my $no_post_indent;
    if (ref($args{cdata}) eq 'ARRAY') {

        if (ref($args{cdata}[0]) eq 'HASH') {

            $LEVEL++;
            $cdata = $NEWLINE;
            for (0 .. $#{ $args{cdata} }) {
                $cdata .= $self->tag( %{ $args{cdata}[$_] } );
            }
            $LEVEL--;

        } else {
            my $str = '';
            for (@{ $args{cdata} }) {
                $str .= $self->tag( tag => $args{tag}, attr => $attr, cdata => $_);
            }
            return $str;
        }

    } elsif (ref($args{cdata}) eq 'HASH') {
        $LEVEL++;
        $cdata = $NEWLINE . $self->tag( %{ $args{cdata} } );
        $LEVEL--;

    } else {
        $cdata = $ENCODE ? HTML::Entities::encode_entities( $args{cdata}, $ENCODES ) : $args{cdata};
        $no_post_indent = 1;
    }

    return ( $INDENT x $LEVEL )
        . "<$args{tag}" . ( defined( $attr_str ) ? $attr_str : scalar( %$attr ) )
        . ">$cdata"
        . ( $no_post_indent ? '' : ( $INDENT x $LEVEL ) )
        . "</$args{tag}>$NEWLINE"
    ;
}

1;

__END__
=head1 NAME

HTML::AutoTag - Just another HTML tag generator.

=head1 SYNOPSIS

  use HTML::AutoTag;

  my $auto = HTML::AutoTag->new( indent => '    ', encode => 1 );

  my %attr = ( style => { color => [qw(red green)] } );
  my @data = qw( one two three four five six seven eight );

  print $auto->tag(
      tag   => 'ol', 
      attr  => {qw( reversed reversed )},
      cdata => [
          map { tag => 'li', attr => \%attr, cdata => $_ }, @data
      ]
  );

=head1 DESCRIPTION

Generate nested HTML (HTML4, XHTML and HTML5) tags with custom indentation,
custom encoding and automatic attribute value rotation.

=head1 METHODS

=over 4

=item * C<new()>

Accepts the following arguments:

=over 8

=item * C<encode>

Encode HTML entities. Boolean. Defaults to false, which produces no encoding.
If set to true without further specifying a value for C<encodes> (see below),
will encode all control chars, high bit chars and '<', '&', '>', ''' and '"'.

  encode => 1

=item * C<encodes>

Encode HTML entities. String. Set value to those characters you wish to
have encoded.

  encodes => '<=>' 

=item * C<indent>

Pretty print results. Defaults to undef which produces no indentation.
Set value to any number of spaces or tabs and newlines will also be appended.

  indent => '    '
  indent => "\t"

=item * C<level>

Indentation level to start at. Can be used in conjunction with C<indent>
to set indentation even deeper to match any existing HTML this code
may be injected into.

  level => 2

=item * C<sorted>

Sorts the attribute names of the tag alphabetically. This is mostly
useful for ensuring consistancy. The attributes (and potential sorting)
happen within L<Tie::Hash::Attribute>. You most likely will not
need this feature.

  sorted => 1

=back

=item * C<tag()>

Accepts three arguments:

=over 8

=item * C<tag>

The name of the tag. String.

  tag => 'table'

=item * C<cdata>

The value inbetween the tag. Types allowed are:

=over 12

=item * scalar - the string to be wrapped in tags

  cdata => 'hello world'

=item * hash ref - another tag with its own cdata and attributes

  cdata => { tag => 'td', attr => {}, cdata => 'value' }

=item * AoH - multiple tags as hash references.

  cdata => [
      { tag => 'td', attr => {}, cdata => 'value1' }
      { tag => 'td', attr => {}, cdata => 'value2' }
  ]

=back

=item * C<attr>

The attributes and values to write out for the tag. Hash reference.

  attr => { border => 1 }

=back

=back

=head1 REQUIRES

=over 4

=item * L<HTML::Entities>

Used to encode unsafe HTML entities.

=item * L<Tie::Hash::Attribute>

Used to create rotating attributes.

=back

=head1 EXAMPLES

The following will render a table with rotating attributes:

  my $auto = HTML::AutoTag->new( indent => "    " );
  my %tr_attr = ( class => [qw(odd even)] );
  
  print $auto->tag(
      tag => 'table',
      attr => { class => 'spreadsheet' },
      cdata => [
          {
              tag => 'tr',
              attr => \%tr_attr,
              cdata => {
                  tag => 'td',
                  attr => { style => { color => [qw(red green)] } },
                  cdata => [qw(one two three four five six)],
              },
          },
          {
              tag => 'tr',
              attr => \%tr_attr,
              cdata => {
                  tag => 'td',
                  attr => { style => { color => [qw(green red)] } },
                  cdata => [qw(seven eight nine ten eleven twelve)],
              },
          },
          {
              tag => 'tr',
              attr => \%tr_attr,
              cdata => {
                  tag => 'td',
                  attr => { style => { color => [qw(red green)] } },
                  cdata => [qw(thirteen fourteen fifteen sixteen seventeen eighteen)],
              },
          },
      ]
  );

The following will emulate CGI.pm's C<scrolling_list()> output:

  my $auto = HTML::AutoTag->new( indent => "    " );

  print $auto->tag(
      tag   => 'select',
      attr  => { name => 'widgets', size => 3, multiple => 'multiple' },
      cdata => [
          { tag => 'option', cdata => 'foo', attr => { value => 1, } },
          { tag => 'option', cdata => 'bar', attr => { value => 2, selected => 'selected' } },
          { tag => 'option', cdata => 'baz', attr => { value => 3, selected => 'selected' } },
      ]
  );

Or in a more programatic way:

  my $auto = HTML::AutoTag->new( indent => "    " );

  my @selected = (2, 3);
  my @options  = (
      [ 1, 'foo' ],
      [ 2, 'bar' ],
      [ 3, 'baz' ],
  );

  my $selected = [];
  for my $o (@options) {
     push @$selected, (grep $_ eq $o->[0], @selected) ? 'selected' : undef;
  }
  my %attr = ( value => [ map $_->[0], @options ], selected => $selected );

  print $auto->tag(
      tag   => 'select',
      attr  => { name => 'widgets', size => scalar @options, multiple => 'multiple' },
      cdata => [ map { tag => 'option', attr => \%attr, cdata => $_->[1] }, @options ],
  );

See tests in C<t/> from the distribution or github for more examples:
L<https://github.com/jeffa/HTML-AutoTag/tree/master/t>

=head1 INSPIRATION

This module was the unintentional result of efforts to refactor
L<DBIx::XHTML_Table> into L<DBIx::HTML> and L<Spreadsheet::HTML>. The need
to reimplement what CGI and HTML::Element (and a slew of others out there
on the CPAN) do was generated from slow performance time and maintaining
the rotating attributes feature (now in L<Tie::Hash::Attribute>).

Two existing modules inspired this one: Lincoln Stein's L<CGI> has
long been able to easily produce completely arbitrary HTML text by
turning any non-defined method call into a wrapper. Gisle Aas's
L<HTML::Tree> distribution has a wonderful method (HTML::Element::new_from_lol)
which this module draws most of its interface inspiration from.

=head1 BUGS AND LIMITATIONS

If you find a situtation in which this module cannot produce what you
need please feel free to report a bug. You may report any bugs or
feature requests to either:

=over 4

=item * Email: C<bug-html-autotag at rt.cpan.org>

=item * Web: L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=HTML-AutoTag>

=back

=head1 GITHUB

The Github project is L<https://github.com/jeffa/HTML-AutoTag>

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc HTML::AutoTag

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here) L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=HTML-AutoTag>

=item * AnnoCPAN: Annotated CPAN documentation L<http://annocpan.org/dist/HTML-AutoTag>

=item * CPAN Ratings L<http://cpanratings.perl.org/d/HTML-AutoTag>

=item * Search CPAN L<http://search.cpan.org/dist/HTML-AutoTag/>

=back

=head1 SEE ALSO

=over 4



( run in 1.341 second using v1.01-cache-2.11-cpan-13bb782fe5a )