NAME
HTML::Blitz::Builder - create HTML code dynamically and safely
SYNOPSIS
use HTML::Blitz::Builder qw(
mk_doctype
mk_comment
mk_elem
to_html
fuse_fragment
);
DESCRIPTION
This module is useful for creating snippets of HTML code (or entire documents) programmatically. It takes care of automatically escaping any strings you pass to it, which prevents the injection of HTML and script code (XSS).
To use it, call the mk_elem
, mk_comment
, and mk_doctype
constructor functions as needed to create the document structure you want. At the very end, pass everything to to_html
to obtain the corresponding HTML code.
The basic data structure used by this module is the document fragment, which in Perl is represented as a list of (zero or more) node values. A node value is one of the following:
A text node, represented by a plain string.
An element node, represented by an object returned from
mk_elem
.A comment node, represented by an object returned from
mk_comment
.A
DOCTYPE
declaration, represented by an object returned frommk_doctype
.
See below for the list of public functions provided by this module, which are exportable on request.
FUNCTIONS
to_html(@nodes)
Takes a list of node values (as described above) and returns the corresponding HTML code.
The list of elements can be empty: to_html()
(which is the same as to_html(())
) simply returns the empty string. More generally, to_html
is a homomorphism from lists to strings in that it preserves concatenation:
to_html(@A, @B) eq (to_html(@A) . to_html(@B))
mk_elem($name, [\%attributes, ] @nodes)
Creates an element with the specified name, attributes, and child nodes.
The only required argument is the element name. The optional second argument is a reference to a hash of attribute name/value pairs; omitting it is equivalent to passing {}
(an empty hashref), meaning no attributes. All remaining arguments are taken to be child nodes.
For example:
to_html( mk_elem("div") )
# => '<div></div>'
to_html( mk_elem("div", "I <3 U") )
# => '<div>I <3 U</div>'
mk_elem
is aware of "void" (i.e. content-free) elements and will not generate closing tags for them:
to_html( mk_elem("br") )
# => '<br>'
If you attempt to create a void element with child nodes, an exception will be thrown:
to_html( mk_elem("br", "Hello!") )
# error!
The same is true for elements that cannot contain nested child elements (title
, textarea
, style
, script
):
to_html( mk_elem("title", "Hello!") )
# => '<title>Hello!</title>'
to_html( mk_elem("title", mk_elem("span", "Hello!")) )
# error!
Because the contents of style
and script
elements do not follow the usual HTML parsing rules, not all values can be represented:
to_html( mk_elem("p", "</p>") )
# => '<p></p></p>'
to_html( mk_elem("style", "</style>") )
# error!
to_html( mk_elem("script", "</script>") )
# error!
All attributes and text nodes are properly HTML escaped:
to_html( mk_elem("a", { href => "/q?a=1&b=2&c=3" }, "things, stuff, &c.") )
# => '<a href="/q?a=1&b=2&c=3">things, stuff, &c.</a>'
The result of to_html
is deterministic; in particular, attributes are always generated in the same order (despite being passed in the form of a hash).
mk_comment($comment)
Creates a comment with the specified contents.
to_html( mk_comment("a b c") )
# => '<!--a b c-->'
Certain strings (most notably -->
) cannot appear in an HTML comment, so the following will throw an exception:
to_html( mk_comment("A --> B") )
# error!
mk_doctype()
Creates a DOCTYPE declaration. It takes no arguments and always produces an html
doctype:
to_html( mk_doctype() )
# => '<!DOCTYPE html>'
fuse_fragment(@nodes)
Sometimes you want to store a document fragment (a list of nodes) in a single Perl value without wrapping an extra element around it. This is what fuse_fragment
does: It takes a list of node values and returns a single object representing that list.
fuse_fragment
is idempotent:
fuse_fragment(fuse_fragment(@nodes))
# is equivalent to
fuse_fragment(@nodes)
Another way of looking at it is that as far as fragment consumers (i.e. to_html
, mk_elem
, and fuse_fragment
itself) are concerned, fuse_fragment
is transparent:
to_html( fuse_fragment(@values) )
# is the same as
to_html( @values )
mk_elem($name, $attrs, fuse_fragment(@values))
# is the same as
mk_elem($name, $attrs, @values)
EXAMPLE
my @fragment =
mk_elem('form', { method => 'POST', action => '/login' },
mk_elem('label',
'Username: ',
mk_elem('input', { name => 'user' }),
),
mk_elem('label',
'Password: ',
mk_elem('input', { name => 'password', type => 'password' }),
),
);
my @document = (
mk_doctype,
mk_elem('title', 'Log in'),
mk_comment('hello, world'),
@fragment,
);
my $html = to_html @document;
# Equivalent to:
# $html =
# '<!DOCTYPE html>'
# . '<title>Log in</title>'
# . '<!--hello, world-->'
# . '<form method="POST" action="/login">'
# . '<label>Username: <input name="user"></label>'
# . '<label>Password: <input name="password" type="password"></label>'
# . '</form>';
AUTHOR
Lukas Mai, <lmai at web.de>
COPYRIGHT & LICENSE
Copyright 2022 Lukas Mai.
This module is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.