File Coverage

blib/lib/MsOffice/Word/Template/Engine/TT2.pm
Criterion Covered Total %
statement 67 79 84.8
branch 6 10 60.0
condition 5 12 41.6
subroutine 16 17 94.1
pod 0 4 0.0
total 94 122 77.0


line stmt bran cond sub pod time code
1             package MsOffice::Word::Template::Engine::TT2;
2 1     1   24 use 5.024;
  1         4  
3 1     1   4 use Moose;
  1         2  
  1         10  
4             extends 'MsOffice::Word::Template::Engine';
5              
6 1     1   7094 use Template::AutoFilter; # a subclass of Template that adds automatic html filtering
  1         60400  
  1         55  
7 1     1   11 use Template::Config; # loaded explicitly so that we can override its $PROVIDER global variable
  1         3  
  1         36  
8 1     1   7 use MsOffice::Word::Surgeon::Utils qw(encode_entities);
  1         3  
  1         128  
9 1     1   773 use MsOffice::Word::Template::Engine::TT2::Provider;
  1         5  
  1         53  
10              
11 1     1   10 use namespace::clean -except => 'meta';
  1         2  
  1         14  
12              
13             our $VERSION = '2.05';
14              
15             #======================================================================
16             # ATTRIBUTES
17             #======================================================================
18              
19             has 'start_tag' => (is => 'ro', isa => 'Str', default => "[% ");
20             has 'end_tag' => (is => 'ro', isa => 'Str', default => " %]");
21             has 'TT2' => (is => 'ro', isa => 'Template', lazy => 1, builder => "_TT2", init_arg => undef);
22              
23             #======================================================================
24             # LAZY ATTRIBUTE CONSTRUCTORS
25             #======================================================================
26              
27             sub _TT2 {
28 1     1   1 my ($self) = @_;
29              
30 1         3 my $TT2_args = delete $self->{_constructor_args};
31              
32             # inject precompiled blocks into the Template parser
33 1         3 my $precompiled_blocks = $self->_precompiled_blocks;
34 1   33     15 $TT2_args->{BLOCKS}{$_} //= $precompiled_blocks->{$_} for keys %$precompiled_blocks;
35              
36             # instantiate the Template object but supply our own Provider subclass. This way of doing is a little
37             # bit rude, but so much easier than using the LOAD_TEMPLATES config options ! ... because here the
38             # Template Toolkit will take care of supplying all default values to the Provider.
39 1         3 local $Template::Config::PROVIDER = 'MsOffice::Word::Template::Engine::TT2::Provider';
40 1         33 my $tt2 = Template::AutoFilter->new($TT2_args);
41              
42 1         18517 return $tt2;
43             }
44              
45             #======================================================================
46             # METHODS
47             #======================================================================
48              
49             sub compile_template {
50 9     9 0 29 my ($self, $template_text) = @_;
51              
52 9         533 return $self->TT2->template(\$template_text);
53             }
54              
55              
56             sub process_part {
57 14     14 0 42 my ($self, $part_name, $package_part, $vars) = @_;
58              
59             # extend $vars with a pointer to the part object, so that it can be called from
60             # the template, for example for replacing an image
61 14         78 my $extended_vars = {package_part => $package_part, %$vars};
62              
63 14         43 return $self->process($part_name, $extended_vars);
64             }
65              
66              
67             sub process {
68 18     18 0 47 my ($self, $template_name, $vars) = @_;
69              
70             # get the compiled template
71 18 50       803 my $tmpl = $self->compiled_template->{$template_name}
72             or die "don't have a compiled template for '$template_name'";
73              
74             # produce the new contents
75 18         774 my $new_contents = $self->TT2->context->process($tmpl, $vars);
76              
77 18         5658 return $new_contents;
78             }
79              
80              
81             #======================================================================
82             # PRE-COMPILED BLOCKS THAT CAN BE INVOKED FROM TEMPLATE DIRECTIVES
83             #======================================================================
84              
85             # arbitrary value for the first bookmark id. 100 should be enough to be above other
86             # bookmarks generated by Word itself. A cleaner but more complicated way would be to parse the template
87             # to find the highest id number really used.
88             my $first_bookmark_id = 100;
89              
90             my %barcode_generator = (
91              
92             Code128 => sub {
93             my ($to_encode, %options) = @_;
94             require Barcode::Code128;
95              
96             $options{border} //= 0;
97             $options{show_text} //= 0;
98             $options{padding} //= 0;
99             my $bc = Barcode::Code128->new;
100             $bc->option(%options);
101             return $bc->png($to_encode);
102             },
103              
104             QRCode => sub {
105             my ($to_encode, %options) = @_;
106             require Image::PNG::QRCode;
107             return Image::PNG::QRCode::qrpng(text => $to_encode);
108             },
109              
110             );
111              
112              
113              
114             # precompiled blocks as facilities to be used within templates
115             sub _precompiled_blocks {
116              
117             return {
118              
119             # a wrapper block for inserting a Word bookmark
120             bookmark => sub {
121 8     8   940 my $context = shift;
122 8         22 my $stash = $context->stash;
123              
124             # assemble xml markup
125 8   66     71 my $bookmark_id = $stash->get('global.bookmark_id') || $first_bookmark_id;
126 8   50     51 my $name = fix_bookmark_name($stash->get('name') || 'anonymous_bookmark');
127              
128 8         45 my $xml = qq{<w:bookmarkStart w:id="$bookmark_id" w:name="$name"/>}
129             . $stash->get('content') # content of the wrapper
130             . qq{<w:bookmarkEnd w:id="$bookmark_id"/>};
131              
132             # next bookmark will need a fresh id
133 8         46 $stash->set('global.bookmark_id', $bookmark_id+1);
134              
135 8         20 return $xml;
136             },
137              
138             # a wrapper block for linking to a bookmark
139             link_to_bookmark => sub {
140 8     8   1310 my $context = shift;
141 8         25 my $stash = $context->stash;
142              
143             # assemble xml markup
144 8   50     51 my $name = fix_bookmark_name($stash->get('name') || 'anonymous_bookmark');
145 8         46 my $content = $stash->get('content');
146 8         37 my $tooltip = $stash->get('tooltip');
147 8 100       51 if ($tooltip) {
148 2         15 encode_entities($tooltip);
149 2         20 $tooltip = qq{ w:tooltip="$tooltip"};
150             }
151 8         57 my $xml = qq{<w:hyperlink w:anchor="$name"$tooltip>$content</w:hyperlink>};
152              
153 8         18 return $xml;
154             },
155              
156             # a block for generating a Word field. Can also be used as wrapper.
157             field => sub {
158 6     6   9760 my $context = shift;
159 6         19 my $stash = $context->stash;
160 6         84 my $code = $stash->get('code'); # field code, including possible flags
161 6         32 my $text = $stash->get('content'); # initial text content (before updating the field)
162              
163 6         43 my $xml = qq{<w:r><w:fldChar w:fldCharType="begin"/></w:r>}
164             . qq{<w:r><w:instrText xml:space="preserve"> $code </w:instrText></w:r>};
165 6 100       21 $xml .= qq{<w:r><w:fldChar w:fldCharType="separate"/></w:r>$text} if $text;
166 6         13 $xml .= qq{<w:r><w:fldChar w:fldCharType="end"/></w:r>};
167              
168 6         37 return $xml;
169             },
170              
171             # a block for replacing a placeholder image by a generated barcode
172             barcode => sub {
173              
174             # get parameters from stash
175 0     0   0 my $context = shift;
176 0         0 my $stash = $context->stash;
177 0         0 my $barcode_type = $stash->get('type'); # either 'Code128' or 'QRCode'
178 0         0 my $package_part = $stash->get('package_part'); # Word::Surgeon::PackagePart
179 0         0 my $img = $stash->get('img'); # title of an existing image to replace
180 0         0 my $to_encode = $stash->get('content'); # text to be encoded
181 0   0     0 my $options = $stash->get('options') || {};
182 0         0 $to_encode =~ s(<[^>]+>)()g;
183              
184             # generate PNG image
185 0 0       0 my $generator = $barcode_generator{$barcode_type}
186             or die "unknown type for barcode generator : '$barcode_type'";
187 0         0 my $png = $generator->($to_encode, %$options);
188              
189             # inject image into the .docx file
190 0         0 $package_part->replace_image($img, $png);
191              
192 0         0 return "";
193             },
194              
195 1     1   14 };
196             }
197              
198              
199              
200             #======================================================================
201             # UTILITY ROUTINES (not methods)
202             #======================================================================
203              
204              
205             sub fix_bookmark_name {
206 16     16 0 26 my $name = shift;
207              
208             # see https://stackoverflow.com/questions/852922/what-are-the-limitations-for-bookmark-names-in-microsoft-word
209              
210 16         40 $name =~ s/[^\w_]+/_/g; # only digits, letters or underscores
211 16         63 $name =~ s/^(\d)/_$1/; # cannot start with a digit
212 16 50       50 $name = substr($name, 0, 40) if length($name) > 40; # max 40 characters long
213              
214 16         47 return $name;
215             }
216              
217              
218             1;
219              
220             __END__
221              
222             =encoding ISO-8859-1
223              
224             =head1 NAME
225              
226             MsOffice::Word::Template::Engine::TT2 -- Word::Template engine based on the Template Toolkit
227              
228             =head1 SYNOPSIS
229              
230             my $template = MsOffice::Word::Template->new(docx => $filename
231             engine_class => 'TT2',
232             engine_args => \%args_for_TemplateToolkit,
233             );
234              
235             my $new_doc = $template->process(\%data);
236              
237             See the main synopsis in L<MsOffice::Word::Template>.
238              
239             =head1 DESCRIPTION
240              
241             Implements a templating engine for L<MsOffice::Word::Template>, based on the
242             L<Template Toolkit|Template>.
243              
244             Like in the regular Template Toolkit, directives like C<INSERT>, C<INCLUDE> or C<PROCESS> can be
245             used to load subtemplates from other MsWord files -- but the C<none> filter must be
246             added after the directive, as explained in L</ABOUT HTML ENTITIES>.
247              
248              
249             =head1 AUTHORING NOTES SPECIFIC TO THE TEMPLATE TOOLKIT
250              
251             This chapter just gives a few hints for authoring Word templates with the
252             Template Toolkit.
253              
254             The examples below use [[double square brackets]] to indicate
255             segments that should be highlighted in B<green> within the Word template.
256              
257              
258             =head2 Bookmarks
259              
260             The template processor is instantiated with a predefined wrapper named C<bookmark>
261             for generating Word bookmarks. Here is an example:
262              
263             Here is a paragraph with [[WRAPPER bookmark name="my_bookmark"]]bookmarked text[[END]].
264              
265             The C<name> argument is automatically truncated to 40 characters, and non-alphanumeric
266             characters are replaced by underscores, in order to comply with the limitations imposed by Word
267             for bookmark names.
268              
269             =head2 Internal hyperlinks
270              
271             Similarly, there is a predefined wrapper named C<link_to_bookmark> for generating
272             hyperlinks to bookmarks. Here is an example:
273              
274             Click [[WRAPPER link_to_bookmark name="my_bookmark" tooltip="tip top"]]here[[END]].
275              
276             The C<tooltip> argument is optional.
277              
278             =head2 Word fields
279              
280             A predefined block C<field> generates XML markup for Word fields, like for example :
281              
282             Today is [[PROCESS field code="DATE \\@ \"h:mm am/pm, dddd, MMMM d\""]]
283              
284             Beware that quotes or backslashes must be escaped so that the Template Toolkit parser
285             does not interpret these characters.
286              
287             The list of Word field codes is documented at
288             L<https://support.microsoft.com/en-us/office/list-of-field-codes-in-word-1ad6d91a-55a7-4a8d-b535-cf7888659a51>.
289              
290             When used as a wrapper, the C<field> block generates a Word field with alternative
291             text content, displayed before the field gets updated. For example :
292              
293             [[WRAPPER field code="TOC \o \"1-3\" \h \z \u"]]Table of contents - press F9 to update[[END]]
294              
295             The same result can also be obtained by using it as a regular block
296             with a C<content> argument :
297              
298             [[PROCESS field code="TOC \o \"1-3\" \h \z \u" content="Table of contents - press F9 to update"]]
299              
300              
301             =head2 barcodes
302              
303             The predefined block C<barcode> generates a barcode image to replace
304             a "placeholder image" already present in the template. This directive can
305             appear anywhere in the document, it doesn't have to be next to the image location.
306              
307             [[ PROCESS barcode type='QRCode' img='img_name' content="some text" ]]
308             # or, used as a wrapper
309             [[ WRAPPER barcode type='QRCode' img='img_name']]some text[[ END ]]
310              
311             Parameters to the C<barcode> block are :
312              
313             =over
314              
315             =item type
316              
317             The type of barcode. The currently implemented types are C<Code128> and C<QRCode>.
318              
319             =item img
320              
321             The image unique identifier (corresponding to the I<alternative text> of that image
322             within the template).
323              
324             =item content
325              
326             numeric value or text string that will be encoded as barcode
327              
328             =item options
329              
330             a hashref of options that will be passed to the barcode generator.
331             Currently only the C<Code128> generator takes options; these are
332             described in the L<Barcode::Code128> Perl module. For example :
333              
334             [[PROCESS barcode type="Code128" img="my_image" content=123456 options={border=>4, padding=>50}]]
335              
336             =back
337              
338             =head2 ABOUT HTML ENTITIES
339              
340             This module uses L<Template::AutoFilter>, a subclass of L<Template> that
341             adds automatically an 'html' filter to every templating directive, so that
342             characters such as C<< '<' >> or C<< '&' >> are automatically encoded as
343             HTML entities.
344              
345             If this encoding behaviour is I<not> appropriate, like for example if the
346             directive is meant to directly produces OOXML markup, then the 'none' filter
347             must be explicitly mentioned in order to prevent html filtering :
348              
349             [[ PROCESS ooxml_producing_block(foo, bar) | none ]]
350              
351             This is the case in particular when including
352             XML fragments from other C<.docx> documents through
353             C<INSERT>, C<INCLUDE> or C<PROCESS> directives : for example
354              
355             [[ INCLUDE lib/fragments/some_other_doc.docx | none ]]
356              
357              
358             =head1 AUTHOR
359              
360             Laurent Dami, E<lt>dami AT cpan DOT org<gt>
361              
362             =head1 COPYRIGHT AND LICENSE
363              
364             Copyright 2020-2024 by Laurent Dami.
365              
366             This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.