File Coverage

blib/lib/MsOffice/Word/Template/Engine/TT2.pm
Criterion Covered Total %
statement 56 68 82.3
branch 6 8 75.0
condition 5 10 50.0
subroutine 13 14 92.8
pod 0 3 0.0
total 80 103 77.6


line stmt bran cond sub pod time code
1             use 5.024;
2 1     1   24 use Template::AutoFilter; # a subclass of Template that adds automatic html filtering
  1         2  
3 1     1   392 use HTML::Entities qw(encode_entities);
  1         53098  
  1         29  
4 1     1   6  
  1         2  
  1         45  
5             use Moose;
6 1     1   6 extends 'MsOffice::Word::Template::Engine';
  1         1  
  1         6  
7              
8             # syntactic sugar for attributes
9              
10             use namespace::clean -except => 'meta';
11              
12 1     1   6097 our $VERSION = '2.0';
  1         2  
  1         9  
13              
14             #======================================================================
15             # ATTRIBUTES
16             #======================================================================
17              
18             has 'start_tag' => (is => 'ro', isa => 'Str', default => "[% ");
19             has 'end_tag' => (is => 'ro', isa => 'Str', default => " %]");
20             has_inner 'TT2' => (is => 'ro', isa => 'Template');
21              
22             #======================================================================
23             # LAZY ATTRIBUTE CONSTRUCTORS
24             #======================================================================
25              
26             my ($self) = @_;
27              
28             my $TT2_args = delete $self->{_constructor_args};
29 1     1   2  
30             # inject precompiled blocks into the Template parser
31 1         1 my $precompiled_blocks = $self->_precompiled_blocks;
32             $TT2_args->{BLOCKS}{$_} //= $precompiled_blocks->{$_} for keys %$precompiled_blocks;
33              
34 1         3 return Template::AutoFilter->new($TT2_args);
35 1   33     12 }
36              
37 1         6 #======================================================================
38             # METHODS
39             #======================================================================
40              
41             my ($self, $part_name, $template_text) = @_;
42              
43             $self->{_compiled_template}{$part_name} = $self->TT2->template(\$template_text);
44             }
45 7     7 0 42  
46              
47 7         195 my ($self, $part_name, $package_part, $vars) = @_;
48              
49             # get the compiled template
50             my $tmpl = $self->{_compiled_template}{$part_name}
51             or die "don't have a compiled template for '$part_name'";
52 14     14 0 30  
53             # extend $vars with a pointer to the part object, so that it can be called from
54             # the template, for example for replacing an image
55 14 50       40 my $extended_vars = {package_part => $package_part, %$vars};
56              
57             # produce the new contents
58             my $new_contents = $self->TT2->context->process($tmpl, $extended_vars);
59              
60 14         52 return $new_contents;
61             }
62              
63 14         362  
64             #======================================================================
65 14         2023 # PRE-COMPILED BLOCKS THAT CAN BE INVOKED FROM TEMPLATE DIRECTIVES
66             #======================================================================
67              
68             # arbitrary value for the first bookmark id. 100 should most often be above other
69             # bookmarks generated by Word itself. TODO : would be better to find the highest
70             # id number really used in the template
71             my $first_bookmark_id = 100;
72              
73             # precompiled blocks as facilities to be used within templates
74              
75             return {
76              
77             # a wrapper block for inserting a Word bookmark
78             bookmark => sub {
79             my $context = shift;
80             my $stash = $context->stash;
81              
82             # assemble xml markup
83             my $bookmark_id = $stash->get('global.bookmark_id') || $first_bookmark_id;
84             my $name = fix_bookmark_name($stash->get('name') || 'anonymous_bookmark');
85 8     8   794  
86 8         17 my $xml = qq{<w:bookmarkStart w:id="$bookmark_id" w:name="$name"/>}
87             . $stash->get('content') # content of the wrapper
88             . qq{<w:bookmarkEnd w:id="$bookmark_id"/>};
89 8   66     75  
90 8   50     47 # next bookmark will need a fresh id
91             $stash->set('global.bookmark_id', $bookmark_id+1);
92 8         46  
93             return $xml;
94             },
95              
96             # a wrapper block for linking to a bookmark
97 8         59 link_to_bookmark => sub {
98             my $context = shift;
99 8         18 my $stash = $context->stash;
100              
101             # assemble xml markup
102             my $name = fix_bookmark_name($stash->get('name') || 'anonymous_bookmark');
103             my $content = $stash->get('content');
104 8     8   1142 my $tooltip = $stash->get('tooltip');
105 8         17 if ($tooltip) {
106             $tooltip = sprintf qq{ w:tooltip="%s"}, encode_entities($tooltip, '<>&"');
107             }
108 8   50     56 my $xml = qq{<w:hyperlink w:anchor="$name"$tooltip>$content</w:hyperlink>};
109 8         33  
110 8         40 return $xml;
111 8 100       43 },
112 2         11  
113             # a block for generating a Word field. Can also be used as wrapper.
114 8         248 field => sub {
115             my $context = shift;
116 8         18 my $stash = $context->stash;
117             my $code = $stash->get('code'); # field code, including possible flags
118             my $text = $stash->get('content'); # initial text content (before updating the field)
119              
120             my $xml = qq{<w:r><w:fldChar w:fldCharType="begin"/></w:r>}
121 6     6   4839 . qq{<w:r><w:instrText xml:space="preserve"> $code </w:instrText></w:r>};
122 6         15 $xml .= qq{<w:r><w:fldChar w:fldCharType="separate"/></w:r>$text} if $text;
123 6         36 $xml .= qq{<w:r><w:fldChar w:fldCharType="end"/></w:r>};
124 6         29  
125             return $xml;
126 6         35 },
127              
128 6 100       16  
129 6         14 # a block for replacing an image by a new barcode
130             barcode => sub {
131 6         14 require Barcode::Code128;
132              
133             my $context = shift;
134             my $stash = $context->stash;
135             my $package_part = $stash->get('package_part'); # Word::Surgeon::PackagePart
136             my $img = $stash->get('img'); # title of an existing image to replace
137 0     0   0 my $to_encode = $stash->get('content'); # text to be encoded
138             $to_encode =~ s(<[^>]+>)()g;
139 0         0  
140 0         0 # create PNG image
141 0         0 my $bc = Barcode::Code128->new;
142 0         0 $bc->option(border => 0,
143 0         0 show_text => 0,
144 0         0 padding => 0);
145             my $png = $bc->png($to_encode);
146             $package_part->replace_image($img, $png);
147 0         0 return "";
148 0         0 },
149             }
150             }
151 0         0  
152 0         0  
153 0         0  
154             #======================================================================
155             # UTILITY ROUTINES (not methods)
156 1     1   13 #======================================================================
157              
158              
159             my $name = shift;
160              
161             # see https://stackoverflow.com/questions/852922/what-are-the-limitations-for-bookmark-names-in-microsoft-word
162              
163             $name =~ s/[^\w_]+/_/g; # only digits, letters or underscores
164             $name =~ s/^(\d)/_$1/; # cannot start with a digit
165             $name = substr($name, 0, 40) if length($name) > 40; # max 40 characters long
166 16     16 0 25  
167             return $name;
168             }
169              
170 16         34  
171 16         31 1;
172 16 50       31  
173              
174 16         25 =encoding ISO-8859-1
175              
176             =head1 NAME
177              
178             MsOffice::Word::Template::Engine::TT2 -- Word::Template engine based on the Template Toolkit
179              
180             =head1 SYNOPSIS
181              
182             my $template = MsOffice::Word::Template->new(docx => $filename
183             engine_class => 'TT2',
184             engine_args => \%args_for_TemplateToolkit,
185             );
186              
187             my $new_doc = $template->process(\%data);
188              
189             See the main synopsis in L<MsOffice::Word::Template>.
190              
191             =head1 DESCRIPTION
192              
193             Implements a templating engine for L<MsOffice::Word::Template>, based on the
194             L<Template Toolkit|Template>.
195              
196              
197             =head1 AUTHORING NOTES SPECIFIC TO THE TEMPLATE TOOLKIT
198              
199             This chapter just gives a few hints for authoring Word templates with the
200             Template Toolkit.
201              
202             The examples below use [[double square brackets]] to indicate
203             segments that should be highlighted in B<green> within the Word template.
204              
205              
206             =head2 Bookmarks
207              
208             The template processor is instantiated with a predefined wrapper named C<bookmark>
209             for generating Word bookmarks. Here is an example:
210              
211             Here is a paragraph with [[WRAPPER bookmark name="my_bookmark"]]bookmarked text[[END]].
212              
213             The C<name> argument is automatically truncated to 40 characters, and non-alphanumeric
214             characters are replaced by underscores, in order to comply with the limitations imposed by Word
215             for bookmark names.
216              
217             =head2 Internal hyperlinks
218              
219             Similarly, there is a predefined wrapper named C<link_to_bookmark> for generating
220             hyperlinks to bookmarks. Here is an example:
221              
222             Click [[WRAPPER link_to_bookmark name="my_bookmark" tooltip="tip top"]]here[[END]].
223              
224             The C<tooltip> argument is optional.
225              
226             =head2 Word fields
227              
228             A predefined block C<field> generates XML markup for Word fields, like for example :
229              
230             Today is [[PROCESS field code="DATE \\@ \"h:mm am/pm, dddd, MMMM d\""]]
231              
232             Beware that quotes or backslashes must be escaped so that the Template Toolkit parser
233             does not interpret these characters.
234              
235             The list of Word field codes is documented at
236             L<https://support.microsoft.com/en-us/office/list-of-field-codes-in-word-1ad6d91a-55a7-4a8d-b535-cf7888659a51>.
237              
238             When used as a wrapper, the C<field> block generates a Word field with alternative
239             text content, displayed before the field gets updated. For example :
240              
241             [[WRAPPER field code="TOC \o \"1-3\" \h \z \u"]]Table of contents - press F9 to update[[END]]
242              
243              
244              
245             =head1 AUTHOR
246              
247             Laurent Dami, E<lt>dami AT cpan DOT org<gt>
248              
249             =head1 COPYRIGHT AND LICENSE
250              
251             Copyright 2020-2022 by Laurent Dami.
252              
253             This library is free software; you can redistribute it and/or modify
254             it under the same terms as Perl itself.