File Coverage

blib/lib/Data/Org/Template.pm
Criterion Covered Total %
statement 343 405 84.6
branch 129 178 72.4
condition 21 30 70.0
subroutine 46 64 71.8
pod 4 7 57.1
total 543 684 79.3


line stmt bran cond sub pod time code
1             package Data::Org::Template;
2              
3 5     5   315304 use 5.006;
  5         50  
4 5     5   26 use strict;
  5         8  
  5         93  
5 5     5   21 use warnings;
  5         8  
  5         132  
6              
7 5     5   22 use Carp;
  5         7  
  5         252  
8 5     5   2302 use Iterator::Simple;
  5         21364  
  5         207  
9 5     5   606 use Data::Dumper;
  5         5759  
  5         9412  
10              
11             =head1 NAME
12              
13             Data::Org::Template - template engine that plays well with iterators
14              
15             =head1 VERSION
16              
17             Version 0.04
18              
19             =cut
20              
21             our $VERSION = '0.04';
22              
23              
24             =head1 SYNOPSIS
25              
26            
27             =head1 CREATING A TEMPLATE
28              
29             =head2 new (text, [start, end])
30              
31             Aside from the text to be loaded as a template, you can also provide delimiters for the fields in C and C. If only one string is provided, it is assumed to encode
32             both start *and* end, and will be split in equal halves. The default delimiters are C<[[> and C<]]>.
33              
34             =cut
35              
36             # [< template_test2|parse_template]
37             sub new {
38 26     26 1 13138 my ($class, $text, $start, $end) = @_;
39 26         62 my $self = bless ({}, $class);
40            
41 26 100       72 if (ref($text) eq 'ARRAY') {
42 1         3 $self->{template} = $text;
43             } else {
44 25         52 $self->{template} = _parse_template ($text, $start, $end);
45             }
46 26         170 $self;
47             }
48              
49              
50              
51             sub _parse_template {
52 25     25   53 my ($text, $start, $end) = @_;
53 25 100       59 if (not defined $end) {
54 24 100       66 if (not defined $start) {
55 20         29 $start = '[[';
56 20         88 $end = ']]';
57             } else {
58 4 50       14 if (length ($start) % 2) {
59 0         0 croak "unmatched template field delimiters $start";
60             } else {
61 4         14 $end = substr($start, length($start)/2);
62 4         10 $start = substr($start, 0, length($start)/2);
63             }
64             }
65             }
66 25         40 my $template = [];
67              
68 25         59 my @pieces = _split_template ($text, $start, $end);
69 25         56 my @stack = ([0, $template]); # 2018-09-08 the stack for pushing section directives
70 25         34 my $swallow_nl = 0;
71 25         55 while (@pieces) {
72 125         166 my $piece = shift(@pieces);
73 125         242 my ($type, $what) = @$piece;
74 125 100       209 if ($type) {
75 55 100 100     259 if ($what eq '.' or $what =~ /^\.\|/) {
    100          
    100          
76 2         6 push @$template, ['?', $what];
77             }
78             elsif ($what =~ /^\./) { # 2018-09-08 section/subsection tag
79 23         30 $swallow_nl = 1;
80 23 100       51 if ($what eq '..') { # section end
    100          
81 8 50       14 shift @stack if ($stack[0]->[0]); # Pop top of stack if it's a subsection.
82 8 50       55 shift @stack unless scalar (@stack) < 2; # .. pops the stack unless the stack is already at the top
83 8         24 $template = $stack[0]->[1];
84             } elsif ($what =~ /^\.\./) { # new subsection
85 7 50       17 shift @stack if ($stack[0]->[0]); # Subsections can't nest.
86 7         12 my $section = $stack[0]->[1];
87 7         18 $what =~ s/^\.\.//;
88 7         17 my ($tag, $parm) = split " ", $what, 2;
89             #my $section = node_add_child ($template, defined $parm ? node_from_string ("$tag: " . '"' . quote_escape($parm) . '"')
90             # : node_from_string ($tag));
91 7         13 my $subsection = [$tag, $parm, []];
92 7         15 $section->[2]->{$tag} = $subsection;
93 7         12 unshift @stack, [1, $subsection];
94             #$template = $section;
95 7         17 $template = $subsection->[2];
96             } else { # top-level section start
97 8         25 $what =~ s/^\.//;
98 8         36 my ($tag, $parm) = split " ", $what, 2;
99             #my $section = node_add_child ($template, defined $parm ? node_from_string ("$tag: " . '"' . quote_escape($parm) . '"')
100             # : node_from_string ($tag));
101 8         19 my $subsection = ['.', undef, []];
102 8         21 my $section = [$tag, $parm, { '.' => $subsection}];
103 8         15 push @$template, $section;
104 8         16 unshift @stack, [0, $section];
105 8         16 unshift @stack, [1, $subsection];
106             #$template = $section;
107 8         22 $template = $subsection->[2];
108             }
109             } elsif ($what =~ /^!/) { # 2018-09-09 banged directive
110 5         10 $swallow_nl = 1;
111 5         14 $what =~ s/^!//;
112 5         16 my ($tag, $parm) = split " ", $what, 2;
113             #node_add_child ($template, defined $parm ? node_from_string ("$tag: " . '"' . quote_escape($parm) . '"')
114             # : node_from_string ($tag));
115 5 100       21 push @$template, defined $parm ? [$tag, $parm] : [$tag];
116             } else {
117             #node_add_child ($template, node_from_string ("insert_value: $what"));
118 25         92 push @$template, ['?', $what];
119             }
120             } else { # If there's anything after us, we can't emit a carriage return at the end.
121 70         136 my @lines = split /^/, $what;
122 70   100     246 while (scalar @lines and $lines[0] =~ /^ /) {
123 3         7 my $line = shift (@lines);
124             #node_add_child ($template, node_from_string ('lit "' . quote_escape($line) . (scalar(@lines) ? '\n"' : '"')));
125 3 50       11 push @$template, ['lit', $line . (scalar(@lines) ? "\n" : '')];
126 3         10 $swallow_nl = 0; # any output invalidates the swallow-nl flag
127             } # Now we have no leading spaces.
128             # We only care about the last line as a lit. This logic below is kind of weird because it evolved from
129             # different assumptions (which were wrong). May need to rewrite. Maybe already did?
130 70 100       143 if (scalar @lines) {
131             # 2018-09-07 - yeah, it was wrong in the case that we have a newline followed immediately by a value field.
132             #my $last_line = pop(@lines);
133             #node_add_child ($template, tagless_text (join ("", @lines))) if scalar @lines;
134             #node_add_child ($template, node_from_string ('lit "' . quote_escape($last_line) . '"'));
135 67         95 foreach my $line (@lines) { # 2018-09-07 - just output everything as a lit instead of screwing around with tagless text, which has syntactic issues anyway.
136 89 100 100     185 if ($swallow_nl and $line eq "\n") { # 2018-09-08 - swallow linefeed after section directive if there's no additional text on that line.
137             } else {
138             #node_add_child ($template, node_from_string ('lit "' . quote_escape($line) . '"'));
139 68         162 push @$template, ['lit', $line];
140             }
141 89         208 $swallow_nl = 0;
142             }
143             }
144             }
145             }
146 25         85 $stack[0]->[1];
147             }
148              
149             sub _regexp_escape {
150 50     50   71 my $re = shift;
151 50         242 $re =~ s/([.\^*+?()[{\\|*])/\\$1/g;
152 50         109 return $re;
153             }
154              
155             sub _split_template {
156 25     25   51 my ($text, $start, $end) = @_;
157            
158 25         49 $start = _regexp_escape ($start);
159 25         51 $end = _regexp_escape ($end);
160              
161 25         42 my @out = ();
162 25         316 foreach my $bit (split (/($start.+?$end)/, $text)) {
163 133 100       471 if ($bit =~ /$start(.*)$end/) {
164 55         147 push @out, [1, $1];
165             } else {
166 78 100       240 push @out, [0, $bit] unless $bit eq '';
167             }
168             }
169 25         77 return @out;
170             }
171              
172             =head1 EXPRESSING A TEMPLATE
173              
174             A template can be either fully or partly expressed. If it's only partly expressed, the result is a new template with some of the
175             fields replaced with values, and others left intact.
176              
177             If the template uses transducers we don't recognize, it will croak with an appropriate warning.
178              
179             =head2 iter([data getter])
180              
181             Starts a token iterator from the template. A token stream, the way I do it, is an iterator for which each value returned is either
182             a string (plain text) or a token. A token is an arrayref of [type, value, ], so extracting the value is easy.
183              
184             If no data getter is specified, uses the one registered in the template. If none is registered, croaks because you can't express a template on nothing, can you?
185              
186             =cut
187              
188             sub iter {
189 21     21 1 25 my $self = shift;
190            
191 21         22 my $context = undef;
192 21 100       39 if (scalar @_) {
193 5         8 $context = [@_];
194             }
195 21         28 my $data_getter = $self->{data};
196 21 100       42 $data_getter = Data::Org::Template::Getter->new ({}) unless defined $data_getter;
197              
198 21 100       38 if (not defined $self->{transducer}) { # Here, do some error checking once we have a transducer scan working.
199             $self->{transducer} = {
200 14         82 ':' => \&Data::Org::Template::Transducer::text::tt,
201             'nl' => \&Data::Org::Template::Transducer::nl::tt,
202             'lit' => \&Data::Org::Template::Transducer::lit::tt,
203             'if' => \&Data::Org::Template::Transducer::if::tt,
204             'with' => \&Data::Org::Template::Transducer::with::tt,
205             'list' => \&Data::Org::Template::Transducer::list::tt,
206             'else' => 'ignore',
207             'alt' => 'ignore',
208             '?' => \&Data::Org::Template::Transducer::value::tt,
209             '*undefined*' => 'ignore',
210             };
211             }
212 21         46 my $tags = $self->transducers_requested();
213 21         37 foreach my $tag (@$tags) {
214 49 100       252 croak ("Unknown transducer '$tag' used in template") unless $self->{transducer}->{$tag};
215             }
216              
217            
218 20         43 _express_template ($self->{template}, $data_getter, $context, 0, $self->{transducer});
219             }
220              
221             sub _express_template {
222 34     34   55 my ($template, $data_getter, $context, $indent, $transducer) = @_;
223            
224 34 50       51 $indent = 0 unless defined $indent;
225            
226 34         43 my @line_queue = ();
227 34         37 my @token_queue = ();
228 34         34 my $substream;
229 34         36 my $local_indent = 0; # The indent is how many spaces we have to insert after each line break;
230             # the local indent is how many *more* spaces a child would have to indent at the current position.
231 34         35 my $needs_indent = 0;
232 34         66 my @child_queue = (@$template); # Take a copy of the template for processing.
233            
234             return sub {
235 155 50   155   247 if (scalar @line_queue) { # If lines are queued from the last token, return a line
236 0         0 my $this_indent = $indent+$local_indent;
237 0 0       0 if (scalar @line_queue == 1) {
238 0         0 $local_indent = length($line_queue[0]);
239             }
240 0         0 return (' ' x $this_indent) . shift (@line_queue); # 2018-09-14 - I'm not convinced by this logic. I really need to do some additional testing of indentation.
241             }
242            
243             SUBSTREAM:
244 246 100       348 if (defined $substream) {
245 212         255 my $token = $substream->();
246 212 100       320 if (defined $token) {
247 121         148 my $val = $token;
248 121 100       183 if (ref $token) {
249 39 50       101 return $token if $token->[0] eq '"'; # Text to be passed through without examination (already indented properly)
250 0         0 $val = $token->[1];
251             }
252 82 100       193 if ($val eq "\n") { # 2018-09-14
    100          
253             # Tokens that are themselves simply a line end get passed through with their type.
254 7         8 $local_indent = 0;
255 7         13 return $token;
256             } elsif ($val =~ /\n/) {
257             # A token that *has* line breaks, though, can't stay a token because we may have to indent it.
258             # Split into lines and deal with it as plain text.
259 17         31 @line_queue = split /^/, $val;
260 17         45 return shift (@line_queue); # Return first line right now
261             } else {
262             # No line breaks means we just increment our local indent, then emit this token
263 58         69 $local_indent += length($val);
264 58         93 return $token;
265             }
266             }
267 91         271 $substream = undef;
268             # fall through to next child for another substream if this one is exhausted
269             }
270            
271             NEXT_CHILD:
272 125 100       178 if (scalar @child_queue) { # Is there still template to be expressed?
273 91         124 my $child = shift (@child_queue);
274 91         121 my $lookup = $child->[0];
275 91 50       131 $lookup = ':' if not $lookup; # Tagless text (not actually needed with this AST, but still)
276 91         151 my $tt = $transducer->{$lookup};
277 91 50       123 if (not defined $tt) { # Do we keep this?
278 0         0 $tt = $transducer->{'*undefined*'};
279             }
280 91 50       123 if (not defined $tt) {
281 0         0 croak ("undefined transducer $tt encountered in template");
282             }
283 91 50       149 goto NEXT_CHILD unless ref $tt; # Skip any "ignore" tags we've explicitly identified
284 91         165 $substream = $tt->($child, $data_getter, $context, $indent + $local_indent, $transducer);
285 91         310 goto SUBSTREAM;
286             }
287 34         40 return undef;
288             }
289 34         189 }
290              
291             =head2 _compile_token_stream ($stream)
292              
293             This is just a helper function - NOT object-oriented in nature (the initial underscore might help you - and me - remember that).
294              
295             =cut
296              
297             sub _compile_token_stream { # 2018-09-13
298 20     20   36 my $stream = shift;
299            
300 20         27 my $output = '';
301 20         22 my $tok;
302 20         22 while (1) {
303 102         142 $tok = $stream->();
304 102 100       258 return $output unless defined $tok;
305 82 100       121 if (not ref $tok) {
306 43         62 $output .= $tok;
307             } else {
308 39         68 $output .= $tok->[1]; # A non-string token is an arrayref with a type, the string content, and arbitrary additional data
309             }
310             }
311             }
312              
313             =head2 text()
314              
315             Compiles the iterator into an output string for you, using C<_compile_token_stream>.
316              
317             =cut
318              
319             sub text {
320 21     21 1 63 my $self = shift;
321 21         44 _compile_token_stream ($self->iter (@_));
322             }
323              
324             # Token stream manipulators.
325             sub quote_substream {
326 5     5 0 7 my $stream = shift;
327             sub {
328 15     15   18 my $v = $stream->();
329 15 100       28 return $v if not defined $v;
330 10 50       14 return $v if ref $v;
331 10         18 return ['"', $v];
332             }
333 5         27 }
334             sub undef_stream {
335 0     0 0 0 sub { return undef };
  0     0   0  
336             }
337              
338             =head1 REGISTERING A DATA GETTER
339              
340             A template can be expressed by providing a data getter (a map from name to value) at expression time, but you can also register a default data getter against
341             the template when you define it, for convenience.
342              
343             =cut
344              
345             =head2 data_getter ([data getter])
346              
347             Sets or gets the current data getter for the template. If you pass one or more data getters (which can be a data getter object, a hashref, or an arrayref of other
348             data getters) then each will be checked in sequence.
349              
350             =cut
351              
352             sub data_getter {
353 14     14 1 131 my $self = shift;
354 14 50       33 return $self->{data} unless @_;
355 14 100       41 if (scalar @_ eq 1) {
356 13         40 $self->{data} = Data::Org::Template::Getter->new ($_[0]);
357             } else {
358 1         3 $self->{data} = Data::Org::Template::Getter->new ([@_]);
359             }
360 14         40 $self->{data};
361             }
362              
363             #=head2 check_data_requested ()
364             # Checks the data requested against the current data getter, if any. Returns a list of values I available in the getter.
365             #=cut
366              
367              
368             # --------------------------------------------------------------
369             # Working with custom transducers, only sketched out
370             # --------------------------------------------------------------
371             #=head2 data_requested () -- this is yet to come
372             #
373             #=cut
374             #
375             #=head2 transducers_requested ()
376             #
377             #This provides a list of the transducers used in the template. It's really only of use if you're using custom transducers.
378             #
379             #=cut
380              
381             # This will really only come in handy once I have a need for custom transducers; it's left over from an earlier prototype where I was playing with exactly that.
382             sub transducers_requested {
383 25     25 0 36 my $self = shift;
384 25         35 my $bag = {};
385 25         57 _list_transducers ($self->{template}, $bag);
386 25         110 my @list = sort {$a cmp $b} keys %$bag;
  41         97  
387 25         81 \@list;
388             }
389             sub _list_transducers {
390 51     51   67 my ($template, $bag) = @_;
391 51         71 foreach my $item (@$template) {
392 116         166 $bag->{$item->[0]} = 1;
393 116 100       192 if (scalar @$item > 2) {
394 14         15 while (my ($k, $v) = each (%{$item->[2]})) {
  40         107  
395 26         53 _list_transducers ($v->[2], $bag);
396             }
397             }
398             }
399             }
400              
401             #=head2 register_transducer ()
402             #
403             #=cut
404              
405              
406              
407              
408             # -----------------------------------------------------------------------------------------------------------
409             # Default getter: Data::Org::Template::Getter
410             # -----------------------------------------------------------------------------------------------------------
411              
412             package Data::Org::Template::Getter;
413 5     5   41 use Scalar::Util qw(blessed);
  5         10  
  5         259  
414 5     5   41 use Data::Dumper;
  5         8  
  5         9033  
415              
416             sub new {
417 19     19   26 my $class = shift;
418 19 50       54 if (scalar @_ eq 1) { # If there's a single input and that input is already capable of acting like a getter, don't instantiate anything, just use it.
419 19         29 my ($candidate) = @_;
420 19 0 33     67 if (blessed($candidate) && $candidate->can('get') && $candidate->can('get_iterated')) {
      33        
421 0         0 return $candidate;
422             }
423             }
424            
425 19         37 my $self = bless {}, $class;
426 19         46 $self->{source} = [@_];
427 19         70 $self->{formatter} = Data::Org::Template::Formatter->new();
428 19         39 $self->{format} = $self->{formatter}->formatter;
429 19         81 return $self;
430             }
431             sub new_raw {
432 0     0   0 my $class = shift;
433 0         0 my $self = bless {}, $class;
434 0         0 $self->{source} = [@_];
435 0         0 $self->{formatter} = undef;
436 0     0   0 $self->{format} = sub { return $_[0]; };
  0         0  
437 0         0 return $self;
438             }
439              
440             sub formatter {
441 0     0   0 my $self = shift;
442 0         0 my $formatter = shift;
443 0 0       0 if (defined $formatter) {
444 0         0 $self->{formatter} = $formatter;
445 0         0 $self->{format} = $self->{formatter}->formatter;
446             }
447 0         0 $self->{formatter};
448             }
449              
450             sub get {
451 47     47   1010 my $self = shift;
452 47         61 my $what = shift;
453 47 50       75 return unless defined $what; # Should this croak?
454 47   66     90 my $context = shift || $self->{source}; # A list of data packets in order of search; the first may be a scalar when we're in a list context.
455            
456 47         53 my @formatters;
457 47         87 ($what, @formatters) = $self->{format}->($what);
458            
459 47         100 my $value = $self->_get($what, $context);
460 47   100     158 while (defined $value and ref ($value) eq 'CODE') {
461 2         5 $value = $value->();
462             }
463            
464 47         69 foreach my $f (@formatters) {
465 9         15 $value = $f->($value);
466             }
467 47         108 return $value;
468             }
469              
470             sub _get {
471 53     53   67 my $self = shift;
472 53         59 my $what = shift;
473 53         58 my $context = shift;
474            
475 53 100       99 return $context->[0] if $what eq '.'; # Special case for a list context.
476            
477 45         59 foreach my $source (@$context) {
478 54 100       112 $source = $self->{source} if $source eq '*';
479 54 100       100 next unless ref $source; # Just in case we're in a list and the first "source" is a scalar value.
480              
481 49         50 my $maybe;
482 49 50       90 if (blessed($source)) {
483 0         0 $maybe = $source->get ($what);
484 0 0       0 return $maybe if defined $maybe;
485 0         0 next;
486             }
487            
488 49 100       81 if (ref($source) eq 'ARRAY') {
489 6         14 $maybe = $self->_get ($what, $source);
490 6 100       12 return $maybe if defined $maybe;
491 1         2 next; # Here's a subtle error - leave this out. This surprised me.
492             }
493              
494             # Must be a hash, then.
495 43 100       112 return $source->{$what} if defined $source->{$what};
496             }
497 4         7 return undef;
498             }
499              
500             sub get_iterated {
501 5     5   7 my $self = shift;
502 5         7 my $what = shift;
503 5   33     17 my $context = shift || $self->{source};
504 5         8 my $src = $self->get ($what, $context);
505            
506 5 100       9 return if not defined $src;
507            
508             # If this is a scalar, "iterate" over that single value.
509 4         7 my $r = ref($src);
510 4 50       8 if (not $r) { # -> new subcontext is an iterator that will return a single subframe, consisting of a hash frame and the existing context as a backup
511 0         0 my $done = 0;
512             return sub {
513 0 0   0   0 return undef if $done;
514 0         0 $done = 1;
515 0         0 return [{'.' => $src}, {_total => 1, _count => 0}, @$context];
516             }
517 0         0 }
518             # If a hash, iterate over the hash as a single child frame.
519 4 50       8 if ($r eq 'HASH') { # -> new subcontext is an iterator that will return a single subframe
520 0 0       0 return unless scalar keys (%$src); # An empty hash is equivalent to no data found
521 0         0 my $done = 0;
522             return sub {
523 0 0   0   0 return undef if $done;
524 0         0 $done = 1;
525 0         0 return [$src, {_total => 1, _count => 0}, @$context];
526             }
527 0         0 }
528             # If a list, it's obvious.
529 4 100       17 if ($r eq 'ARRAY') {
530 2 50       5 return unless scalar $src; # An empty list is equivalent to no data found
531 2         4 my @queue = @$src;
532 2         3 my $total = scalar @queue;
533 2         4 my $count = 0;
534 2         6 my $query_info = {
535             _total => $total,
536             _count => $count,
537             _remaining => $total
538             };
539            
540             return sub {
541 8 100   8   18 return undef unless scalar @queue;
542 4         5 $query_info->{_count} = $count;
543 4         8 $query_info->{_remaining} = $total - $count - 1;
544 4         5 $count += 1; # For next time
545 4         6 my $next = shift @queue;
546 4         7 return [$next, $query_info, @$context];
547             }
548 2         8 }
549 2 50 33     18 if (blessed($src) && $src->can('iter_hash')) { # We have a record stream!
550 2         4 my $count = 0;
551 2         3 my $query_info = {
552             _count => $count,
553             };
554 2         8 my $iter = $src->iter_hash(); # Might think of ways to pass parameters at some point.
555 2         102 my $next = $iter->();
556 2 50       46 return unless $next;
557            
558             return sub {
559 8 100   8   16 return unless $next;
560 4         5 $query_info->{_count} = $count;
561 4         6 $count += 1; # For next time
562 4         5 my $this = $next;
563 4         8 $next = $iter->();
564 4         39 return [$this, $query_info, @$context];
565             }
566 2         12 }
567             }
568              
569             # -----------------------------------------------------------------------------------------------------------
570             # Formatting framework
571             # -----------------------------------------------------------------------------------------------------------
572              
573             package Data::Org::Template::Formatter;
574              
575             sub new {
576 19     19   26 my $class = shift;
577 19         28 my $self = bless {}, $class;
578             $self->{lookup} = {
579 9     9   29 html => sub { \&html_encode },
580 19         81 };
581 19         39 $self;
582             }
583              
584             sub register {
585 0     0   0 my $self = shift;
586 0         0 my $name = shift;
587 0 0       0 return unless defined $name;
588 0         0 my $formatter = shift;
589 0 0       0 if (defined $formatter) {
590 0         0 $self->{lookup}->{$name} = $formatter;
591             }
592 0         0 $self->{lookup}->{$name};
593             }
594              
595             sub formatter {
596 19     19   31 my $self = shift;
597             sub {
598 47     47   94 $self->parse(shift);
599             }
600 19         46 }
601              
602             sub parse {
603 47     47   59 my $self = shift;
604 47         108 my $spec = shift;
605            
606 47         62 my @coderefs = ();
607 47         130 while ($spec =~ /(.*)\| *([[:alnum:]][[:alnum:] \-_.*\/\\]*)$/) {
608 9         29 my ($new_spec, $format) = ($1, $2);
609 9         16 $spec = $new_spec;
610 9         34 $spec =~ s/ *$//; # Drop trailing spaces, if any
611 9         22 push @coderefs, $self->make_formatter ($format);
612             }
613 47         132 return ($spec, @coderefs);
614             }
615              
616             sub make_formatter {
617 9     9   12 my $self = shift;
618 9         10 my $format = shift;
619 9         14 my $parm = '';
620 9 50       28 if ($format =~ /^([[:alnum:]]+)(.*)$/) {
621 9         22 ($format, $parm) = ($1, $2);
622             }
623 9         16 my $formatter = $self->{lookup}->{$format};
624 9 50   0   16 return sub { $_[0] } unless defined $formatter; # Error handling might be nice here
  0         0  
625 9         16 return $formatter->($parm);
626             }
627              
628             sub html_encode {
629 9     9   13 my $str = shift;
630 9         14 $str =~ s/&/&/g;
631 9         16 $str =~ s/
632 9         16 $str =~ s/>/>/g;
633 9         18 $str;
634             }
635              
636             # -----------------------------------------------------------------------------------------------------------
637             # Standard transducers: text, nl, lit, value, if, with, and list.
638             # -----------------------------------------------------------------------------------------------------------
639             package Data::Org::Template::Transducer::text;
640              
641 0     0   0 sub values { () }
642             sub tt {
643 0     0   0 my $source = shift;
644 0         0 my $done = 0;
645             sub {
646 0 0   0   0 return undef if $done;
647 0         0 $done = 1;
648 0         0 return $source->[1] . "\n";
649             }
650 0         0 }
651              
652              
653             package Data::Org::Template::Transducer::nl;
654              
655 0     0   0 sub values { () }
656             sub tt {
657 1     1   2 my $done = 0;
658             sub {
659 2 100   2   5 return undef if $done;
660 1         2 $done = 1;
661 1         2 return "\n";
662             }
663 1         4 }
664              
665             package Data::Org::Template::Transducer::lit;
666              
667 0     0   0 sub values { () }
668             sub tt {
669 56     56   62 my $source = shift;
670 56         73 my $done = 0;
671             sub {
672 112 100   112   200 return undef if $done;
673 56         62 $done = 1;
674 56         87 return $source->[1];
675             }
676 56         128 }
677              
678             package Data::Org::Template::Transducer::value;
679              
680 0     0   0 sub values { ($_[0]->[1]) }
681             sub tt {
682 25     25   45 my ($source, $data_getter, $context, $indent, $transducer) = @_;
683 25         49 my $value = $data_getter->get($source->[1], $context);
684 25         30 my $done = 0;
685             sub {
686 50 100   50   78 return undef if $done;
687 25         30 $done = 1;
688 25         38 return $value;
689             }
690 25         66 }
691              
692              
693             package Data::Org::Template::Transducer::if;
694              
695 0     0   0 sub values { () }
696             sub tt {
697 2     2   13 my ($source, $data_getter, $context, $indent, $transducer) = @_;
698              
699 2 100       10 if ($data_getter->get($source->[1], $context)) {
700 1         13 return Data::Org::Template::quote_substream (Data::Org::Template::_express_template ($source->[2]->{'.'}->[2], $data_getter, $context, $indent, $transducer));
701             } else {
702 1 50       4 if (exists $source->[2]->{'else'}) {
703 1         4 return Data::Org::Template::quote_substream (Data::Org::Template::_express_template ($source->[2]->{'else'}->[2], $data_getter, $context, $indent, $transducer));
704             } else {
705 0         0 return Data::Org::Template::undef_stream();
706             }
707             }
708             }
709              
710             package Data::Org::Template::Transducer::with;
711              
712 0     0   0 sub values { () }
713             sub tt {
714 2     2   4 my ($source, $data_getter, $context, $indent, $transducer) = @_;
715 2         4 my $ctx = $data_getter->get($source->[1], $context);
716 2 100       5 if (defined $ctx) {
717 1         4 return Data::Org::Template::quote_substream (Data::Org::Template::_express_template ($source->[2]->{'.'}->[2], $data_getter, [$ctx, '*'], $indent, $transducer));
718             } else {
719 1 50       4 if (exists $source->[2]->{'else'}) {
720 1         3 return Data::Org::Template::quote_substream (Data::Org::Template::_express_template ($source->[2]->{'else'}->[2], $data_getter, $context, $indent, $transducer));
721             } else {
722 0         0 return Data::Org::Template::undef_stream();
723             }
724             }
725             }
726              
727              
728             package Data::Org::Template::Transducer::list;
729 5     5   38 use Data::Dumper;
  5         9  
  5         1746  
730              
731 0     0   0 sub values { () }
732             sub tt {
733 5     5   9 my ($source, $data_getter, $context, $indent, $transducer) = @_;
734 5         12 my $iter = $data_getter->get_iterated ($source->[1], $context);
735 5         21 my $alt = $source->[2]->{alt};
736            
737 5 100       11 if (defined $iter) {
738 4         6 my $subctx;
739 4         6 my $next_subctx = $iter->();
740 4         5 my $state = 0; # 0 = expressing row, 1 = expressing alt (if any)
741 4         4 my $curstream;
742 4         18 my $empty = 0;
743             sub {
744 33 50   33   48 return undef if $empty;
745             DO_STREAM:
746 42 100       53 if ($curstream) {
747 38         48 my $tok = $curstream->();
748 38 100       70 if (defined $tok) {
749 29 50       46 return $tok if ref $tok;
750 29         51 return ['"', $tok];
751             }
752 9         13 $state = not $state; # We exhausted this stream, so go to the next - alt if we just did a row, row if we just did an alt.
753             }
754             # No stream active - let's start one.
755             NEXT_STATE:
756 20 100       35 if ($state) { # We just did a row, so if there's an alt we should do it.
757 8 100 100     20 if ($next_subctx and $alt) {
758 1         2 $curstream = Data::Org::Template::_express_template ($alt->[2], $data_getter, $subctx, $indent, $transducer);
759 1         4 goto DO_STREAM;
760             } else {
761 7         9 $state = 0;
762 7         23 goto NEXT_STATE;
763             }
764             } else { # Start a new row.
765 12         19 $subctx = $next_subctx;
766 12         16 $next_subctx = $iter->();
767 12 100       20 if (defined $subctx) {
768 8         16 $curstream = Data::Org::Template::_express_template ($source->[2]->{'.'}->[2], $data_getter, $subctx, $indent, $transducer);
769 8         31 goto DO_STREAM;
770             }
771 4         5 $empty = 1;
772 4         5 return undef;
773             }
774             }
775 4         15 } else {
776 1 50       3 if (exists $source->[2]->{'else'}) {
777 1         3 return Data::Org::Template::quote_substream (Data::Org::Template::_express_template ($source->[2]->{'else'}->[2], $data_getter, $context, $indent, $transducer));
778             } else {
779 0           return Data::Org::Template::undef_stream();
780             }
781             }
782             }
783              
784              
785              
786             =head1 AUTHOR
787              
788             Michael Roberts, C<< >>
789              
790             =head1 BUGS
791              
792             Please report any bugs or feature requests to C, or through
793             the web interface at L. I will be notified, and then you'll
794             automatically be notified of progress on your bug as I make changes.
795              
796              
797              
798              
799             =head1 SUPPORT
800              
801             You can find documentation for this module with the perldoc command.
802              
803             perldoc Data::Org::Template
804              
805              
806             You can also look for information at:
807              
808             =over 4
809              
810             =item * RT: CPAN's request tracker (report bugs here)
811              
812             L
813              
814             =item * AnnoCPAN: Annotated CPAN documentation
815              
816             L
817              
818             =item * CPAN Ratings
819              
820             L
821              
822             =item * Search CPAN
823              
824             L
825              
826             =back
827              
828              
829             =head1 ACKNOWLEDGEMENTS
830              
831              
832             =head1 LICENSE AND COPYRIGHT
833              
834             Copyright 2020 Michael Roberts.
835              
836             This program is free software; you can redistribute it and/or modify it
837             under the terms of the the Artistic License (2.0). You may obtain a
838             copy of the full license at:
839              
840             L
841              
842             Any use, modification, and distribution of the Standard or Modified
843             Versions is governed by this Artistic License. By using, modifying or
844             distributing the Package, you accept this license. Do not use, modify,
845             or distribute the Package, if you do not accept this license.
846              
847             If your Modified Version has been derived from a Modified Version made
848             by someone other than you, you are nevertheless required to ensure that
849             your Modified Version complies with the requirements of this license.
850              
851             This license does not grant you the right to use any trademark, service
852             mark, tradename, or logo of the Copyright Holder.
853              
854             This license includes the non-exclusive, worldwide, free-of-charge
855             patent license to make, have made, use, offer to sell, sell, import and
856             otherwise transfer the Package with respect to any patent claims
857             licensable by the Copyright Holder that are necessarily infringed by the
858             Package. If you institute patent litigation (including a cross-claim or
859             counterclaim) against any party alleging that the Package constitutes
860             direct or contributory patent infringement, then this Artistic License
861             to you shall terminate on the date that such litigation is filed.
862              
863             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
864             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
865             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
866             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
867             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
868             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
869             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
870             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
871              
872              
873             =cut
874              
875             1; # End of Data::Org::Template