package XML::XPathScript::Template;
use strict;
use warnings;
use Carp;
use Scalar::Util qw/ reftype /;
use Data::Dumper;
use XML::XPathScript::Template::Tag;
use Clone qw/ clone /;
use Scalar::Util qw/ refaddr /;
use overload '&{}' => \&_overload_func,
q{""} => \&_overload_quote;
our $VERSION = '1.54';
sub new {
my( $class ) = @_;
my $self = {};
bless $self, $class;
return $self;
}
sub set { ##no critic
croak "method set called with more than two arguments" if @_ > 3;
my( $self, $tag, $attribute_ref ) = @_;
my $type = reftype $tag;
my @templates = # templates to change
!$type ? $self->{$tag}
||= new XML::XPathScript::Template::Tag
: $type eq 'ARRAY' ? map { $self->{$_}
||= new XML::XPathScript::Template::Tag
} @$tag
: croak "tag cannot be of type $type"
;
$_->set( $attribute_ref ) for @templates;
return;
}
sub copy {
my( $self, $src, $copy, $attributes_ref ) = @_;
croak "tag $src not found in template"
unless $self->{$src};
my %attributes = %{ $self->{$src} };
%attributes = map { $_ => $attributes{ $_ } }@$attributes_ref
if $attributes_ref;
$self->set( $copy, \%attributes );
return;
}
sub alias {
my( $self, $src, $copy ) = @_;
$self->{$_} = $self->{$src} for ref( $copy ) ? @$copy : $copy;
return;
}
sub dump { ##no critic
my( $self, @tags ) = @_;
my %template = %{$self};
@tags = keys %template unless @tags;
%template = map { $_ => $template{ $_ } } @tags;
return Data::Dumper->Dump( [ \%template ], [ 'template' ] );
}
sub clear {
my( $self, $tags ) = @_;
delete $self->{ $_ } for $tags
? @$tags
: grep { !/^:/ } keys %$self; ##no critic
return;
}
sub is_alias {
my( $self, $tag ) = @_;
my $id = $self->{$tag};
my @aliases = grep { $_ ne $tag
and refaddr( $self->{$_} ) eq refaddr( $id ) }
keys %{$self};
return @aliases;
}
sub unalias {
my( $self, $tag ) = @_;
my $fresh = new XML::XPathScript::Template::Tag;
$fresh->set( $self->{$tag} );
$self->{$tag} = $fresh;
return;
}
sub namespace {
my( $self, $namespace ) = @_;
return $self->{ ":$namespace" } ||= new XML::XPathScript::Template;
}
sub resolve {
my $template = shift;
my( $namespace, $tag ) = @_ == 2 ? @_ : ( undef, @_ );
no warnings qw/ uninitialized /;
$namespace = ':'.$namespace;
return ( ( $template->{$namespace} && # selection order
( $template->{$namespace}{$tag} # foo:bar
|| $template->{$namespace}{'*'} ) ) # foo:*
|| $template->{$tag} # bar
|| $template->{'*'} ); # *
# (and undef if nothing)
}
sub import_template {
my( $self, $other_template ) = @_;
carp "incorrect call for import_template(): no argument or is not a template"
unless $other_template and $other_template =~ /HASH/;
for my $k ( keys %$other_template ) {
if ( 0 == index $k, ':' ) { # it's a namespace
my $ns = $k;
$ns =~ s/^://;
my $subtemplate = $self->namespace( $ns );
$subtemplate->import( $other_template->{$k} );
}
else { # it's a regular tag
$self->set( $k => $other_template->{$k} );
}
}
return;
}
sub _overload_func {
my $self = shift;
return sub { $self->set( @_ ) }
}
sub _overload_quote {
my $self = shift;
return $self;
return sub { $self };
}
1;
__END__
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Module Documentation
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
=head1 NAME
XML::XPathScript::Template - XML::XPathScript transformation template
=head1 SYNOPSIS
<%
$t->set( 'important' => { 'pre' => '',
'prechild' => '',
'postchild' => '',
} );
# urgent and annoying share the 'pre' and 'post'
# of important
$t->copy( 'important' => [ qw/ urgent annoying / ],
[ qw/ pre post / ], );
# redHot is a synonym of important
$t->alias( 'important' => 'redHot' );
%>
<%= apply_templates() %>
=head1 DESCRIPTION
A stylesheet's template defines the transformations and actions that
are performed on the tags of a document as they are processed.
The template of a stylesheet can be accessed via variables
I<$t>, I<$template> and I<$XML::XPathScript::trans>.
=head1 METHODS
=head2 new
$template = XML::XPathScript::Template->new
Creates and returns a new, empty template.
=head2 set
$template->set( $tag, \%attributes )
$template->set( \@tags , \%attributes )
Updates the $tag or @tags in the template with the
given %attributes.
Thank to the magic of overloading, using the $template
as a code reference acts as a shortcut to I.
Example:
$template->set( 'foo' => { pre => '', post => '' } );
# or, if you prefer,
$template->( 'foo' => { pre => '', post => '' } );
=head2 copy
$template->copy( $original_tag, $copy_tag );
$template->copy( $original_tag, $copy_tag, \@attributes );
$template->copy( $original_tag, \@copy_tags );
$template->copy( $original_tag, \@copy_tags, \@attributes );
Copies all attributes (or a subset of them if @attributes is given)
of $original_tag to $copy_tag.
Note that subsequent modifications of the original tag will not
affect the copies. To bind several tags to the same behavior, see
L.
Example:
# copy the attributes 'pre' and 'post' of important
# to 'urgent' and 'redHot'
$template->copy( 'important' => [ qw/ urgent redHot / ],
[ qw/ pre post / ] );
=head2 import_template
$template->import_template( $other_template )
Imports another template into the current one.
=head2 alias
$template->alias( $original_tag => $alias_tag )
$template->alias( $original_tag => \@alias_tags )
Makes the target tags aliases to the original tag. Further
modifications that will be done on any of these tags will
be reflected on all others.
Example:
$template->alias( 'foo' => 'bar' );
# also modifies 'foo'
$template->set( 'bar' => { pre => '' } );
=head2 is_alias
@aliases = $template->is_alias( $tag )
Returns all tags that are aliases to $tag.
=head2 unalias
$template->unalias( $tag )
Unmerge $tag of its aliases, if it has any. Further modifications to
$tag will not affect the erstwhile aliases, and vice versa.
Example:
$template->alias( 'foo' => [ qw/ bar baz / ] );
$template->set( 'foo' => { pre => '' } ); # affects foo, bar and baz
$template->unalias( 'bar' );
$template->set( 'bar' => { pre => '' } ); # affects only bar
$template->set( 'baz' => { pre => '' } ); # affects foo and baz
=head2 clear
$template->clear()
$template->clear( \@tags )
Delete all tags, or those given by @tags, from the template.
Example:
$template->clear([ 'foo', 'bar' ]);
=head2 dump
$template->dump()
$template->dump( @tags )
Returns a pretty-printed dump of the templates. If @tags are
specified, only return their templates.
Example:
<%= $template->dump( 'foo' ) %>
# will yield something like
#
# $template = {
# foo => {
# post => '',
# pre => '',
# }
# };
=head2 namespace
my $subtemplate = $template->namespace( $uri );
Returns the sub-template associated to the namespace defined by $uri.
Example:
$template->set( 'foo' => { 'pre' => 'within default namespace' } );
my $subtemplate = $template->namespace( 'http://www.www3c.org/blah/' );
$subtemplate->set( 'foo' => { 'pre' => "within 'blah' namespace" } );
=head2 resolve
$tag = $template->resolve( $namespace, $tagname );
$tag = $template->resolve( $tagname );
Returns the tag object within $template that matches $namespace and
$tagname best. The returned match is the first one met in the following
list:
=over
=item *
$namespace:$tagname
=item *
$namespace:*
=item *
$tagname
=item *
*
=item *
undef
=back
Example:
$template->set( foo => { pre => 'a' } );
$template->set( '*' => { pre => 'b' } );
$template->namespace( 'http://blah' )->set( foo => { pre => 'c' } );
$template->namespace( 'http://blah' )->set( '*' => { pre => 'd' } );
$template->resolve( 'foo' )->get( 'pre' ); # returns 'a'
$template->resolve( 'baz' )->get( 'pre' ); # returns 'b'
$template->resolve( 'http://meeh', 'foo' )->get( 'pre' ); # returns 'a'
$template->resolve( 'http://blah', 'foo' )->get( 'pre' ); # returns 'c'
$template->resolve( 'http://blah', 'baz' )->get( 'pre' ); # returns 'd'
=head1 BACKWARD COMPATIBILITY
Prior to version 1.0 of XML::XPathScript, the template of a
stylesheet was not an object but a simple hash reference. Modifications
to the template were done by manipulating the hash directly.
<%
# pre-1.0 way of manipulating the template
$t->{important}{pre} = '';
for my $tag ( qw/ urgent redHot / ) {
for my $attr ( qw/ pre post / ) {
$t->{$tag}{$attr} = $t->{important}{$attr};
}
}
$t->{ alert } = $t->{ important };
%>
Don't tell anyone, but as an XML::XPathScript::Template is
a blessed hash reference this way of doing things will
still work. However, direct manipulation of the template's hash
is deprecated. Instead, it is recommended to use the object's
access methods.
<%
# correct way to manipulate the template
$t->set( important => { pre => '',
showtag => 1
} );
$t->copy( important => [ qw/ urgent redHot / ], [ qw/ pre post / ] );
$t->alias( important => alert );
%>
=head1 BUGS
Please send bug reports to ,
or via the web interface at
http://rt.cpan.org/Public/Dist/Display.html?Name=XML-XPathScript .
=head1 AUTHOR
Yanick Champoux
=cut