| Name | Score | Position |
|---|---|---|
| Therese | 90 | 1 |
| Chrissie | 85 | 2 |
| Andy | 50 | 3 |
Hello, World
Second para
=head1 DESCRIPTION C<< HTML::Tiny >> is a simple, dependency free module for generating HTML (and XML). It concentrates on generating syntactically correct XHTML using a simple Perl notation. In addition to the HTML generation functions utility functions are provided to =over =item * encode and decode URL encoded strings =item * entity encode HTML =item * build query strings =item * JSON encode data structures =back =head1 INTERFACE =over =item C<< new >> Create a new C<< HTML::Tiny >>. The constructor takes one optional argument: C<< mode >>. C<< mode >> can be either C<< 'xml' >> (default) or C<< 'html' >>. The difference is that in HTML mode, closed tags will not be closed with a forward slash; instead, closed tags will be returned as single open tags. Example: # Set HTML mode. my $h = HTML::Tiny->new( mode => 'html' ); # The default is XML mode, but this can also be defined explicitly. $h = HTML::Tiny->new( mode => 'xml' ); HTML is a dialect of SGML, and is not XML in any way. "Orphan" open tags or unclosed tags are legal and in fact expected by user agents. In practice, if you want to generate XML or XHTML, supply no arguments. If you want valid HTML, use C<< mode => 'html' >>. =back =cut sub new { my $self = bless {}, shift; my %params = @_; my $mode = $params{'mode'} || 'xml'; croak "Unknown mode: $mode" unless $mode eq 'xml' or $mode eq 'html'; $self->{'_mode'} = $mode; $self->_set_auto( 'method', 'closed', @DEFAULT_CLOSED ); $self->_set_auto( 'suffix', "\n", @DEFAULT_NEWLINE ); return $self; } sub _set_auto { my ( $self, $kind, $value ) = splice @_, 0, 3; $self->{autotag}->{$kind}->{$_} = $value for @_; } =head2 HTML Generation =over =item C<< tag( $name, ... ) >> Returns HTML (or XML) that encloses each of the arguments in the specified tag. For example print $h->tag('p', 'Hello', 'World'); would printHello
World
notice that each argument is individually wrapped in the specified tag. To avoid this multiple arguments can be grouped in an anonymous array: print $h->tag('p', ['Hello', 'World']); would printHelloWorld
The [ and ] can be thought of as grouping a number of arguments. Attributes may be supplied by including an anonymous hash in the argument list: print $h->tag('p', { class => 'normal' }, 'Foo'); would printFoo
Attribute values will be HTML entity encoded as necessary. Multiple hashes may be supplied in which case they will be merged: print $h->tag('p', { class => 'normal' }, 'Bar', { style => 'color: red' }, 'Bang!' ); would printBar
Bang!
Notice that the class="normal" attribute is merged with the style attribute for the second paragraph. To remove an attribute set its value to undef: print $h->tag('p', { class => 'normal' }, 'Bar', { class => undef }, 'Bang!' ); would printBar
Bang!
An empty attribute - such as 'checked' in a checkbox can be encoded by passing an empty array reference: print $h->closed( 'input', { type => 'checkbox', checked => [] } ); would print Bthis
', 'that
' ); That means that when you nest calls to tag (or the equivalent HTML aliases - see below) the individual arguments to the inner call will be tagged separately by each enclosing call. In practice this means that print $h->tag('p', $h->tag('b', 'Foo', 'Bar')); would printFoo
Bar
You can modify this behavior by grouping multiple args in an anonymous array: print $h->tag('p', [ $h->tag('b', 'Foo', 'Bar') ] ); would printFooBar
This behaviour is powerful but can take a little time to master. If you imagine '[' and ']' preventing the propagation of the 'tag individual items' behaviour it might help visualise how it works. Here's an HTML table (using the tag-name convenience methods - see below) that demonstrates it in more detail: print $h->table( [ $h->tr( [ $h->th( 'Name', 'Score', 'Position' ) ], [ $h->td( 'Therese', 90, 1 ) ], [ $h->td( 'Chrissie', 85, 2 ) ], [ $h->td( 'Andy', 50, 3 ) ] ) ] ); which would print the unformatted version of:| Name | Score | Position |
|---|---|---|
| Therese | 90 | 1 |
| Chrissie | 85 | 2 |
| Andy | 50 | 3 |
All other tag methods generate tags to wrap whatever content they
are passed:
print $h->p('Hello, World');
prints:
Hello, World
So the following are equivalent: print $h->a({ href => 'http://hexten.net' }, 'Hexten'); and print $h->tag('a', { href => 'http://hexten.net' }, 'Hexten'); =head2 Utility Methods =over =item C<< url_encode( $str ) >> URL encode a string. Spaces become '+' and non-alphanumeric characters are encoded as '%' + their hexadecimal character code. $h->url_encode( 'a
b
', 'nested multi tag OK' . $mode; is $h->tag( 'p', $h->tag( 'b', [ 'a', 'b' ] ) ), 'ab
', 'nested grouped tag OK' . $mode; # Attributes is $h->open( 'p', { class => 'normal' } ), '', 'simple attr OK' . $mode; is $h->open( 'p', { class => 'normal', style => undef } ), '
', 'skip undef attr OK' . $mode; is $h->tag( 'p', { class => 'small' }, 'a', 'b' ), '
a
b
', 'multi w/ attr OK' . $mode; is $h->tag( 'p', { class => 'small' }, 'a', { class => undef }, 'b' ), 'a
b
', 'change attr OK' . $mode; } # Stringification package T::Obj; sub new { bless {}, shift } sub as_string { 'an object' } sub TO_JSON { 'a json object' } package T::Obj2; sub new { bless {}, shift } package main; my $obj = T::Obj->new; is $h->tag( 'p', $obj ), 'an object
', 'stringification OK'; my $obj2 = T::Obj2->new; like $h->tag( 'p', $obj2 ), '/T::Obj2=.+?
/', 'non as_string OK'; # Only hashes allowed eval { $h->closed( { src => 'spork' }, 'Text here' ); }; like $@, '/Attributes\s+must\s+be\s+passed\s+as\s+hash\s+references/', 'error on non-hash OK'; # URL encoding, decoding is $h->url_encode( 'one", "
two" ], "args" => [ "one", "two" ], "expect_scalar" => "
one
two", "method" => "blockquote" }, { "expect_list" => [ "one\n", "two\n" ], "args" => [ "one", "two" ], "expect_scalar" => "one\ntwo\n", "method" => "body" }, { "expect_list" => [ "", "" ], "args" => [ "one", "two" ], "expect_scalar" => "", "method" => "button" }, { "expect_list" => [ "
one", "two" ],
"args" => [ "one", "two" ],
"expect_scalar" => "onetwo",
"method" => "code"
},
{
"expect_list" =>
[ "one
\n", "two
\n" ], "args" => [ "one", "two" ], "expect_scalar" => "one
\ntwo
\n", "method" => "p" }, { "expect_list" => [ "one", "
two" ], "args" => [ "one", "two" ], "expect_scalar" => "
one
two", "method" => "pre" }, { "expect_list" => [ "
one", "
two" ], "args" => [ "one", "two" ], "expect_scalar" => "
one
two", "method" => "q" }, { "args" => [ { spaces => ' ', '&' => '>' } ], "expect_scalar" => "%26=%3e&spaces=+++", "method" => "query_encode" }, { "expect_list" => [ "one", "two" ], "args" => [ "one", "two" ], "expect_scalar" => "onetwo", "method" => "samp" }, { "expect_list" => [ "", "" ], "args" => [ "one", "two" ], "expect_scalar" => "", "method" => "script" }, { "expect_list" => [ "", "" ], "args" => [ "one", "two" ], "expect_scalar" => "", "method" => "select" }, { "expect_list" => [ "one", "two" ], "args" => [ "one", "two" ], "expect_scalar" => "onetwo", "method" => "small" }, { "expect_list" => [ "one", "two" ], "args" => [ "one", "two" ], "expect_scalar" => "onetwo", "method" => "span" }, { "expect_list" => [ "one", "two" ], "args" => [ "one", "two" ], "expect_scalar" => "onetwo", "method" => "strong" }, { "expect_list" => [ "", "" ], "args" => [ "one", "two" ], "expect_scalar" => "", "method" => "style" }, { "expect_list" => [ "one", "two" ], "args" => [ "one", "two" ], "expect_scalar" => "onetwo", "method" => "sub" }, { "expect_list" => [ "one", "two" ], "args" => [ "one", "two" ], "expect_scalar" => "onetwo", "method" => "sup" }, { "expect_list" => [ "
'],
"expect_scalar" => '
',
"method" => "img"
},
{
"args" => [ { type => 'text' }, { name => 'widget' } ],
"expect_scalar" => "",
"method" => "input"
},
{
"args" => [ { href => 'http://foo.net/' } ],
"expect_scalar" => '' ,
"method" => "link"
},
{
"args" => [],
"expect_scalar" => "",
"method" => "meta"
},
{
"args" => [ { value => 1 } ],
"expect_scalar" => "",
"method" => "param"
},
);
my @schedule_html = (
@common_schedule,
{
"args" => [ { name => 'foo' } ],
"expect_scalar" => "",
"expect_list" => [""],
"method" => "area"
},
{
"args" => [ { href => 'http://hexten.net/' } ],
"expect_scalar" => "
'],
"expect_scalar" => '
',
"method" => "img"
},
{
"args" => [ { type => 'text' }, { name => 'widget' } ],
"expect_scalar" => "",
"method" => "input"
},
{
"args" => [ { href => 'http://foo.net/' } ],
"expect_scalar" => '' ,
"method" => "link"
},
{
"args" => [],
"expect_scalar" => "",
"method" => "meta"
},
{
"args" => [ { value => 1 } ],
"expect_scalar" => "",
"method" => "param"
},
);
plan tests => ( @schedule_xml + @schedule_html ) * 3 * 4;
@schedule{qw(xml html)} = ( \@schedule_xml, \@schedule_html );
}
sub apply_test {
my ( $h, $test ) = @_;
my $method = $test->{method};
can_ok $h, $method;
my $got = $h->$method( @{ $test->{args} } );
is_deeply $got, $test->{expect_scalar},
"$method: scalar result matches";
my $expect_list = $test->{expect_list}
|| [ $test->{expect_scalar} ];
my @got = $h->$method( @{ $test->{args} } );
is_deeply \@got, $expect_list, "$method: list result matches";
}
for my $mode ( qw(xml html) ) {
my @schedule = @{ $schedule{$mode} };
# Run the tests three times, forwards and backwards to make sure they
# don't interfere with each other.
{
my $h = HTML::Tiny->new( mode => $mode );
apply_test( $h, $_ ) for @schedule, reverse @schedule, @schedule;
}
# And once again, this time with a fresh HTML::Tiny for each test
apply_test( HTML::Tiny->new( mode => $mode ), $_ ) for @schedule;
}
HTML-Tiny-1.05/t/030-tags.t 0000644 0000765 0000120 00000004022 11075713655 013622 0 ustar andy admin use strict;
use HTML::Tiny;
use Test::More tests => 18;
ok my $h = HTML::Tiny->new, 'Create succeeded';
ok my $h_html = HTML::Tiny->new( mode => 'html' ),
'Create succeeded (mode HTML)';
common_checks( $h );
common_checks( $h_html, ' (mode HTML)' );
is $h->br, '
', 'img OK';
is $h_html->br, '
',
'img OK (mode HTML)';
sub common_checks {
my $h = shift;
my $mode = shift || '';
is $h->p( 'hello, world' ), "hello, world
\n", 'p OK' . $mode; is $h->a( { href => 'http://hexten.net', title => 'Hexten' }, 'Hexten' ), 'Hexten', 'a OK' . $mode; is $h->textarea(), '', 'empty tag OK' . $mode; is $h->html( [ $h->head( $h->title( 'Sample page' ) ), $h->body( [ $h->h1( { class => 'main' }, 'Sample page' ), $h->p( 'Hello, World', { class => 'detail' }, 'Second para' ) ] ) ] ), "Hello, World
\nSecond para
\n" . "\n\n", 'complex HTML OK' . $mode; is $h->table( [ $h->tr( [ $h->th( 'Name', 'Score', 'Position' ) ], [ $h->td( 'Therese', 90, 1 ) ], [ $h->td( 'Chrissie', 85, 2 ) ], [ $h->td( 'Andy', 50, 3 ) ] ) ] ), "| Name | Score | Position |
|---|---|---|
| Therese | 90 | 1 |
| Chrissie | 85 | 2 |
| Andy | 50 | 3 |
',
'img OK';
is $h_html->stringify( [ \'br' ] ), '
', 'img OK (mode HTML)';
sub common_checks {
my $h = shift;
my $mode = shift || '';
is $h->stringify( [ \'p', 'hello, world' ] ),
"hello, world
\n", 'p OK' . $mode; is $h->stringify( [ \'a', { href => 'http://hexten.net', title => 'Hexten' }, 'Hexten' ] ), 'Hexten', 'a OK' . $mode; is $h->stringify( [ \'textarea' ] ), '', 'empty tag OK' . $mode; is $h->stringify( [ \'table', [ \'tr', [ \'th', 'Name', 'Score', 'Position' ], [ \'td', 'Therese', 90, 1 ], [ \'td', 'Chrissie', 85, 2 ], [ \'td', 'Andy', 50, 3 ] ] ] ), "| Name | Score | Position |
|---|---|---|
| Therese | 90 | 1 |
| Chrissie | 85 | 2 |
| Andy | 50 | 3 |
Hello, World
\nSecond para
\n" . "\n\n", 'complex HTML OK' . $mode; } HTML-Tiny-1.05/t/050-validate_tag.t 0000644 0000765 0000120 00000001304 11075713655 015312 0 ustar andy admin use strict; use HTML::Tiny; use Test::More tests => 4; package My::HTML::Tiny; use vars qw/@ISA/; @ISA = qw/HTML::Tiny/; use HTML::Tiny; sub validate_tag { my $self = shift; my ( $closed, $name, $attr ) = @_; push @{ $self->{valid_args} }, [ $closed, $name, $attr ]; } sub get_validation { shift->{valid_args} } package main; ok my $h = My::HTML::Tiny->new, 'Created OK'; isa_ok $h, 'HTML::Tiny'; is $h->tag( 'p', { class => 'small' }, 'a', { class => undef }, 'b' ), 'a
b
', 'change attr OK'; my $got = $h->get_validation; my $want = [ [ 0, 'p', { 'class' => 'small' } ], [ 0, 'p', { 'class' => undef } ] ]; is_deeply $got, $want, 'validation hook called OK'; HTML-Tiny-1.05/xt/ 0000755 0000765 0000120 00000000000 11155001364 012353 5 ustar andy admin HTML-Tiny-1.05/xt/author/ 0000755 0000765 0000120 00000000000 11155001364 013655 5 ustar andy admin HTML-Tiny-1.05/xt/author/pod-coverage.t 0000644 0000765 0000120 00000001534 11075713655 016436 0 ustar andy admin #!perl -T use Test::More; plan skip_all => 'Need qr{} to work' if $] < 5.005; eval "use Test::Pod::Coverage 1.04"; plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@; # Won't compile on 5.0.4: qr didn't exist then. eval <<'EOT'; all_pod_coverage_ok( { private => [qr{^_}], trustme => [ qr{^(?:a|abbr|acronym|address|area|b|base|bdo|big|blockquote|body|button)$}, qr{^(?:caption|cite|code|col|colgroup|dd|del|div|dfn|dl|dt|em|fieldset|form)$}, qr{^(?:frame|frameset|h1|h2|h3|h4|h5|h6|head|hr|html|i|iframe|img|ins|kbd|label)$}, qr{^(?:legend|li|link|map|meta|noframes|noscript|object|ol|optgroup|option|p)$}, qr{^(?:param|pre|q|samp|script|select|small|span|strong|style|sub|sup|table)$}, qr{^(?:tbody|td|textarea|tfoot|th|thead|title|tr|tt|ul|var|br|input)$} ] } ); EOT HTML-Tiny-1.05/xt/author/pod.t 0000644 0000765 0000120 00000000214 10716111100 014611 0 ustar andy admin #!perl -T use Test::More; eval "use Test::Pod 1.14"; plan skip_all => "Test::Pod 1.14 required for testing POD" if $@; all_pod_files_ok();