# html { # head { title { text "Title" } }; # body { # p { class_ "warning"; text "paragraph" } # }; # } #### # doc { # my_elem { # # children go here # }; # }; #### # my_elem { # text "some text"; # my_attr_ "value"; # }; #### our $__frag; # points to fragment under active construction sub doc(&) { my ($content_fn) = @_; local $__frag = [undef,undef,undef]; $content_fn->(); $__frag->[2][0]; } sub _elem { my ($elem_name, $content_fn) = @_; # an element is represented by the triple [name, attrs, children] my $elem = [$elem_name, undef, undef]; do { local $__frag = $elem; $content_fn->() }; push @{$__frag->[2]}, $elem; } sub _attr { my ($attr_name, $val) = @_; push @{$__frag->[1]}, [$attr_name, $val]; } sub text($) { push @{$__frag->[2]}, @_; } #### sub define_vocabulary { my ($elems, $attrs) = @_; eval "sub $_(&) { _elem('$_',\@_) }" for @$elems; eval "sub ${_}_(\$) { _attr('$_',\@_) }" for @$attrs; } #### BEGIN { define_vocabulary( [qw( html head title body h1 h2 h3 p img br )], [qw( src href class style )] ); } #### my $my_doc = doc { html { head { title { text "Title" } }; body { p { class_ "warning"; text "paragraph" } }; } }; use Data::Dumper; $Data::Dumper::Indent = $Data::Dumper::Terse = 1; print Dumper $my_doc; # [ # 'html', # undef, # [ # [ # 'head', # undef, # [ # [ # 'title', # undef, # [ # 'Title' # ] # ] # ] # ], # [ # 'body', # undef, # [ # [ # 'p', # [ # [ # 'class', # 'warning' # ] # ], # [ # 'paragraph' # ] # ] # ] # ] # ] # ] #### use XML::Writer; sub render_via_xml_writer { my $doc = shift; my $writer = XML::Writer->new(@_); # extra args go to ->new() my $render_fn; $render_fn = sub { my $frag = shift; my ($elem, $attrs, $children) = @$frag; $writer->startTag( $elem, map {@$_} @$attrs ); for (@$children) { ref() ? $render_fn->($_) : $writer->characters($_); } $writer->endTag($elem); }; $render_fn->($doc); $writer->end(); } #### render_via_xml_writer( $my_doc, DATA_MODE => 1, UNSAFE => 1 ); # # # Title # # #

paragraph

# # ##
## sub render_doc(&) { my $docfn = shift; render_via_xml_writer( doc( \&$docfn ), DATA_MODE => 1, UNSAFE => 1 ); } #### render_doc { html { head { title { text "My grand document!" } }; body { h1 { text "Heading" }; p { class_ "first"; # attribute class="first" text "This is the first paragraph!"; style_ "font: bold"; # another attr }; # it's just Perl, so we can mix in other code for (2..5) { p { text "Plus paragraph number $_." } } }; }; }; # # # My grand document! # # #

Heading

#

This is the first paragraph!

#

Plus paragraph number 2.

#

Plus paragraph number 3.

#

Plus paragraph number 4.

#

Plus paragraph number 5.

# #