line
stmt
bran
cond
sub
pod
time
code
1
2
#
3
# Setup and initialization is done with objects, but execution
4
# proceedural code using local() variables for state. This
5
# imposes a recusion model on the control flow, but allows
6
# previous states to automatically resume.
7
#
8
9
package HTML::Transmorgify;
10
11
8
8
265496
use strict;
8
21
8
385
12
8
8
41
use warnings;
8
14
8
270
13
14
8
8
43
use List::Util qw(first);
8
17
8
1157
15
8
8
8754
use Image::Size;
8
46837
8
636
16
8
8
80
use Scalar::Util qw(reftype blessed);
8
16
8
964
17
8
8
14302
use File::Slurp;
8
204411
8
759
18
8
8
101
use Digest::MD5 qw(md5_hex);
8
1580
8
520
19
8
8
10534
use Data::Dumper;
8
92888
8
669
20
require Exporter;
21
8
8
6848
use Module::Load;
8
8747
8
52
22
8
8
5932
use HTML::Transmorgify::Symbols;
8
21
8
24142
23
24
our $VERSION = 0.12;
25
26
our @ISA = qw(Exporter);
27
our @EXPORT = qw(dangling);
28
our @EXPORT_OK = qw(
29
dstring
30
run
31
compile
32
dangling
33
continue_compile
34
capture_compile
35
queue_intercept
36
queue_capture
37
allocate_result_type
38
eat_cr
39
rbuf
40
postbuf
41
module_bits
42
boolean
43
bomb
44
%variables
45
%transformations
46
%dispatch
47
%priorities
48
@post_intercept_push
49
$xml_quoting
50
$textref
51
$rbuf
52
$debug
53
$tagset
54
$input_file
55
$input_line
56
$modules
57
$result
58
$query_param
59
$original_file
60
$original_line
61
$invocation_options
62
$process_text_ref
63
);
64
65
our $tagset;
66
our $textref;
67
our $result;
68
our %variables;
69
our $rbuf;
70
our $modules;
71
our $debug = 0;
72
our %compiled; # cache of compiled text -> $rbuf
73
our $intercept_okay = 0;
74
our $input_file;
75
our $input_line;
76
our $original_file;
77
our $original_line;
78
our $xml_quoting = 0;
79
our @result_array;
80
our %dispatch;
81
our %priorities;
82
our %queued_intercepts;
83
our @queued_captures;
84
our @post_intercept_push;
85
our $invocation_options;
86
our $wrap_compile_cb;
87
our $process_text_ref;
88
89
our %result_index = ( text => 0, script => 1 );
90
our %reverse_result_index = reverse %result_index;
91
our $result_key_count = 2;
92
93
our $query_param;
94
95
my %base_tags;
96
97
#### PUBLIC FUNCTIONS
98
99
sub allocate_result_type
100
{
101
4
4
1
8
my ($type) = @_;
102
4
50
12
return $result_index{$type} if defined $result_index{$type};
103
4
10
$result_index{$type} = $result_key_count;
104
4
15
$reverse_result_index{$result_key_count} = $type;
105
4
11
return $result_key_count++;
106
}
107
108
sub rbuf
109
{
110
210
50
210
1
709
die if grep { ref($_) && reftype($_) ne 'CODE' } @_;
210
50
1645
111
210
1268
push (@$rbuf, @_);
112
}
113
114
sub postbuf
115
{
116
2
2
1
5
push (@post_intercept_push, @_);
117
}
118
119
#
120
# True if defined and true or defined and empty
121
# False if 0 or
122
#
123
sub boolean
124
{
125
286
286
1
360
my ($b, $default) = @_;
126
286
100
1886
return $default unless defined $b;
127
11
29
$b = lc($b);
128
11
50
33
return 0 if $b eq 'false';
129
11
50
36
return 0 if $b eq 'no';
130
11
50
30
return 0 if $b eq 'off';
131
11
50
27
return 1 if $b eq '';
132
11
100
65
return 1 if $b;
133
2
11
return 0;
134
}
135
136
#### METHODS
137
138
sub new
139
{
140
7
7
0
1581
my ($pkg, %opts) = @_;
141
7
56
my $self = bless {
142
tagset => new_hash(%base_tags),
143
modules => 1,
144
packages => {},
145
modules => '',
146
options => \%opts,
147
pre_compile_cb => [],
148
}, $pkg;
149
7
28
return $self;
150
}
151
152
my $module_count = 0;
153
my %module_bits;
154
155
sub module_bits
156
{
157
19
19
0
50
my ($pkg) = @_;
158
19
100
80
$pkg = ref($pkg) if ref($pkg);
159
19
100
77
return $module_bits{$pkg} if defined $module_bits{$pkg};
160
#print STDERR "# Allocating module bits for $pkg (at $module_count)\n";
161
15
43
$module_bits{$pkg} = '';
162
15
73
vec($module_bits{$pkg}, $module_count++, 1) = 1;
163
15
62
return $module_bits{$pkg};
164
}
165
166
sub intercept_exclusive
167
{
168
4
4
0
49
my ($self, $tobj, $tag_pkg, $priority, %tags) = @_;
169
4
39
$self->intercept($tobj, $tag_pkg, %tags);
170
4
77
for my $t (keys %tags) {
171
68
50
145
if (! $dispatch{$t}) {
0
172
68
161
$dispatch{$t} = HTML::Transmorgify::Exclusive->new($tag_pkg);
173
} elsif ($dispatch{$t}->exclusive) {
174
0
0
$dispatch{$t} = HTML::Transmorgify::MutuallyExclusive->more($tag_pkg);
175
} else {
176
0
0
die;
177
}
178
}
179
}
180
181
sub intercept_shared
182
{
183
7
7
0
40
my ($self, $tobj, $tag_pkg, $priority, %tags) = @_;
184
7
50
33
53
die if $priorities{$tag_pkg} && $priorities{$tag_pkg} != $priority;
185
7
22
$priorities{$tag_pkg} = $priority;
186
7
63
$self->intercept($tobj, $tag_pkg, %tags);
187
7
26
for my $t (keys %tags) {
188
29
50
74
if (! $dispatch{$t}) {
0
189
29
98
$dispatch{$t} = HTML::Transmorgify::Stack->new($tag_pkg);
190
} elsif ($dispatch{$t}->exclusive) {
191
0
0
die;
192
} else {
193
0
0
$dispatch{$t} = HTML::Transmorgify::Stack->more($tag_pkg);
194
}
195
}
196
}
197
198
sub intercept_pre_compile
199
{
200
4
4
0
9
my ($self, $cb) = @_;
201
4
11
push(@{$self->{pre_compile_cb}}, $cb);
4
20
202
}
203
204
sub queue_capture
205
{
206
8
8
1
13
my ($cb) = @_;
207
8
34
push(@queued_captures, $cb);
208
}
209
210
sub queue_intercept
211
{
212
11
11
1
55
my ($tag_pkg, %new) = @_;
213
37
106
my @k = $tag_pkg
214
11
50
48
? (map { "$tag_pkg $_" } keys %new)
215
: (keys %new);
216
11
92
@queued_intercepts{@k} = values %new;
217
}
218
219
sub intercept
220
{
221
11
11
0
55
my ($self, $tobj, $tag_pkg, %new) = @_;
222
11
20
my %opts;
223
11
100
61
if (ref $_[0]) {
224
8
35
%opts = %{shift(@_)};
8
73
225
}
226
11
21
my $ts;
227
11
50
40
if (ref $tobj) {
0
228
11
77
$tobj->{modules} |= $self->module_bits;
229
11
32
$ts = $tobj->{tagset};
230
} elsif ($intercept_okay) {
231
0
0
$modules |= $self->module_bits;
232
0
0
$ts = $tagset;
233
} else {
234
0
0
die;
235
}
236
97
219
my @k = $tag_pkg
237
11
50
103
? (map { "$tag_pkg $_" } keys %new)
238
: (keys %new);
239
11
40
my %old = map { $_ => $ts->{$_} } @k;
97
257
240
11
97
@$ts{@k} = values %new;
241
11
89
return %old;
242
}
243
244
0
0
0
0
sub add_tags { die "must redefine" }
245
246
sub mixin
247
{
248
7
7
0
51
my ($self, $module) = @_;
249
7
44
load $module;
250
7
623
$module->add_tags($self);
251
}
252
253
sub process
254
{
255
37
37
0
98839
my $self = shift;
256
37
50
260
die unless blessed $self;
257
37
128
local($tagset) = $self->{tagset};
258
37
124
local($modules) = $self->{modules};
259
37
95
local($process_text_ref) = \$_[0];
260
37
92
local($intercept_okay) = 1;
261
37
53
shift;
262
37
79
local($invocation_options) = {};
263
37
50
137
$invocation_options = shift if ref $_[0];
264
37
307
local(%variables) = @_;
265
37
50
265
local($query_param) = $invocation_options->{query_param} || {};
266
37
33
223
local($original_file) = local($input_file) = $invocation_options->{input_file} || (caller())[1];
267
37
33
187
local($original_line) = local($input_line) = $invocation_options->{input_line} || (caller())[2];
268
37
164
local($xml_quoting) = first_key('xml_quoting', 0, $invocation_options, $self->{options});
269
37
159
$_->($self) for @{$self->{pre_compile_cb}};
37
206
270
37
134
my $buf = compile($modules, $process_text_ref);
271
37
117
local(@result_array) = ( '' );
272
#print Dumper([__FILE__, __LINE__, $rbuf]) if $debug;
273
37
98
run($buf);
274
37
50
116
return map { $_ => $result_array[$result_index{$_}] } keys %result_index
0
0
275
if wantarray;
276
37
404
return $result_array[0];
277
}
278
279
280
#### (SEMI)PRIVATE FUNCTIONS
281
282
sub run
283
{
284
306
306
1
388
my $buf = shift;
285
286
306
100
961
return run($buf, \@result_array) unless $_[0];
287
288
180
254
local $result = shift;
289
290
180
336
for my $i (@$buf) {
291
581
100
1630
if (ref $i) {
292
8
8
80
use Data::Dumper;
8
16
8
1871
293
280
50
864
die Dumper($buf) unless reftype($i) eq 'CODE';
294
280
881
$i->();
295
} else {
296
301
50
576
printf STDERR "# Appending %s\n", dstring($i) if $debug;
297
301
1356
$result->[0] .= $i;
298
}
299
}
300
}
301
302
sub first_key
303
{
304
37
37
0
105
my ($key, $default, @hashes) = @_;
305
37
136
for my $h (@hashes) {
306
74
100
285
next unless exists $h->{$key};
307
6
31
return $h->{$key};
308
}
309
31
95
return $default;
310
}
311
312
sub dstring
313
{
314
8
8
45
use Carp qw(confess);
8
17
8
39468
315
0
0
0
0
my ($s, @pos) = @_;
316
0
0
0
return "UNDEF" unless defined $s;
317
0
0
0
confess() if grep { $_ > length($s) } @pos; # XXX
0
0
318
0
0
substr($s, $_, 0) = "*##*" for (reverse sort { $a <=> $b } @pos);
0
0
319
0
0
$s =~ s/\n/\\n/g;
320
0
0
return $s;
321
}
322
323
sub eat_cr
324
{
325
89
89
1
165
my $o = pos($$textref);
326
89
674
$$textref =~ /\G\n/gcs;
327
89
136
my $n = pos($$textref);
328
89
50
412
printf STDERR "# EAT_CR %s\n", dstring($$textref, $o, $n) if $debug;
329
}
330
331
sub compile
332
{
333
73
73
1
565
my $cacheline = shift;
334
73
111
local $textref = shift;
335
336
73
50
201
printf STDERR "# Invoking compile(%s, %s) for %s\n", tobits($cacheline), scalar(%$tagset), dstring($$textref, 0) if $debug;
337
73
103
my $md5;
338
73
50
197
confess() unless defined $$textref;
339
73
407
$md5 = md5_hex($$textref);
340
73
196
my $cached = $compiled{$cacheline}{$md5};
341
73
100
178
if ($cached) {
342
1
50
4
print STDERR "# returning cached result\n" if $debug;
343
1
4
return $cached;
344
}
345
72
130
local($rbuf) = \my @rbuf;
346
72
225
pos($$textref) = 0;
347
my $ccb = sub {
348
72
72
187
continue_compile(undef, undef, undef);
349
72
297
};
350
72
50
165
if ($wrap_compile_cb) {
351
0
0
local($wrap_compile_cb);
352
0
0
$wrap_compile_cb->($ccb);
353
} else {
354
72
136
$ccb->();
355
}
356
72
274
$compiled{$cacheline}{$md5} = \@rbuf;
357
72
50
185
printf STDERR "# Done compile(%s, %s) now at %d\n", tobits($cacheline), scalar(%$tagset), pos($$textref) if $debug;
358
72
627
return $rbuf;
359
}
360
361
sub capture_compile
362
{
363
65
65
1
125
my $onetag = $_[0];
364
65
50
167
die unless $onetag;
365
65
346
local($dispatch{"/$onetag"}) = HTML::Transmorgify::Deferred->new($dispatch{"/$onetag"});
366
65
164
my $buf = [];
367
{
368
65
87
local($rbuf) = $buf;
65
103
369
65
280
continue_compile(@_);
370
}
371
65
100
3368
return $buf unless wantarray;
372
10
36
return ($buf, $dispatch{"/$onetag"});
373
}
374
375
my $no_opts = {};
376
377
sub continue_compile
378
{
379
148
148
1
441
my ($onetag, $starting_attr, $opts, %tags) = @_;
380
148
66
523
$opts ||= $no_opts;
381
106
357
my @ks = $opts->{tag_package}
382
148
100
648
? (map { "$opts->{tag_package} $_" } keys %tags)
383
: (keys %tags);
384
385
148
50
391
print STDERR "# overriding ".join(';', @ks)."\n" if $debug;
386
148
477
local(@$tagset{@ks}) = values %tags;
387
148
571
my $start = pos($$textref);
388
148
50
0
310
printf STDERR "# Invoking continue_compile(%s/%s) at %d for %s from %d\n", $onetag || '?', scalar(%$tagset), $start, dstring($$textref, $start), (caller())[2] if $debug;
389
148
100
284
if ($onetag) {
390
76
369
local($dispatch{"/$onetag"}) = HTML::Transmorgify::CloseTag->new($dispatch{"/$onetag"});
391
76
659
my $finaltag = do_compile();
392
76
50
33
761
bomb("Could not find closing $onetag>", starting_attr => $starting_attr)
393
unless defined($finaltag) && $finaltag eq "/$onetag";
394
} else {
395
72
158
do_compile();
396
}
397
148
50
0
702
printf STDERR "# Done continue_compile(%s/%s) at %d, now at %d\n", $onetag || '?', scalar(%$tagset), $start, pos($$textref) if $debug;
398
}
399
400
sub do_compile
401
{
402
148
148
0
378
my $copied = pos($$textref);
403
148
50
339
print STDERR "# starting compile, pos = $copied\n" if $debug;
404
148
419
while (pos($$textref) < length($$textref)) {
405
328
50
1029
printf STDERR "## pos = %d\n", pos($$textref) if $debug;
406
328
1160
$$textref =~ m{ \G [^<]+ }xgc;
407
328
600
my $before = pos($$textref);
408
328
100
1261
unless ($$textref =~ m{ \G < ( /? [^>\s]+ ) }xgc) {
409
22
37
$$textref =~ m{ \G < }xgc;
410
22
75
next;
411
}
412
306
621
my $tag = $1;
413
306
742
$$textref =~ m{ \G \s+ }xgc;
414
306
100
915
if ($dispatch{$tag}) {
100
415
294
631
my $boring = substr($$textref, $copied, $before-$copied);
416
417
294
100
625
if ($before-$copied) {
418
199
50
403
printf STDERR "# pushing pre-tag stuff %d-%d: %s (%s)\n", $copied, $before, dstring($boring), dstring($$textref, $copied, $before) if $debug;
419
199
454
push(@$rbuf, $boring);
420
}
421
422
294
435
my @atvals;
423
294
1523
while ( $$textref =~ m{
424
\G
425
([\w\.]+)
426
(?:
427
=
428
(?: ([\w\.]*) | '([^']+)' | "([^"]+)" )
429
)?
430
(?=[\s>])
431
}xgc
432
) {
433
299
545
my $name = $1;
434
299
849
1858
my $val = (first { defined $_} ($2, $3, $4));
849
1462
435
299
1184
push(@atvals, $name => $val);
436
299
1404
$$textref =~ m{ \G \s+ }xgc;
437
}
438
294
843
$$textref =~ m{ \G (/?) > }xgc;
439
440
294
531
my $closed = $1;
441
442
294
50
691
printf STDERR "# callback for %s at %d: %s\n", $tag, $copied, dstring($$textref, $copied, pos($$textref)) if $debug;
443
294
1008
my $attr = HTML::Transmorgify::Attributes->new($tag, \@atvals, $closed);
444
294
1207
my $r = $dispatch{$tag}->call($tag, $attr, $closed);
445
294
100
66
947
if ($r && $r == 22) {
446
76
50
174
printf STDERR "# %s indicates - done wih compile() pos is %d\n", $tag, pos($$textref) if $debug;
447
76
465
return $tag;
448
}
449
218
50
414
printf STDERR "# continuing compile at %d: %s\n", pos($$textref), dstring($$textref, $copied, pos($$textref)) if $debug;
450
218
1674
$copied = pos($$textref);
451
} elsif ($dispatch{macro}) {
452
9
100
74
if ($$textref =~ m{ \G (?: [^'">] | '[^'<>]*' | "[^"<>]*" )* > }xgc) {
453
# easy skip
454
6
50
21
printf STDERR "# advancing past tag with no callback & no macros (%s), now at %d: %s\n", $tag, pos($$textref), dstring($$textref, pos($$textref)) if $debug;
455
} else {
456
3
50
8
printf STDERR "# Tag with <> inside quotes found (%s) %s\n", pos($$textref), dstring($$textref, pos($$textref)) if $debug;
457
3
5
my @atvals;
458
3
21
while ( $$textref =~ m{
459
\G
460
([\w\.]+)
461
(?:
462
=
463
(?: ([\w\.]*) | '([^']+)' | "([^"]+)" )
464
)?
465
(?=[\s>])
466
}xgc
467
) {
468
3
7
my $name = $1;
469
3
9
23
my $val = (first { defined $_} ($2, $3, $4));
9
17
470
3
12
push(@atvals, $name => $val);
471
3
14
$$textref =~ m{ \G \s+ }xgc;
472
}
473
3
10
$$textref =~ m{ \G (/?) > }xgc;
474
3
7
my $closed = $1;
475
476
3
50
7
if (grep { /
6
22
477
3
50
8
printf STDERR "# There are calls in the tag, compiling\n" if $debug;
478
3
7
my $boring = substr($$textref, $copied, $before-$copied);
479
480
3
50
10
if ($before-$copied) {
481
0
0
0
printf STDERR "# Pushing pre-tag stuff %d-%d: %s (%s)\n", $copied, $before, $boring, dstring($$textref, $copied, $before) if $debug;
482
0
0
push(@$rbuf, $boring);
483
}
484
3
9
my $attr = HTML::Transmorgify::Attributes->new($tag, \@atvals, $closed);
485
3
3
12
push(@$rbuf, sub { $result->[0] .= "$attr" });
3
51
486
3
6
$copied = pos($$textref);
487
3
50
10
printf STDERR "# Continuing compile at %d\n", pos($$textref) if $debug;
488
} else {
489
0
0
0
printf STDERR "# advancing past tag with no macros (%s), now at %d: %s\n", $tag, pos($$textref), dstring($$textref, pos($$textref)) if $debug;
490
}
491
}
492
} else {
493
# advance to the end of the tag
494
3
27
$$textref =~ m{ \G (?: [^'">] | '[^']*' | "[^"]*" )* > }xgc;
495
3
50
16
printf STDERR "# Advancing past tag with no callback (%s), now at %d: %s\n", $tag, pos($$textref), dstring($$textref, pos($$textref)) if $debug;
496
}
497
230
100
100
2742
if (@$rbuf > 1 && ! ref($rbuf->[-1]) && ! ref($rbuf->[-2])) {
100
498
36
151
$rbuf->[-2] .= pop(@$rbuf);
499
}
500
}
501
72
154
my $boring = substr($$textref, $copied);
502
72
50
163
printf STDERR "# pushing final stuff %d-%d: %s (%s)\n", $copied, length($$textref), $boring, dstring($$textref, $copied) if $debug;
503
72
100
236
push(@$rbuf, $boring) if length($boring);
504
72
277
return;
505
}
506
507
sub bomb
508
{
509
0
0
0
0
my ($message, %context) = @_;
510
0
0
my $c = '';
511
0
0
0
if ($context{attr}) {
512
0
0
$c .= sprintf(" at <%s> from at %s, line %d",
513
$context{starting_attr}->tag,
514
$context{starting_attr}->location,
515
);
516
}
517
0
0
0
if ($context{starting_attr}) {
518
0
0
$c .= sprintf(" from <%s> starting at %s, line %d",
519
$context{starting_attr}->tag,
520
$context{starting_attr}->location,
521
);
522
}
523
0
0
0
my $clev = $context{caller_level} || 0;
524
0
0
die sprintf("Erorr: %s%s at %s:%d\n", $message, $c, (caller($clev))[1], (caller($clev))[2]);
525
}
526
527
sub dangling
528
{
529
0
0
0
0
my ($attr, $closed) = @_;
530
0
0
bomb(sprintf("<%s> found without a preceeding start tag in %s:%d", $attr->tag, $input_file, $input_line));
531
}
532
533
sub tobits
534
{
535
0
0
0
0
join('', unpack("b*", $_[0]));
536
}
537
538
package HTML::Transmorgify::Attributes;
539
540
8
8
100
use strict;
8
19
8
315
541
8
8
43
use warnings;
8
184
8
336
542
8
8
47
use HTML::Transmorgify::Symbols;
8
11
8
1571
543
544
import HTML::Transmorgify qw($tagset $textref $debug run dstring $rbuf $input_file $input_line $xml_quoting module_bits compile rbuf);
545
546
our @rtmp;
547
our %tagset_hash;
548
549
my $module_bits = module_bits('tag expand');
550
551
#
552
# $atvals are pairs representing the attributes.
553
# a value of undef indicates that the attribute
554
# didn't have a value at all. For example:
555
# would be [ 'selected' => undef ]
556
#
557
#
558
# Boolean values like "selected", "checked", etc
559
# are represented by having an undef value internally.
560
# If you request their value though, they return their
561
# own name. get('selected') will return 'selected'.
562
#
563
# This mans you should not set values to the return value from
564
# get!
565
#
566
567
sub new
568
{
569
300
300
0
1131
my ($pkg, $tag, $atvals, $closed) = @_;
570
571
8
8
6718
use integer;
8
74
8
49
572
573
300
468
my $dbug = $HTML::Transmorgify::debug;
574
575
300
437
my $numattr = scalar(@$atvals)/2;
576
577
300
445
my @callbacks;
578
579
my $lastpos;
580
{
581
300
321
my $i = 1;
300
347
582
300
100
1384
while ($i <= @$atvals && ! defined($atvals->[$i])) {
583
99
754
$i += 2;
584
}
585
300
579
$lastpos = ($i - 3) / 2;
586
}
587
588
300
373
my %vals;
589
300
741
for (my $j = 0; $j < @$atvals; $j+=2) {
590
311
1309
$vals{lc($atvals->[$j])} = $atvals->[$j+1];
591
}
592
593
300
919
my %needs_cooking = map { $_ => scalar($vals{$_} =~ /<\w+\s/) } grep { defined($vals{$_}) } keys %vals;
196
721
311
4527
594
595
300
424
my %cooked;
596
my @hidden;
597
0
0
my %hidden;
598
599
my $f_raw = sub {
600
278
278
405
my ($at, $pos) = @_;
601
278
100
100
729
if (defined($pos) && $pos <= $lastpos) {
602
2
8
return $atvals->[$pos*2];
603
}
604
276
100
4050
if (exists $vals{$at}) {
605
46
50
343
return $vals{$at} if defined $vals{$at};
606
0
0
return $at; # boolean
607
}
608
230
955
return;
609
300
1486
};
610
611
my $f_get = sub {
612
660
660
951
my ($at, $pos) = @_;
613
660
100
100
1639
if (defined($pos) && $pos <= $lastpos) {
614
118
514
return $atvals->[$pos*2];
615
}
616
542
100
1910
return unless exists $vals{$at};
617
329
100
611
unless ($needs_cooking{$at}) {
618
310
100
1252
return $vals{$at} if defined $vals{$at};
619
3
9
return $at; # boolean
620
}
621
622
19
50
33
printf "# Cooking %s for get attr=%s\n", HTML::Transmorgify::dstring($vals{$at}), $at if $dbug;
623
624
19
100
39
unless ($cooked{$at}) {
625
9
20
$cooked{$at} = compile($HTML::Transmorgify::modules, \$vals{$at});
626
}
627
628
19
49
local(@rtmp) = ( '' );
629
19
50
run($cooked{$at}, \@rtmp);
630
631
8
8
3913
use Data::Dumper;
8
25
8
14450
632
19
50
42
print STDERR Dumper([ __FILE__, __LINE__, $cooked{$at}]) if $dbug;
633
634
19
50
37
printf "# get(%s) = '%s'\n", $at, dstring($rtmp[0]) if $dbug;
635
636
19
50
37
die if @rtmp > 1;
637
638
19
82
return $rtmp[0];
639
300
1520
};
640
641
my $f_static = sub {
642
120
120
197
my ($at, $pos) = @_;
643
120
100
100
513
if (defined($pos) && $pos <= $lastpos) {
644
44
197
return $atvals->[$pos*2];
645
}
646
76
100
379
return unless exists $vals{$at};
647
2
50
9
return $at unless defined $vals{$at}; # boolean
648
2
50
17
return $vals{$at} unless $needs_cooking{$at};
649
0
0
return;
650
300
1218
};
651
652
# stringify
653
my $f_stringify = sub {
654
108
108
142
my ($self) = @_;
655
108
113
my $rv;
656
108
190
for my $cb (@callbacks) {
657
0
0
my $res = $cb->($self);
658
0
0
0
if (defined $res) {
659
0
0
0
die "multiple callbacks providing results for $tag" if defined $rv;
660
0
0
$rv = $res;
661
}
662
}
663
108
50
205
return $rv if defined $rv;
664
108
217
my $text = "<$tag";
665
108
248
for(my $i = 0; $i <= $lastpos; $i++) {
666
0
0
0
next if defined($hidden[$i]);
667
0
0
$text .= " " . _safe($atvals->[$i*2]);
668
}
669
108
247
for(my $j = $lastpos+1; $j < $numattr; $j++) {
670
222
341
my $a = $atvals->[$j*2];
671
222
100
457
next if defined($hidden{$a});
672
200
100
384
if (defined($atvals->[$j*2+1])) {
673
182
438
$text .= " $a=" . _safe($f_get->($atvals->[$j*2]), 1);
674
} else {
675
18
49
$text .= " $a";
676
}
677
}
678
# use Scalar::Util qw(refaddr);
679
# $text .= ' refaddr="' . refaddr($atvals) . '"' if $dbug;
680
681
108
132
$text .= ">";
682
108
50
201
printf STDERR "# tag text = '%s'\n", dstring($text) if $dbug;
683
108
380
return $text;
684
300
2927
};
685
686
my $f_hide_position = sub {
687
0
0
0
@hidden[@_] = @_;
688
300
1158
};
689
690
my $f_set = sub {
691
34
34
128
while (my ($k, $v) = splice(@_, 0, 2)) {
692
34
100
66
unless (exists $vals{$k}) {
693
20
43
push(@$atvals, $k, $v);
694
20
26
$numattr++;
695
}
696
34
50
62
print STDERR "# Setting $tag attribute $k = '$v'\n" if $dbug;
697
34
164
$vals{$k} = $v;
698
}
699
300
1283
};
700
701
300
408
my $invoking_textref = $textref;
702
300
462
my $invoking_pos = pos($$textref);
703
300
774
my $invoking_file = $input_file;
704
300
363
my $invoking_line = $input_line;
705
300
365
my $lines_in;
706
707
my $f_location = sub {
708
0
0
0
0
$lines_in ||= (substr($$invoking_textref, 0, $invoking_pos) =~ tr/\n/\n/);
709
0
0
($invoking_file, $invoking_line + $lines_in)
710
300
990
};
711
712
300
634
my $eval_at_runtime = grep { $_ } values %needs_cooking;
196
382
713
714
return bless [
715
$f_raw, # 0
716
$f_get, # 1
717
$f_stringify, # 2
718
$closed, # 3
719
$f_static, # 4
720
\%vals, # 5
721
30
30
110
sub { @hidden[@_] = @_ }, # 6
722
$lastpos, # 7
723
101
101
488
sub { @hidden{@_} = @_ }, # 8
724
300
4119
$f_set, # 9
725
$tag, # 10
726
$f_location, # 11
727
$eval_at_runtime, # 12
728
\%needs_cooking, # 13
729
\@callbacks, # 14
730
], $pkg;
731
}
732
733
# XXX add runtime pre-stringify callback funcs
734
735
278
278
1
343
sub raw { my $self = shift; $self->[0]->(@_) };
278
1013
736
478
478
1
565
sub get { my $self = shift; $self->[1]->(@_) };
478
1228
737
108
108
1
1036
sub as_string { my $self = shift; $self->[2]->($self, @_) };
108
242
738
0
0
1
0
sub closed { my $self = shift; $self->[3] };
0
0
739
120
120
1
172
sub static { my $self = shift; $self->[4]->(@_) };
120
410
740
166
166
1
222
sub vals { my $self = shift; $self->[5] };
166
499
741
30
30
1
48
sub hide_position { my $self = shift; $self->[6]->(@_) };
30
90
742
37
100
37
1
286
sub last_position { my $self = shift; return @_ ? ($_[0] <= $self->[7]) : $self->[7] };
37
348
743
101
101
1
99
sub hide { my $self = shift; $self->[8]->(@_) };
101
160
744
34
34
1
40
sub set { my $self = shift; $self->[9]->(@_) };
34
77
745
98
98
1
141
sub tag { my $self = shift; $self->[10] };
98
588
746
0
0
1
0
sub location { my $self = shift; $self->[11]->(@_) };
0
0
747
0
0
1
0
sub needs_cooking { my $self = shift; $self->[13] };
0
0
748
0
0
1
0
sub output_callback { my $self = shift; push(@{$self->[14]}, @_); $self->[12] = 2 };
0
0
0
0
0
0
749
750
sub eval_at_runtime
751
{
752
43
43
1
40
my $self = shift;
753
43
52
my $r = $self->[12];
754
43
50
289
$self->[12] = $_[0] if @_;
755
43
84
return $r;
756
}
757
758
sub boolean
759
{
760
223
223
1
476
my ($self, $name, $pos, $default, %opts) = @_;
761
223
100
829
my $b = $opts{raw}
762
? $self->raw($name, $pos, %opts)
763
: $self->get($name, $pos, %opts);
764
223
100
623
$default = 0 unless defined $default;
765
223
594
return HTML::Transmorgify::boolean($b, $default);
766
}
767
768
sub static_action
769
{
770
0
0
1
0
my ($attr, $tag, $sub) = @_;
771
0
0
0
my @tags = ref($tag) ? @$tag : $tag;
772
0
0
for my $t (@tags) {
773
0
0
0
0
unless ($attr->static($t) || ! defined $attr->raw($t)) {
774
0
0
rbuf($sub);
775
0
0
return;
776
}
777
}
778
0
0
$sub->(1);
779
}
780
781
sub add_to_result
782
{
783
95
95
1
107
my $self = shift;
784
95
100
234
if ($self->[12]) {
785
43
43
173
rbuf(sub { $HTML::Transmorgify::result->[0] .= $self->as_string });
43
77
786
} else {
787
52
123
push(@$HTML::Transmorgify::rbuf, $self->as_string);
788
}
789
}
790
791
use overload
792
8
84
'""' => \&as_string,
793
8
8
54
;
8
18
794
795
sub _safe
796
{
797
182
182
236
my ($val, $is_val) = @_;
798
799
182
50
100
1331
if (! defined($val)) {
100
50
800
0
0
return '""';
801
} elsif ($val !~ /[^\w.]/ && ! ($is_val && $xml_quoting)) {
802
6
35
return $val;
803
} elsif ($val =~ /'/) {
804
0
0
return qq{'$val'};
805
} else {
806
176
815
return qq{"$val"};
807
}
808
809
}
810
811
812
package HTML::Transmorgify::MutuallyExclusive;
813
814
8
8
1925
use strict;
8
17
8
271
815
8
8
36
use warnings;
8
12
8
10547
816
import HTML::Transmorgify qw($debug);
817
818
sub call
819
{
820
0
0
0
my $self = shift;
821
0
0
my $tag = shift;
822
0
0
my $attr = shift;
823
0
0
my $i = 0;
824
0
0
0
print STDERR "Callback MUTUALLY EXCLUSIVE for $tag\n" if $debug;
825
0
0
while ($i < @$self) {
826
0
0
my $cb = $HTML::Transmorgify::tagset->{"$self->[$i] $tag"};
827
0
0
$i++;
828
0
0
0
next unless $cb;
829
0
0
my $rv = $cb->($attr, @_);
830
0
0
while ($i < @$self) {
831
0
0
my $cb2 = $HTML::Transmorgify::tagset->{"$self->[$i] $tag"};
832
0
0
$i++;
833
0
0
0
die if $cb2;
834
}
835
0
0
0
if ($rv) {
836
0
0
0
printf STDERR "# Will interpolate $tag later, current value is $attr\n" if $debug;
837
0
0
$attr->add_to_result;
838
}
839
0
0
return 0;
840
}
841
0
0
$attr->add_to_result;
842
0
0
return 0;
843
}
844
845
0
0
0
sub exclusive { 1 };
846
847
sub new
848
{
849
0
0
0
my ($pkg, @tags) = @_;
850
0
0
return bless \@tags, $pkg;
851
}
852
853
sub more
854
{
855
0
0
0
my ($self, @tags) = @_;
856
0
0
push(@$self, @tags);
857
}
858
859
package HTML::Transmorgify::Exclusive;
860
861
8
8
57
use strict;
8
14
8
275
862
8
8
39
use warnings;
8
15
8
3276
863
import HTML::Transmorgify qw($debug);
864
865
sub new
866
{
867
68
68
97
my ($pkg, $tag_pkg) = @_;
868
68
274
return bless \$tag_pkg, $pkg;
869
}
870
871
sub call
872
{
873
143
143
200
my $self = shift;
874
143
305
my $tag = shift;
875
143
184
my $attr = shift;
876
143
50
385
print STDERR "# Callback EXCLUSIVE for $tag\n" if $debug;
877
143
420
my $cb = $HTML::Transmorgify::tagset->{"$$self $tag"};
878
143
50
372
unless ($cb) {
879
0
0
0
print STDERR "# No <$$self $tag> callback\n" if $debug;
880
0
0
push(@$rbuf, "$attr");
881
0
0
return 0;
882
}
883
143
1046
my $rv = $cb->($attr, @_);
884
143
50
438
if ($rv) {
50
885
0
0
$attr->add_to_result;
886
0
0
0
printf STDERR "# Including exclusive attribute for $attr\n" if $debug;
887
} elsif ($debug) {
888
0
0
0
printf STDERR "# NOT Including exclusive attribute for $attr\n" if $debug;
889
}
890
143
305
return 0;
891
}
892
893
0
0
0
sub exclusive { 1 };
894
895
sub more
896
{
897
0
0
0
my ($self, $tag) = @_;
898
0
0
return HTML::Transmorgify::MutuallyExclusive->new($$self, $tag);
899
}
900
901
package HTML::Transmorgify::Stack;
902
903
8
8
43
use strict;
8
16
8
238
904
8
8
54
use warnings;
8
14
8
5920
905
import HTML::Transmorgify qw(%priorities $debug continue_compile capture_compile rbuf);
906
907
#
908
# Tags for shared callbacks are always included in the output stream
909
#
910
911
sub call
912
{
913
95
95
119
my $self = shift;
914
95
118
my $tag = shift;
915
95
94
my $attr = shift;
916
95
98
my $i = 0;
917
95
135
local(%HTML::Transmorgify::queued_intercepts);
918
95
123
local(@HTML::Transmorgify::queued_captures);
919
95
101
local(@HTML::Transmorgify::post_intercept_push);
920
921
95
50
840
print STDERR "Callback STACK for $tag\n" if $debug;
922
95
94
my @rt_callback;
923
95
203
while ($i < @$self) {
924
95
316
my $cb = $HTML::Transmorgify::tagset->{"$self->[$i] $tag"};
925
95
97
$i++;
926
95
100
178
unless ($cb) {
927
12
50
25
print STDERR "NO callback for ".$self->[$i-1]." <$tag>\n" if $debug;
928
12
31
next;
929
}
930
83
50
139
print STDERR "Calling ".$self->[$i-1]." <$tag>...\n" if $debug;
931
83
268
my $r = $cb->($attr, @_);
932
83
50
33
320
if (ref($r) && ref($r) eq 'CODE') {
933
0
0
push(@rt_callback, $r);
934
}
935
}
936
95
50
389
if (@rt_callback) {
937
0
0
0
rbuf (sub { $_->($attr) for @rt_callback });
0
0
938
0
0
$attr->eval_at_runtime(1);
939
}
940
95
218
$attr->add_to_result;
941
95
50
239
printf STDERR "# Including attribute for $attr\n" if $debug;
942
95
100
403
if (@HTML::Transmorgify::queued_captures) {
100
943
8
50
18
print STDERR "# Capturing to /$tag with queued intercepts in play: ".join(';', keys %HTML::Transmorgify::queued_intercepts)."\n" if $debug;
944
8
37
my ($b, $deferred) = capture_compile($tag, $attr, undef, %HTML::Transmorgify::queued_intercepts);
945
8
19
for my $ccb (@HTML::Transmorgify::queued_captures) {
946
8
34
$ccb->($b);
947
}
948
8
18
push(@$HTML::Transmorgify::rbuf, @$b);
949
8
33
$deferred->doit();
950
} elsif (keys %HTML::Transmorgify::queued_intercepts) {
951
11
50
154
print STDERR "# Processing to /$tag with queued intercepts in play: ".join(';', keys %HTML::Transmorgify::queued_intercepts)."\n" if $debug;
952
11
59
continue_compile($tag, $attr, undef, %HTML::Transmorgify::queued_intercepts);
953
}
954
95
134
push(@$HTML::Transmorgify::rbuf, @HTML::Transmorgify::post_intercept_push);
955
956
95
427
return 0;
957
}
958
959
0
0
0
sub exclusive { 0 };
960
961
sub new
962
{
963
29
29
57
my ($pkg, @tag_pkgs) = @_;
964
29
62
my $self = bless \@tag_pkgs, $pkg;
965
29
81
$self->more;
966
29
101
return $self;
967
}
968
969
sub more
970
{
971
29
29
52
my ($self, @tag_pkgs) = @_;
972
29
109
@$self = sort { $priorities{$a} <=> $priorities{$b} } @$self, @tag_pkgs;
0
0
973
}
974
975
package HTML::Transmorgify::CloseTag;
976
977
8
8
52
use strict;
8
15
8
449
978
8
8
44
use warnings;
8
18
8
1633
979
import HTML::Transmorgify qw($debug);
980
981
sub new
982
{
983
76
76
214
my ($pkg, $oldval) = @_;
984
76
487
return bless \$oldval, $pkg;
985
}
986
987
sub call
988
{
989
76
76
127
my $self = shift;
990
76
50
351
if ($$self) {
991
76
50
1644
print STDERR "# CLOSE TAG WILL CALL CALLBACK\n" if $debug;
992
76
241
$$self->call(@_);
993
} else {
994
0
0
0
print STDERR "# CLOSE TAG NO CALLBACK TO CALL\n" if $debug;
995
0
0
my $attr = shift;
996
0
0
$attr->add_to_result;
997
}
998
76
142
return 22;
999
}
1000
1001
package HTML::Transmorgify::Deferred;
1002
1003
8
8
64
use strict;
8
13
8
264
1004
8
8
64
use warnings;
8
12
8
1788
1005
1006
import HTML::Transmorgify qw($debug);
1007
1008
sub new
1009
{
1010
65
65
189
my ($pkg, $oldval) = @_;
1011
65
396
return bless [$oldval], $pkg;
1012
}
1013
1014
sub call
1015
{
1016
65
65
500
my $self = shift;
1017
65
206
push(@$self, @_);
1018
65
123
return 0;
1019
}
1020
1021
sub doit
1022
{
1023
9
9
52
my $self = shift;
1024
9
50
26
if ($self->[0]) {
1025
9
13
my $cb = shift(@$self);
1026
9
38
$cb->call(@$self);
1027
} else {
1028
0
0
my $attr = shift(@$self);
1029
0
0
$attr->add_to_result;
1030
}
1031
9
251
return 0;
1032
}
1033
1034
1;