File Coverage

blib/lib/XML/XPathScript/Template.pm
Criterion Covered Total %
statement 87 97 89.6
branch 15 24 62.5
condition 7 15 46.6
subroutine 23 25 92.0
pod 11 11 100.0
total 143 172 83.1


line stmt bran cond sub pod time code
1             package XML::XPathScript::Template;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: XML::XPathScript transformation template
4             $XML::XPathScript::Template::VERSION = '2.00';
5 27     27   392578 use strict;
  27         53  
  27         641  
6 27     27   110 use warnings;
  27         54  
  27         514  
7              
8 27     27   110 use Carp;
  27         40  
  27         1287  
9 27     27   134 use Scalar::Util qw/ reftype /;
  27         57  
  27         1403  
10 27     27   13146 use Data::Dumper;
  27         139084  
  27         1513  
11 27     27   9596 use XML::XPathScript::Template::Tag;
  27         59  
  27         707  
12 27     27   8787 use Clone qw/ clone /;
  27         52320  
  27         1338  
13 27     27   160 use Scalar::Util qw/ refaddr /;
  27         43  
  27         1372  
14              
15 27         162 use overload '&{}' => \&_overload_func,
16 27     27   162 q{""} => \&_overload_quote;
  27         50  
17              
18             sub new {
19 85     85 1 491 my( $class ) = @_;
20              
21 85         172 my $self = {};
22 85         157 bless $self, $class;
23              
24 85         1232 return $self;
25             }
26              
27             sub set { ##no critic
28 40 50   40 1 2694 croak "method set called with more than two arguments" if @_ > 3;
29              
30 40         76 my( $self, $tag, $attribute_ref ) = @_;
31              
32 40         90 my $type = reftype $tag;
33             my @templates = # templates to change
34             !$type ? $self->{$tag}
35             ||= new XML::XPathScript::Template::Tag
36 40 50 66     446 : $type eq 'ARRAY' ? map { $self->{$_}
  4 100 33     19  
37             ||= new XML::XPathScript::Template::Tag
38             } @$tag
39             : croak "tag cannot be of type $type"
40             ;
41              
42 40         160 $_->set( $attribute_ref ) for @templates;
43              
44 40         76 return;
45             }
46              
47             sub copy {
48 3     3 1 901 my( $self, $src, $copy, $attributes_ref ) = @_;
49              
50             croak "tag $src not found in template"
51 3 50       9 unless $self->{$src};
52              
53 3         4 my %attributes = %{ $self->{$src} };
  3         11  
54 3 100       9 %attributes = map { $_ => $attributes{ $_ } }@$attributes_ref
  1         4  
55             if $attributes_ref;
56            
57 3         8 $self->set( $copy, \%attributes );
58              
59 3         7 return;
60             }
61              
62             sub alias {
63 2     2 1 1327 my( $self, $src, $copy ) = @_;
64              
65 2 100       16 $self->{$_} = $self->{$src} for ref( $copy ) ? @$copy : $copy;
66              
67 2         5 return;
68             }
69              
70              
71             sub dump { ##no critic
72 1     1 1 5 my( $self, @tags ) = @_;
73            
74 1         2 my %template = %{$self};
  1         4  
75            
76 1 50       4 @tags = keys %template unless @tags;
77            
78 1         2 %template = map { $_ => $template{ $_ } } @tags;
  1         4  
79            
80 1         8 return Data::Dumper->Dump( [ \%template ], [ 'template' ] );
81             }
82              
83             sub clear {
84 0     0 1 0 my( $self, $tags ) = @_;
85              
86 0 0       0 delete $self->{ $_ } for $tags
87             ? @$tags
88 0         0 : grep { !/^:/ } keys %$self; ##no critic
89 0         0 return;
90             }
91              
92              
93             sub is_alias {
94 2     2 1 434 my( $self, $tag ) = @_;
95              
96 2         6 my $id = $self->{$tag};
97              
98             my @aliases = grep { $_ ne $tag
99 14 100       51 and refaddr( $self->{$_} ) eq refaddr( $id ) }
100 2         3 keys %{$self};
  2         6  
101              
102 2         11 return @aliases;
103             }
104              
105             sub unalias {
106 1     1 1 2 my( $self, $tag ) = @_;
107              
108 1         4 my $fresh = new XML::XPathScript::Template::Tag;
109              
110 1         4 $fresh->set( $self->{$tag} );
111              
112 1         2 $self->{$tag} = $fresh;
113              
114 1         2 return;
115             }
116              
117             sub namespace {
118 2     2 1 8 my( $self, $namespace ) = @_;
119              
120 2   33     10 return $self->{ ":$namespace" } ||= new XML::XPathScript::Template;
121             }
122              
123             sub resolve {
124 1617     1617 1 2257 my $template = shift;
125 1617 50       3352 my( $namespace, $tag ) = @_ == 2 ? @_ : ( undef, @_ );
126              
127 27     27   18181 no warnings qw/ uninitialized /;
  27         64  
  27         8123  
128 1617         3053 $namespace = ':'.$namespace;
129              
130             return ( ( $template->{$namespace} && # selection order
131             ( $template->{$namespace}{$tag} # foo:bar
132             || $template->{$namespace}{'*'} ) ) # foo:*
133             || $template->{$tag} # bar
134 1617   66     9613 || $template->{'*'} ); # *
135             # (and undef if nothing)
136             }
137              
138             sub import_template {
139 1     1 1 5 my( $self, $other_template ) = @_;
140              
141 1 50 33     3 carp "incorrect call for import_template(): no argument or is not a template"
142             unless $other_template and $other_template =~ /HASH/;
143              
144 1         3 for my $k ( keys %$other_template ) {
145 1 50       4 if ( 0 == index $k, ':' ) { # it's a namespace
146 0         0 my $ns = $k;
147 0         0 $ns =~ s/^://;
148 0         0 my $subtemplate = $self->namespace( $ns );
149 0         0 $subtemplate->import( $other_template->{$k} );
150             }
151             else { # it's a regular tag
152 1         3 $self->set( $k => $other_template->{$k} );
153             }
154             }
155              
156 1         2 return;
157             }
158              
159             sub _overload_func {
160 1     1   23 my $self = shift;
161 1     1   4 return sub { $self->set( @_ ) }
162 1         4 }
163              
164             sub _overload_quote {
165 10     10   978 my $self = shift;
166 10         39 return $self;
167 0     0     return sub { $self };
  0            
168             }
169              
170             1;
171              
172             =pod
173              
174             =encoding UTF-8
175              
176             =head1 NAME
177              
178             XML::XPathScript::Template - XML::XPathScript transformation template
179              
180             =head1 VERSION
181              
182             version 2.00
183              
184             =head1 SYNOPSIS
185              
186             <%
187             $t->set( 'important' => { 'pre' => '',
188             'post' => '',
189             'prechild' => '',
190             'postchild' => '',
191             } );
192              
193             # urgent and annoying share the 'pre' and 'post'
194             # of important
195             $t->copy( 'important' => [ qw/ urgent annoying / ],
196             [ qw/ pre post / ], );
197              
198             # redHot is a synonym of important
199             $t->alias( 'important' => 'redHot' );
200              
201             %>
202             <%= apply_templates() %>
203              
204             =head1 DESCRIPTION
205              
206             A stylesheet's template defines the transformations and actions that
207             are performed on the tags of a document as they are processed.
208              
209             The template of a stylesheet can be accessed via variables
210             I<$t>, I<$template> and I<$XML::XPathScript::trans>.
211              
212             =head1 METHODS
213              
214             =head2 new
215              
216             $template = XML::XPathScript::Template->new
217              
218             Creates and returns a new, empty template.
219              
220             =head2 set
221              
222             $template->set( $tag, \%attributes )
223             $template->set( \@tags , \%attributes )
224              
225             Updates the $tag or @tags in the template with the
226             given %attributes.
227              
228             Thank to the magic of overloading, using the $template
229             as a code reference acts as a shortcut to I.
230              
231             Example:
232              
233             $template->set( 'foo' => { pre => '', post => '' } );
234             # or, if you prefer,
235             $template->( 'foo' => { pre => '', post => '' } );
236              
237             =head2 copy
238              
239             $template->copy( $original_tag, $copy_tag );
240             $template->copy( $original_tag, $copy_tag, \@attributes );
241             $template->copy( $original_tag, \@copy_tags );
242             $template->copy( $original_tag, \@copy_tags, \@attributes );
243              
244             Copies all attributes (or a subset of them if @attributes is given)
245             of $original_tag to $copy_tag.
246              
247             Note that subsequent modifications of the original tag will not
248             affect the copies. To bind several tags to the same behavior, see
249             L.
250              
251             Example:
252              
253             # copy the attributes 'pre' and 'post' of important
254             # to 'urgent' and 'redHot'
255             $template->copy( 'important' => [ qw/ urgent redHot / ],
256             [ qw/ pre post / ] );
257              
258             =head2 import_template
259              
260             $template->import_template( $other_template )
261              
262             Imports another template into the current one.
263              
264             =head2 alias
265              
266             $template->alias( $original_tag => $alias_tag )
267             $template->alias( $original_tag => \@alias_tags )
268              
269             Makes the target tags aliases to the original tag. Further
270             modifications that will be done on any of these tags will
271             be reflected on all others.
272              
273             Example:
274              
275             $template->alias( 'foo' => 'bar' );
276            
277             # also modifies 'foo'
278             $template->set( 'bar' => { pre => '' } );
279              
280             =head2 is_alias
281              
282             @aliases = $template->is_alias( $tag )
283              
284             Returns all tags that are aliases to $tag.
285              
286             =head2 unalias
287              
288             $template->unalias( $tag )
289              
290             Unmerge $tag of its aliases, if it has any. Further modifications to
291             $tag will not affect the erstwhile aliases, and vice versa.
292              
293             Example:
294              
295             $template->alias( 'foo' => [ qw/ bar baz / ] );
296             $template->set( 'foo' => { pre => '' } ); # affects foo, bar and baz
297             $template->unalias( 'bar' );
298             $template->set( 'bar' => { pre => '' } ); # affects only bar
299             $template->set( 'baz' => { pre => '' } ); # affects foo and baz
300              
301             =head2 clear
302              
303             $template->clear()
304             $template->clear( \@tags )
305              
306             Delete all tags, or those given by @tags, from the template.
307              
308             Example:
309              
310             $template->clear([ 'foo', 'bar' ]);
311              
312             =head2 dump
313              
314             $template->dump()
315             $template->dump( @tags )
316              
317             Returns a pretty-printed dump of the templates. If @tags are
318             specified, only return their templates.
319              
320             Example:
321              
322             <%= $template->dump( 'foo' ) %>
323            
324             # will yield something like
325             #
326             # $template = {
327             # foo => {
328             # post => '',
329             # pre => '',
330             # }
331             # };
332              
333             =head2 namespace
334              
335             my $subtemplate = $template->namespace( $uri );
336              
337             Returns the sub-template associated to the namespace defined by $uri.
338              
339             Example:
340              
341             $template->set( 'foo' => { 'pre' => 'within default namespace' } );
342             my $subtemplate = $template->namespace( 'http://www.www3c.org/blah/' );
343             $subtemplate->set( 'foo' => { 'pre' => "within 'blah' namespace" } );
344              
345             =head2 resolve
346              
347             $tag = $template->resolve( $namespace, $tagname );
348             $tag = $template->resolve( $tagname );
349              
350             Returns the tag object within $template that matches $namespace and
351             $tagname best. The returned match is the first one met in the following
352             list:
353              
354             =over
355              
356             =item *
357              
358             $namespace:$tagname
359              
360             =item *
361              
362             $namespace:*
363              
364             =item *
365              
366             $tagname
367              
368             =item *
369              
370             *
371              
372             =item *
373              
374             undef
375              
376             =back
377              
378             Example:
379              
380             $template->set( foo => { pre => 'a' } );
381             $template->set( '*' => { pre => 'b' } );
382             $template->namespace( 'http://blah' )->set( foo => { pre => 'c' } );
383             $template->namespace( 'http://blah' )->set( '*' => { pre => 'd' } );
384              
385             $template->resolve( 'foo' )->get( 'pre' ); # returns 'a'
386             $template->resolve( 'baz' )->get( 'pre' ); # returns 'b'
387             $template->resolve( 'http://meeh', 'foo' )->get( 'pre' ); # returns 'a'
388             $template->resolve( 'http://blah', 'foo' )->get( 'pre' ); # returns 'c'
389             $template->resolve( 'http://blah', 'baz' )->get( 'pre' ); # returns 'd'
390              
391             =head1 BACKWARD COMPATIBILITY
392              
393             Prior to version 1.0 of XML::XPathScript, the template of a
394             stylesheet was not an object but a simple hash reference. Modifications
395             to the template were done by manipulating the hash directly.
396              
397             <%
398             # pre-1.0 way of manipulating the template
399             $t->{important}{pre} = '';
400             $t->{important}{post} = '';
401            
402             for my $tag ( qw/ urgent redHot / ) {
403             for my $attr ( qw/ pre post / ) {
404             $t->{$tag}{$attr} = $t->{important}{$attr};
405             }
406             }
407              
408             $t->{ alert } = $t->{ important };
409             %>
410              
411             Don't tell anyone, but as an XML::XPathScript::Template is
412             a blessed hash reference this way of doing things will
413             still work. However, direct manipulation of the template's hash
414             is deprecated. Instead, it is recommended to use the object's
415             access methods.
416              
417             <%
418             # correct way to manipulate the template
419             $t->set( important => { pre => '',
420             post => '',
421             showtag => 1
422             } );
423              
424             $t->copy( important => [ qw/ urgent redHot / ], [ qw/ pre post / ] );
425              
426             $t->alias( important => alert );
427             %>
428              
429             =head1 AUTHORS
430              
431             =over 4
432              
433             =item *
434              
435             Yanick Champoux
436              
437             =item *
438              
439             Dominique Quatravaux
440              
441             =item *
442              
443             Matt Sergeant
444              
445             =back
446              
447             =head1 COPYRIGHT AND LICENSE
448              
449             This software is copyright (c) 2019, 2018, 2008, 2007 by Matt Sergeant.
450              
451             This is free software; you can redistribute it and/or modify it under
452             the same terms as the Perl 5 programming language system itself.
453              
454             =cut
455              
456             __END__