File Coverage

blib/lib/Data/Org/Template.pm
Criterion Covered Total %
statement 342 404 84.6
branch 127 176 72.1
condition 18 27 66.6
subroutine 46 64 71.8
pod 4 7 57.1
total 537 678 79.2


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