File Coverage

blib/lib/LaTeX/Replicase.pm
Criterion Covered Total %
statement 506 533 94.9
branch 364 434 83.8
condition 174 237 73.4
subroutine 21 21 100.0
pod 2 2 100.0
total 1067 1227 86.9


line stmt bran cond sub pod time code
1             package LaTeX::Replicase;
2              
3 2     2   173671 use 5.010;
  2         4  
4 2     2   8 use strict;
  2         6  
  2         39  
5 2     2   9 use warnings;
  2         2  
  2         105  
6 2     2   7 use utf8;
  2         4  
  2         10  
7              
8 2     2   64 use File::Basename qw(fileparse);
  2         3  
  2         163  
9 2     2   8 use File::Path qw(make_path);
  2         2  
  2         85  
10 2     2   786 use File::Compare;
  2         1444  
  2         95  
11 2     2   13 use Carp;
  2         3  
  2         13118  
12              
13             require Exporter;
14              
15             our @ISA = qw(Exporter);
16              
17             our %EXPORT_TAGS = ('all' => [ qw(
18             replication
19             tex_escape
20             ) ],
21             );
22              
23             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24             our @EXPORT = qw( );
25              
26             our $VERSION = '0.705';
27             our $DEBUG; $DEBUG = 0 unless defined $DEBUG;
28             our @logs;
29             our $nlo = 1; # Number Line Output, start of 1
30              
31             sub tex_escape {
32 15 100 66 15 1 209678 return if ! $_[0] or $_[0] =~/^[a-zA-Z0-9,=:;!\.\s\+\-\*]+$/ or $_[0] =~s/^%%%://;
      100        
33              
34 11         33 for( $_[0] ) {
35 11         28 s/\\/\\textbackslash\{\}/g;
36 11         81 s/([%}{_&\$\#])/\\$1/g; # masking active symbols
37 11         57 s/\^/\\$&\{\}/g; # ^ --> \^{}
38              
39 11 100 66     68 s/~/\\texttt\{\\~\{\}\}/g if $_[1] && $_[1] =~/~/; # tilde (~) --> \texttt{\~{}}
40             }
41             }
42              
43              
44             sub replication {
45 23     23 1 278749 my( $source, $info, %op ) = @_;
46              
47 23 100       51 our $DEBUG; $DEBUG = $op{debug} if defined $op{debug};
  23         130  
48 23         52 $DEBUG += 0;
49 23         85 our @logs = ();
50              
51 23 100 66     133 if( defined( $source ) && length( $source ) ) {
52              
53 22 100       90 if(ref \$source eq 'SCALAR') {
    100          
54 19         42 for( $source ) {
55 19         93 s/^\s+//;
56 19         53 s/\s+.*//s;
57              
58 19 50       914 $_ = (glob)[0] if $^O =~/(?:linux|bsd|darwin|solaris|sunos)/;
59             }
60             }
61             elsif(ref $source ne 'ARRAY') {
62 1         3 $_ = "!!! ERROR#6: invalid FILE or ARRAY input!";
63 1 50       4 $op{silent} or carp $_;
64              
65 1         3 push @logs, $_;
66 1         4 return \@logs;
67             }
68              
69             }
70             else {
71 1         5 $_ = "!!! ERROR#0: undefined input FILE or ARRAY!";
72 1 50       5 $op{silent} or carp $_;
73              
74 1         3 push @logs, $_;
75 1         8 return \@logs;
76             }
77              
78 21 100       82 push @logs, "--> Checking source data: '$source'" if $DEBUG;
79              
80 21 100 100     368 if((ref \$source eq 'SCALAR' and ! -s $source) or (ref $source eq 'ARRAY' and ! @$source)) {
      66        
      66        
81 1         4 $_ = "!!! ERROR#1: source ('$source') does NOT exist or is EMPTY!";
82 1 50       5 $op{silent} or carp $_;
83              
84 1         3 push @logs, $_;
85 1         5 return \@logs;
86             }
87              
88             # global data of TeX file
89 20 100 100     170 unless( $info
      66        
90             and (( ref $info eq 'HASH' and %$info ) or (ref $info eq 'ARRAY' and @$info ))
91             ) {
92 2         5 $_= "!!! ERROR#2: EMPTY or WRONG data!";
93 2 50       9 $op{silent} or carp $_;
94              
95 2         5 push @logs, $_;
96 2         11 return \@logs;
97             }
98              
99             # environments: global for %%%V:, %%%VAR: ; and local for %%%VAR:
100 18         40 my $data = my $vardata = $info;
101              
102 18         51 my( $filename, $dir );
103 18 100       53 if( ref \$source eq 'SCALAR') {
104 16         476 ( $filename, $dir, my($ext)) = fileparse( $source );
105             }
106             else { # for ARRAY input
107 2         4 $filename = 'ready.tex';
108 2         5 $dir = '.';
109             }
110              
111 18         45 my( $fh, $ofile );
112 18 100 66     100 if( defined( $_ = $op{ofile} ) && length ) {
113 17 100       64 if(/::STDOUT$/) {
114 1         3 $fh = $ofile = $_;
115             }
116             else {
117 16         48 s/^\s+//;
118 16         53 s/\s+.*//s;
119 16 50       691 $ofile = ( $^O =~/(?:linux|bsd|darwin|solaris|sunos)/ ) ? (glob)[0] : $_;
120             }
121             }
122             else {
123 1   33     6 my $outdir = $op{outdir} // "$dir/$$"; # Target dir for ready TeX file
124 1 50       4 if( length $outdir ) {
125 1         4 for( $outdir ) {
126 1         4 s/^\s+//;
127 1         4 s/\s+.*//s;
128              
129 1 50       50 $_ = (glob)[0] if $^O =~/(?:linux|bsd|darwin|solaris|sunos)/;
130             }
131             }
132             else { # for $outdir = ''
133 0         0 $outdir = "./$$";
134             }
135              
136 1 50       13 unless( -d $outdir ) {
137 1         333 make_path( $outdir, {error => \my $err} );
138              
139 1 50 33     13 if ($err && @$err) {
140              
141 0         0 for my $diag (@$err) {
142 0         0 my( $path, $message ) = %$diag;
143 0 0 0     0 $_ = ( $path && length( $path ) ) ?
144             "!!! ERROR#7: ('$path' creation problem) $message" :
145             "!!! ERROR#8: (general error) $message";
146 0 0       0 $op{silent} or carp $_;
147 0         0 push @logs, $_;
148             }
149              
150 0         0 return \@logs;
151             }
152              
153             }
154              
155 1         4 $ofile = "$outdir/$filename";
156             }
157              
158 18 100       69 push @logs, "--> Using '$ofile' file as output" if $DEBUG;
159              
160             # new file must be different
161 18 100 66     323 if( -s $ofile and ref \$source eq 'SCALAR'
      66        
      66        
162             and (
163             ( $source eq $ofile and compare( $source, $ofile ) == 0 )
164             or
165             ( join(',', stat $source) eq join(',', stat $ofile) )
166             )
167             ) {
168 1         192 $_= "!!! ERROR#3: Input (template) & output files match. Can't overwrite template file!";
169 1 50       6 $op{silent} or carp $_;
170              
171 1         4 push @logs, $_;
172 1         6 return \@logs;
173             }
174              
175 17         44 my $TEMPLATE;
176 17 100       64 if( ref \$source eq 'SCALAR') {
177 15 100       48 my $mode = $op{utf8} ? ':utf8' : '';
178              
179 15 100       38 push @logs, "--> Open '$source'" if $DEBUG;
180              
181 15 50       742 open $TEMPLATE, "<:raw$mode", $source or do{
182 0         0 $_= "!!! ERROR#4: $!";
183 0 0       0 $op{silent} or carp $_;
184              
185 0         0 push @logs, $_;
186 0         0 return \@logs;
187             };
188             }
189              
190 17 100       72 unless( $fh ) { # it's not "::STDOUT"
191 16 100       57 my $mode = $op{utf8} ? ':encoding(utf8)' : '';
192              
193 16 100       52 push @logs, "--> Open '$ofile'" if $DEBUG;
194              
195 1 50   1   1072 open $fh, ">$mode", $ofile or do{
  1         18  
  1         6  
  16         2344  
196 0         0 $_= "!!! ERROR#5: $!";
197 0 0       0 $op{silent} or carp $_;
198              
199 0         0 push @logs, $_;
200 0         0 return \@logs;
201             };
202             }
203              
204 17         1600 $nlo = 1;
205 17         27 my $chkVAR = 0; # check %%%VAR for ARRAY|HASH|SCALAR|REF->SCALAR type
206 17         81 my $key;
207             my $tdz; # flag of The Dead Zone
208 17         0 my @columns;
209 17         31 my $end = 0;
210              
211             =for comment
212             =begin comment
213             @columns:
214             [...]: -- table columns
215             [...]{...} -- descriptions (properties) of table columns:
216             {ki} -- name (key || index ) of a variable from $data->{ $key }
217             {%} -- NO \par
218             {v} -- to paste by default text (located in template) if variable =~/^\x{001}$/
219             {p} -- to paste text on right
220             {head}[...] -- TeX strings before %%%V:
221             {tail}[...] -- TeX strings after %%%V:
222             {eX}[...] -- indices of {head} that eXcept for the first and last elements and rows of %%%VAR:
223             =end comment
224             =cut
225              
226 17 100       52 if( $TEMPLATE ) {
227 15         506 while( my $z = <$TEMPLATE> ) {
228              
229 925         2268 $end = &_line_decryption( $fh, $info, \$z, \$data, \$vardata, \$chkVAR, \$key, \$tdz, \@columns, \%op );
230              
231 925 100       2375 if( $end ) {
232 12 100       27 print { $fh } <$TEMPLATE> if $end != 3; # NOT ( \endinput AND \end{document} )
  9         163  
233              
234 12         31 last; #--> Exit template
235             }
236 913         2914 undef $z;
237              
238             }
239 15         252 close $TEMPLATE;
240             }
241             else {
242 2         8 for my $z ( @$source ) {
243              
244 264 50       415 if( $end ) {
245 0         0 print { $fh } $z;
  0         0  
246             }
247             else {
248 264         583 $end = &_line_decryption( $fh, $info, \$z, \$data, \$vardata, \$chkVAR, \$key, \$tdz, \@columns, \%op );
249              
250 264 100       582 last if $end == 3; # \endinput OR \end{document}
251             }
252             }
253             }
254              
255 17 100 100     61 if( ! $end and defined( $key ) ) {
256 2         8 &_var_output( $fh, $key, $vardata, \@columns, \%op );
257              
258 2         5 $_ = "~~> l.EOF. WARNING#1: Missing '%%%ENDx' tag for '$key'";
259 2 100       23 $op{silent} or carp $_;
260 2         396 push @logs, $_;
261             }
262              
263 17 100       1187 $ofile =~/::STDOUT$/ or close $fh;
264              
265 17 100       136 @logs or return;
266 10         118 return \@logs;
267             }
268              
269             #---------------------
270             # Internal function(s)
271              
272             sub _line_decryption {
273 1189     1189   2621 my( $fh, $info, $z, $data, $vardata, $chkVAR, $key, $tdz, $columns, $op ) = @_;
274              
275 1189         1486 our $DEBUG;
276 1189         1437 our @logs;
277 1189         1455 our $nlo;
278              
279 1189 100       2854 if( defined $$key ) { # We are in VAR-structure
    100          
280              
281 533 100       2150 return 0 unless $$z =~/%%%[AETV]\S*:/; # Nope control tags --> drop TeX line
282              
283 411 100 66     2802 if( $$z =~/%%%(?:END(?[TZ]?)|TDZ|VAR):/) {
    50 100        
      0        
      33        
      66        
284 80         518 my $t = $+{t};
285              
286 80         284 my $end = &_var_output( $fh, $$key, $$vardata, $columns, $op );
287              
288             # Clear the VAR-structure for the next external VARiable
289 80         126 $$chkVAR = 0;
290 80         140 undef $$key;
291 80         369 @$columns = ();
292              
293 80 100       151 return $end if $end;
294              
295 78 100 100     196 return 1 if $t && $t eq 'T'; # END of Template area --> output everything to the end of template without substitution
296              
297 75 100 66     163 undef $$tdz if $t && $t eq 'Z';
298              
299 75 100       482 return 0 if $$z =~/%%%ENDZ?:/; # end of %%%VAR: tag
300              
301 11 100       69 if( $$z =~/%%%+TDZ:/) { # The Dead Zone
302 7         14 $$tdz = 1;
303 7         21 return 0;
304             }
305              
306             }
307             elsif( (ref $$vardata eq 'HASH' and (
308             ref $$vardata->{ $$key } eq 'HASH'
309             or ref $$vardata->{ $$key } eq 'ARRAY'
310             or ref \$$vardata->{ $$key } eq 'SCALAR'
311             or ref $$vardata->{ $$key } eq 'SCALAR' # REF->SCALAR
312             )
313             )
314             or (ref $$vardata eq 'ARRAY' and (
315             ref $$vardata->[ $$key ] eq 'HASH'
316             or ref $$vardata->[ $$key ] eq 'ARRAY'
317             or ref \$$vardata->[ $$key ] eq 'SCALAR'
318             or ref $$vardata->[ $$key ] eq 'SCALAR' # REF->SCALAR
319             )
320             )
321             ) {
322 331 100       808 my $vk = ref $$vardata eq 'HASH' ? $$vardata->{ $$key } : $$vardata->[ $$key ];
323 331 100 100     1060 my $sclr = (ref \$vk eq 'SCALAR' or ref $vk eq 'SCALAR') ? 1 : 0;
324              
325             # Index of column in target table
326 331 100 100     1193 my $j = ( @$columns && exists( $columns->[-1]{ki} ) ) ?
      50        
327             @$columns :
328             ($#$columns // 0);
329 331 100 100     1007 $j = 0 if $j < 0 or $sclr;
330              
331 331 100 100     3758 if( ! $sclr and $$z =~/^(.*?)\s?%%%+V:\s*([^\s:%#]+)(%?)\s?(.*)/) {
    100 100        
332             # the non-SCALAR V-variable is nested in a VAR-structure
333 126         307 my $dV = $1; # Value, by default
334 126         280 my $ki = $2; # name (key or index) of V-variable
335 126         233 my $Np = $3; # NO \par
336 126         222 my $paste = $4; # on right
337              
338 126 100 100     664 if( $$chkVAR == 0b0001) { # V-variable is in {HASH|ARRAY}.ARRAY of VAR-structure
    100 66        
    100 66        
    100 66        
339              
340 32 100 66     266 if( $ki eq '@') {
    100 66        
    50          
341 1         4 $ki = '0-'; # ALL elements
342 1         4 $columns->[$j]{ki} = $ki; # starting index (unnamed meaning)
343             }
344             elsif( $ki =~/^\-*(\d+)$/ && ($1 < @$vk or ($ki < 0 && $1 == @$vk)) ) {
345             # specific indices, e.g.: 0 or 3 or -1
346 16         64 $columns->[$j]{ki} = $ki;
347             }
348             elsif( $ki =~/^[\d,\-]+$/) {
349             # mixed indexes, e.g.: 1-3,6-7-9,-,4,-5,0,7- or 3- (i.e. 3..arr_end) or 0-5 (0..5) or -1- (-1,-2,..arr_start)
350 15         34 for( $ki ) {
351 15         49 s/\-+/-/g;
352 15         43 s/,+/,/g;
353             }
354 15         42 $columns->[$j]{ki} = $ki;
355             }
356             else {
357 0 0 0     0 push @logs, "~~> l.$. WARNING#8: ARRAY index is not numeric in %%%V:". $ki if $DEBUG or ! $op->{ignore};
358             }
359              
360             }
361             elsif( $$chkVAR == 0b0010) { # V-variable is in ARRAY.HASH of VAR-structure
362              
363 38         87 for my $d ( @$vk ) {
364 38 50       107 if( exists $d->{$ki} ) {
365 38         105 $columns->[$j]{ki} = $ki; # save variable name in j-th column
366 38         63 last;
367             }
368             }
369             }
370             elsif( $$chkVAR == 0b0100 or $$chkVAR == 0b01000 ) { # V-variable is SCALAR (or REF->SCALAR) in regular ARRAY of VAR-structure
371              
372 17 100 33     113 if( $ki eq '@') {
    100 66        
    100          
373 5         11 $ki = '0-'; # ALL elements
374 5         15 $columns->[$j]{ki} = $ki; # starting index (unnamed meaning)
375             }
376             elsif( $ki =~/^\-*(\d+)$/ && ($1 < @$vk or ($ki < 0 && $1 == @$vk)) ) {
377             # specific indices, e.g.: 0 or 3 or -1
378 4         14 $columns->[$j]{ki} = $ki;
379             }
380             elsif( $ki =~/^[\d,\-]+$/) {
381             # mixed indexes, e.g.: 1-3,6-7-9,-,4,-5,0,7- or 3- (i.e. 3..arr_end) or 0-5 (0..5) or -1- (-1,-2,..arr_start)
382 7         28 for( $ki ) {
383 7         51 s/\-+/-/g;
384 7         29 s/,+/,/g;
385             }
386 7         22 $columns->[$j]{ki} = $ki;
387             }
388              
389             }
390             elsif( ref $vk eq 'HASH'
391             and exists( $vk->{$ki} )
392             and ( ref \$vk->{$ki} eq 'SCALAR'
393             or ref $vk->{$ki} eq 'SCALAR'
394             or ( $ki eq '@'
395             and ref $vk->{$ki} eq 'ARRAY'
396             )
397             )
398             ) {
399 33         76 $columns->[$j]{ki} = $ki; # save variable key in j-th element
400             }
401              
402 126 100       416 &_set_column( $dV, $Np, $paste, $columns->[$j] ) if exists $columns->[$j]{ki};
403             }
404             elsif( $$z =~/(?.+?)\s?%%%+ADD(?[AEX]?):(?

%?)/

405             or $$z =~/^\s*%%%+ADD(?[AEX]?):(?

%?)\s?(?.*?)[\r\n]*$/

406             ) {
407 200         1273 my $s = $+{s};
408              
409 200 100       826 if( $+{p} ) {
410 51 100       121 length($s) or return 0;
411             }
412             else {
413 149         258 $s .= "\n";
414             }
415              
416 199 100       1137 if( $+{t} eq 'A') { # %%%ADDA:
    100          
417 3         7 push @{ $columns->[$j]{head} }, $s;
  3         11  
418 3         9 $columns->[$j]{ki} = '%%%ADDA'; # phantom
419 3         9 $columns->[$j]{'%'} = 1;
420             }
421             elsif( $+{t} eq 'E') { # %%%ADDE:
422 30 50 66     165 if( @$columns && ( $sclr or (exists( $columns->[-1]{ki} ) && ! $columns->[$j]) ) ) {
      33        
423 30         40 push @{ $columns->[-1]{tail} }, $s;
  30         106  
424             }
425             else {
426 0         0 push @{ $columns->[$j]{head} }, $s;
  0         0  
427             }
428             }
429             else { # %%%ADD[X]:
430 166         277 push @{ $columns->[$j]{head} }, $s;
  166         666  
431 166 100 100     899 $columns->[$j]{eX}{ $#{ $columns->[$j]{head} } } = undef if ! $sclr and $+{t} eq 'X'; # $$chkVAR && ... %%%ADDX:
  33         132  
432             }
433             }
434              
435 330         1000 return 0;
436             }
437             else {
438 0         0 return 0;
439             }
440              
441             }
442             elsif( $$z =~/%%%+END(?[TZ]?):/) { # end of template area
443              
444             # Clear the VAR-structure for the next external variable
445 39         70 $$chkVAR = 0;
446 39         65 undef $$key;
447 39         67 @$columns = ();
448              
449 39 100       366 return 1 if $+{t} eq 'T'; # END of Template area --> output everything to the end of template without substitution
450              
451 33 100       167 undef $$tdz if $+{t} eq 'Z'; # End of TDZ
452 33         85 return 0;
453             }
454              
455 621 100       1436 $$tdz = 1 if $$z =~s/^\s*%%%+TDZ:\s?[\r\n]*//; # The Dead Zone
456              
457 621 100       1166 if( $$tdz ) { # The Dead Zone is ON
458 124 100       290 if( length $$z ) {# Output TeX
459 116         155 print { $fh } $$z;
  116         347  
460 116         180 ++$nlo;
461             }
462 124         287 return 0;
463             }
464              
465 497 100       2683 if( $$z =~/(.*?)\s?%%%+VAR:\s*([^\s:%#]+)(%?)\s?(.*)/) {
    100          
    100          
466 99         290 my $before = $1;
467 99         202 my $k = $2; # name (key)
468 99         220 my $Np = $3; # NO \par
469 99         204 my $paste = $4; # on right text for SCALAR only
470              
471             # root or global structure (environment)
472 99 100       240 my $vd = ( $k =~s/^\/+//) ? $info : $$data;
473              
474 99         164 my $x; # for unknown/undefined sub-key
475              
476             # Search nested sub-keys
477 99         302 for my $sk ( split '/', $k ) {
478 100         157 $$vardata = $vd;
479 100 50       195 length( $sk ) or next;
480              
481 100 100 66     658 if( $sk =~/^\d+$/ && ref $vd eq 'ARRAY' and defined( $vd->[$sk] )) {
    100 66        
      66        
482 3 100       8 last if &_data_redef( $sk, $vd->[$sk], \$k, \$vd, \$x );
483             }
484             elsif( ref $vd eq 'HASH' and exists( $vd->{$sk} )) {
485 82 100       244 last if &_data_redef( $sk, $vd->{$sk}, \$k, \$vd, \$x );
486             }
487             else {
488 15         26 $x = $sk;
489 15         24 last;
490             }
491             }
492              
493             # Clear the VAR-structure for a new variable
494 99         183 $$chkVAR = 0;
495 99         153 undef $$key;
496 99         180 @$columns = ();
497              
498 99 100       194 if( $x ) {
499 16 100 100     101 push @logs, "~~> l.$. WARNING#2: unknown or undef ARRAY|HASH|SCALAR|REF.SCALAR of sub-key '$x' in %%%VAR:". $k if $DEBUG or ! $op->{ignore};
500              
501 16         27 $$vardata = $$data;
502 16         22 print { $fh } $$z;
  16         46  
503 16         24 ++$nlo;
504 16         41 return 0;
505             }
506              
507             # key or sub-...sub-key is found
508 83 100       219 push @logs, "--> l.$. Found %%%VAR:". $k if $DEBUG;
509              
510 83 100       245 my $vk = ref $$vardata eq 'HASH' ? $$vardata->{$k} : $$vardata->[$k];
511              
512 83 100 100     210 push @logs, "~~> l.$. NOT defined key in %%%VAR:". $k if ! defined($vk) && $DEBUG;
513              
514 83 100       205 return 0 if &_chk_var( $fh, $k, $vk, $Np, \$paste, \$before, $chkVAR, $columns, $z, $op );
515              
516             # push @logs, "--> l.$. Remember key = '$k' (chkVAR=$$chkVAR), type: ".ref($vk) if $DEBUG; ###AG
517              
518 82         194 $$key = $k; # save key name
519 82         220 return 0;
520              
521             }
522             elsif( $$z =~/%%%+V:\s*=(def|esc|ignore|silent|debug)=\s*(\S*)/) { # setting up facultative options
523 8   100     51 $op->{$1} = $2 || 0;
524 8 100 100     40 $DEBUG = $2 || 0 if $1 eq 'debug';
525 8         19 return 0;
526             }
527             elsif( $$z =~/^(?.*?)\s?%%%+V:\s*(?[^\s:%#]+)(?

%?)\s?(?.*)/) {

528 54         346 my $k = $+{k};
529              
530 54         138 my %el;
531 54         291 &_set_column( $+{v}, $+{p}, $+{s}, \%el );
532              
533 54         141 my $inidata = $$data; # save initial environment
534              
535 54 100       148 if( $k =~s/^\/+//) {
536 7         13 $$data = $info; # reset to root environment
537              
538 7 100       26 length($k) or return 0;
539             }
540              
541             # Search nested sub-keys
542 50         73 my $x = 0; # for unknown sub-key
543 50         150 for my $sk ( split '/', $k ) {
544 57 50       131 length( $sk ) or next;
545              
546 57         92 my $d;
547 57 100 100     481 if( $sk =~/^\d+$/ && ref $$data eq 'ARRAY' && defined( $$data->[$sk] ) ) {
    100 66        
      66        
548 6         13 $d = $$data->[$sk];
549             }
550             elsif( ref $$data eq 'HASH' && exists( $$data->{$sk} )) {
551 21         54 $d = $$data->{$sk};
552             }
553             else {
554 30 100 100     207 push @logs, "~~> l.$. WARNING#3: unknown sub-key '$sk' in %%%V:". $k if $DEBUG or ! $op->{ignore};
555              
556 30         45 print { $fh } $$z;
  30         65  
557 30         62 ++$nlo;
558              
559 30         42 $x = 1;
560 30         58 last;
561             }
562              
563             # Check type
564 27 100 100     116 if( (ref $d eq 'ARRAY' or ref $d eq 'HASH') ) {
565 9         16 $$data = $d; # sub-key (path) found: redefined
566 9         69 next;
567             }
568              
569 18         25 my $v;
570 18 100       57 if( ref \$d eq 'SCALAR') {
    100          
571 16         34 $v = $d;
572             }
573             elsif( ref $d eq 'SCALAR') { # REF->SCALAR
574 1         4 $v = $$d;
575             }
576             else {
577 1 50 33     14 push @logs, "~~> l.$. WARNING#4: wrong type (not SCALAR|ARRAY|HASH) of '$sk' in %%%V:". $k if $DEBUG or ! $op->{ignore};
578              
579 1         3 print { $fh } $$z;
  1         3  
580 1         2 ++$nlo;
581              
582 1         2 $x = 1;
583 1         3 last;
584             }
585              
586 17 100       55 $_ = &_v_print( $fh, $k, $v, \%el, $op ) and return $_;
587              
588 14         26 $x = 1;
589 14         34 last;
590             }
591              
592 47 100       160 $$data = $inidata if $x; # value found or unknown sub-key: reset to initial environment
593              
594 47         148 return 0;
595             }
596              
597 336         483 print { $fh } $$z;
  336         968  
598 336         491 ++$nlo;
599              
600 336         644 return 0;
601             }
602              
603              
604             sub _set_column {
605 208     208   721 my( $dV, $Np, $paste, $column ) = @_;
606              
607 208 100       623 $column->{v} = $dV if length $dV;
608 208 100       400 $column->{'%'} = 1 if $Np;
609 208 100       509 $column->{p} = $paste if length $paste;
610             }
611              
612              
613             sub _data_redef {
614 85     85   205 my( $sk, $d, $k, $data, $x ) = @_;
615              
616 85 100 100     287 if( ref $d eq 'ARRAY' or ref $d eq 'HASH') {
617 49         77 $$data = $d; # redefined for %%%VAR:
618 49         172 return 0;
619             }
620              
621 36 100 100     108 if( ref \$d eq 'SCALAR' or ref $d eq 'SCALAR') {
622 35         57 $$k = $sk;
623             }
624             else {
625 1         3 $$x = $sk;
626             }
627 36         113 return 1;
628             }
629              
630             sub _chk_var {
631 83     83   237 my( $fh, $k, $vk, $Np, $paste, $before, $chkVAR, $columns, $z, $op ) = @_;
632              
633 83         132 our $DEBUG;
634 83         116 our @logs;
635 83         97 our $nlo;
636              
637 83 100 100     267 if( ref $vk eq 'ARRAY') {
    100          
638              
639 38 100       50 if( @{ $vk } ) {
  38         82  
640             # Check ARRAY.{ARRAY|HASH|SCALAR[.REF]}
641              
642 37         59 for my $d ( @{ $vk } ) {
  37         104  
643 135 100       329 if(ref $d eq 'ARRAY'){
    100          
    100          
    50          
644 35         63 $$chkVAR |= 0b00001;
645             }
646             elsif(ref $d eq 'HASH') {
647 22         40 $$chkVAR |= 0b00010;
648             }
649             elsif(ref \$d eq 'SCALAR') {
650 74         122 $$chkVAR |= 0b00100;
651             }
652             elsif(ref $d eq 'SCALAR') { # REF->SCALAR
653 4         10 $$chkVAR |= 0b01000;
654             }
655             else {
656 0         0 $$chkVAR |= 0b10000;
657             }
658             }
659             }
660             else {
661 1         4 $$chkVAR |= 0b00100; # by default, SCALAR
662             }
663              
664 38 100 66     248 if( ! $$chkVAR or $$chkVAR > 0b01000 or ($$chkVAR & ($$chkVAR - 1)) ) {
      66        
665 1 50 33     17 push @logs, "~~> l.$. WARNING#6: mixed types (ARRAY with HASH with SCALAR or other) of %%%VAR:". $k if $DEBUG or ! $op->{ignore};
666              
667 1         2 print { $fh } $$z;
  1         19  
668 1         4 ++$nlo;
669 1         7 return 1;
670             }
671             }
672             elsif( ref \$vk eq 'SCALAR' or ref $vk eq 'SCALAR') {
673 35         104 $columns->[0]{ki} = $k;
674 35         80 &_set_column('', $Np, $$paste, $columns->[0] );
675             }
676              
677 82 100       172 if( $$before ) {# Output prefix TeX
678 13         24 print { $fh } $$before;
  13         42  
679             # ++$nlo;
680             }
681              
682 82         225 return 0;
683             }
684              
685             # VALUE output
686             sub _v_print {
687 438     438   962 my( $fh, $k, $v, $el, $op ) = @_;
688 438 100       854 $v = $$v if ref $v eq 'SCALAR';
689              
690 438         558 our $DEBUG;
691 438         532 our @logs;
692 438         513 our $nlo;
693              
694 438 100       812 unless( defined $v ) {
695 40 100       90 if( $op->{def} ) {
696 39 100       85 push @logs, "~~> l.$.".' NOT defined %%%V[AR]:'. $k if $DEBUG;
697 39         95 return 0;
698             }
699 1         3 $v = '';
700             }
701              
702 399 100       920 if( $v =~/^\x{001}[\x{003}\x{004}]?$/) { # by default text from template
703 4 100       14 if( exists $el->{v} ) {
704 3         7 print { $fh } $el->{v};
  3         20  
705              
706 3 50       16 push @logs, "--> l.$.>$nlo".' Insert text by default %%%V[AR]:'. $k .'= '. $el->{v} if $DEBUG;
707             }
708 4         14 $v =~s/^\x{001}//;
709             }
710              
711 399 100       1333 if( $v =~/^\x{003}$/) { # END of INPUT template, similar to \endinput
    100          
    100          
712 1         2 say { $fh } '\endinput';
  1         2  
713 1         1 ++$nlo;
714 1         4 return 3;
715             }
716             elsif( $v =~/^\x{004}$/) { # END of INPUT template, similar to \endinput
717 4         7 say { $fh } '\end{document}';
  4         21  
718 4         8 ++$nlo;
719 4         21 return 3;
720             }
721             elsif( length $v ) {
722 385 100       816 tex_escape( $v, $op->{esc} ) if $op->{esc};
723              
724 385 100       848 push @logs, "--> l.$.>$nlo".' Insert %%%V[AR]:'. $k .'= '. $v if $DEBUG;
725              
726 385         523 print { $fh } $v;
  385         1157  
727 385         1034 ++$nlo while $v =~/\n/g;
728              
729 385 100       877 print { $fh } $el->{p} if exists $el->{p};
  10         41  
730             }
731              
732 394 100       775 unless( $el->{'%'} ) {
733 260         387 print { $fh } "\n"; # NO:YES \par
  260         578  
734 260         420 ++$nlo;
735             }
736              
737 394         971 return 0;
738             }
739              
740             # HEAD-TAIL output
741             sub _ht_print {
742 840     840   1580 my( $fh, $el, $ht, $border ) = @_;
743              
744 840 100       1875 $el->{$ht} or return;
745              
746 407         551 our $DEBUG;
747 407         530 our @logs;
748 407         514 our $nlo;
749              
750 407         595 my $i = 0;
751 407         535 foreach( @{ $el->{$ht} } ) {
  407         910  
752 496 100 100     1647 next if $ht eq 'head' and $border && exists( $el->{eX} ) && exists( $el->{eX}{$i} );
      100        
      100        
753              
754 463 100       1077 push @logs, "-->\tl.$.>$nlo Insert $ht: ". $_ if $DEBUG;
755              
756 463         615 print { $fh } $_;
  463         1577  
757 463         744 ++$nlo;
758             }
759             continue {
760 496         996 ++$i;
761             }
762              
763             }
764              
765             # HEAD-VALUE-TAIL output
766             sub _hvt_print {
767 437     437   1011 my( $fh, $ki, $val, $el, $op, $border ) = @_;
768              
769 437         573 our $DEBUG;
770 437         553 our @logs;
771 437         514 our $nlo;
772              
773 437 100 100     1357 if( length($ki) and ! defined $val ) {
774 16 50       43 if( $op->{def} ) {
775 16 100       58 push @logs, "~~> l.$.".' NOT defined %%%V:'. $ki if $DEBUG;
776 16         43 return 0;
777             }
778              
779 0         0 $val = '';
780             }
781              
782             # output head of variable
783 421         955 &_ht_print( $fh, $el, 'head', $border );
784              
785             # output value of variable ( $fh, $ki, $val, $el, $op )
786 421 100       758 $_ = &_v_print and return $_;
787              
788             # output tail of variable
789 419         980 &_ht_print( $fh, $el, 'tail', 0);
790              
791 419         932 return 0;
792             }
793              
794              
795             sub _s_a_prn {
796 203     203   428 my( $fh, $i, $values, $el, $op, $border, $col ) = @_;
797              
798 203         375 my $val = $values->[$i];
799 203 100       433 $val = $$val if ref $val eq 'SCALAR';
800              
801 203         290 my $end = 0;
802              
803 203 100       403 if( ref \$val eq 'SCALAR') {
    50          
804 201         400 $end = &_hvt_print( $fh, $i, $val, $el, $op, $$border );
805              
806 201         325 ++$$col;
807 201         298 $$border = 0;
808             }
809             elsif( ref $val eq 'ARRAY') { # [...].ARRAY.ARRAY
810              
811 2         3 for( @$val ) {
812 17 50       25 next if ref \$_ ne 'SCALAR';
813              
814 17         43 $end = &_hvt_print( $fh, $i, $_, $el, $op, $$border );
815              
816 17         16 ++$$col;
817 17         13 $$border = 0;
818              
819 17 50       25 last if $end;
820             }
821              
822             }
823              
824 203         652 return $end;
825             }
826              
827              
828             sub _mixed_indices {
829 125     125   289 my( $fh, $ki, $values, $el, $op, $border ) = @_;
830              
831 125         206 my $nd = @$values;
832 125         190 my $col = my $end = 0;
833              
834 125         403 for my $ii ( split ',', $ki ) { # e.g. -1-,1-3,6-7-9,-,4,-5,0,7-
835 142 100       277 next if $ii eq '-';
836              
837 141 100       341 if( $ii =~/^(\-[1-9]\d*)\-(\d*)$/) { # -1- i.e. reverse: -1,-2,..-@arr (i.e. arr_start)
838 12         31 my $s = $1;
839 12   66     95 my $e = -1*($2 || $nd);
840 12 100       41 $s = -1*$nd if abs($s) > $nd;
841 12 100       26 $e = -1*$nd if abs($e) > $nd;
842 12 100       29 ($s, $e) = ($e, $s) if $e > $s;
843              
844 12         33 for( my $i = $s; $i >= $e; --$i ) {
845 38 50       81 $end = &_s_a_prn( $fh, $i, $values, $el, $op, \$border, \$col ) and last;
846             }
847              
848 12 50       38 $end ? last : next;
849             }
850              
851 129 100       274 if( $ii =~/^\-[0-9]+$/ ) { # -5
852 1         5 my $i = $ii+0;
853              
854 1 50       5 if( abs($i) <= $nd ) {
855 1 50       4 $end = &_s_a_prn( $fh, $i, $values, $el, $op, \$border, \$col ) and last;
856             }
857              
858 1         22 next;
859             }
860              
861 128         403 my @n = grep{length} sort{$a <=> $b} split '-', $ii;
  131         391  
  3         11  
862              
863 128 100 66     518 if( @n < 2 and $n[0] < $nd ) { # e.g. 4 || 0 || 7(-)
864 125 100       311 if( $ii =~/\-$/) { # 7(-)
865              
866 17         47 for( my $i = $n[0]; $i < $nd; ++$i ) {
867 49 50       107 $end = &_s_a_prn( $fh, $i, $values, $el, $op, \$border, \$col ) and last;
868             }
869              
870             }
871             else { # 4 || 0
872 108 50       312 $end = &_s_a_prn( $fh, $n[0], $values, $el, $op, \$border, \$col ) and last;
873             }
874              
875             }
876             else { # 1-3 ->(1..3) || 6-7-9 ->(6..9)
877 3         8 for( my $i = $n[0]; $i <= $n[-1]; ++$i ) {
878 7 100       12 $end = &_s_a_prn( $fh, $i, $values, $el, $op, \$border, \$col ) and last;
879             }
880             }
881              
882 128 100       353 last if $end;
883             }
884              
885 125         338 return( $col, $end );
886             }
887              
888              
889             sub _var_output {
890 82     82   198 my( $fh, $key, $vardata, $columns, $op ) = @_;
891 82 100       264 my $values = (ref $vardata eq 'HASH') ? $vardata->{ $key } : $vardata->[ $key ];
892              
893 82 50       165 @$columns or return;
894              
895 82         121 our $DEBUG;
896 82         114 our @logs;
897 82         101 our $nlo;
898              
899 82         147 my $end = 0;
900              
901 82 100 100     290 if( ref \$values eq 'SCALAR' or ref $values eq 'SCALAR') { # key => SCALAR
902 35         107 return &_hvt_print( $fh, $key, $values, $columns->[0], $op );
903             }
904              
905 47 100       114 if( ref $values eq 'ARRAY') { # key => ARRAY
    50          
906              
907 37 100       94 unless( @$values ) {
908 1 50 33     31 push @logs, "~~> l.$. WARNING#7: empty ARRAY of %%%VAR:". $key if $DEBUG or ! $op->{ignore};
909 1         5 return 0;
910             }
911              
912             # Forming a table
913 36         62 my $row = 0;
914 36         59 my $nd = @$values;
915              
916             _var_output_M0:
917 36         80 foreach my $d ( @$values ) { # loop through table rows
918              
919 92 100       209 push @logs, '--> Table row = '. $row if $DEBUG;
920              
921 92         134 my $col = 0;
922 92         157 foreach my $el ( @$columns ) { # loop through table columns (for ARRAY.HASH) or rows (for ARRAY.SCALAR)
923              
924 269         515 my $ki = $el->{ki};
925 269 100 100     808 my $border = ((! $row and ! $col) or ($row >= $#{ $values } and (!defined( $ki ) or !length( $ki )) ) ) ? 1 : 0;
926              
927 269         444 my $val;
928 269 100       446 if( defined $ki ) {
929 232 50 100     1077 if( $ki eq '%%%ADDA') {
    100          
    100          
    50          
    0          
930 0         0 $val = '';
931             }
932             elsif( ref \$d eq 'SCALAR' or ref $d eq 'SCALAR') { # (ARRAY.SCALAR or ARRAY.REF->SCALAR) in regular vector
933              
934 28 50       142 if( $ki =~/^[\d,\-]+$/) {
935             # mixed indices, e.g.: 1-3,6-7-9,-,4,-5,0,7- or 3- (i.e. 3..arr_end) or 0-5 (0..5) or -1- (-1,-2,..arr_start)
936 28 100       81 last _var_output_M0 if $row;
937              
938 16         74 ($_, $end) = &_mixed_indices( $fh, $ki, $values, $el, $op, $border );
939 16 50       67 $col += $_ - 1 if $_;
940             }
941              
942 16 100       44 $end ? last _var_output_M0 : next;
943             }
944             elsif( ref $d eq 'HASH') { # ARRAY.HASH
945 95         192 $val = $d->{$ki};
946              
947 95 100 100     483 if( defined( $val ) && ref $val eq 'ARRAY') { # ARRAY.HASH.ARRAY
    50 33        
948 2         7 for my $vv ( @$val ) {
949 14 50       34 next unless ref \$vv eq 'SCALAR';
950              
951 14 50       25 $end = &_hvt_print( $fh, $ki, $vv, $el, $op, $border ) and last _var_output_M0;
952 14         20 ++$col;
953             }
954              
955 2 50       10 $end ? last _var_output_M0 : next;
956             }
957             elsif( ref \$val ne 'SCALAR' and ref $val ne 'SCALAR') {
958 0         0 next;
959             }
960             }
961             elsif( ref $d eq 'ARRAY') { # ARRAY.ARRAY
962              
963 109 50       482 if( $ki =~/^[\d,\-]+$/) {
964             # mixed indices, e.g.: 1-3,6-7-9,-,4,-5,0,7- or 3- (i.e. 3..arr_end) or 0-5 (0..5) or -1- (-1,-2,..arr_start)
965 109         272 ($_, $end) = &_mixed_indices( $fh, $ki, $d, $el, $op, $border );
966 109 50       277 $col += $_ - 1 if $_;
967             }
968              
969 109 50       299 $end ? last _var_output_M0 : next;
970             }
971             elsif( $op->{def} ) {
972              
973 0 0       0 push @logs, "~~> l.$. NOT defined %%%V:". $ki if $DEBUG;
974              
975 0         0 next;
976             }
977             }
978             else {
979             # empty parameter -- at the very end of the columns (parameters)
980 37         101 $ki = '';
981             }
982              
983 130 50       263 $end = &_hvt_print( $fh, $ki, $val, $el, $op, $border ) and last _var_output_M0;
984             }
985             continue {
986 256         530 ++$col;
987             }
988              
989 79 50       156 last if $end;
990             }
991             continue {
992 79         186 ++$row;
993             }
994              
995             }
996             elsif( ref $values eq 'HASH') {
997              
998 10         17 my $col = 0;
999 10         22 foreach my $el ( @$columns ) { # loop through parameters of %%%VAR-structure
1000              
1001 39         80 my $ki = $el->{ki};
1002 39 100 100     101 my $border = ( ! $col or ($col >= $#{ $columns } and (!defined( $ki ) or !length( $ki )) )) ? 1 : 0;
1003              
1004 39         64 my $val;
1005 39 100       70 if( defined $ki ) {
1006 36 100 100     270 if( $ki eq '%%%ADDA') {
    100 66        
    100 33        
    100 66        
    50 66        
1007 3         5 $val = '';
1008             }
1009             elsif( ref \$values->{$ki} eq 'SCALAR' and ( !$op->{def} or defined( $values->{$ki} ) ) ) { # HASH.SCALAR
1010 24         42 $val = $values->{$ki};
1011             }
1012             elsif( ref $values->{$ki} eq 'SCALAR' and ( !$op->{def} or defined( ${ $values->{$ki} } ) ) ) { # HASH.REF->SCALAR
1013 1         3 $val = ${ $values->{$ki} };
  1         3  
1014             }
1015             elsif( $ki eq '@' and ref $values->{'@'} eq 'ARRAY') {
1016 2         4 for my $k ( @{ $values->{'@'} } ) {
  2         5  
1017 11 100 66     48 next unless defined($k) && exists( $values->{$k} );
1018              
1019 10         16 my $v;
1020 10 100       38 if( ref \$values->{$k} eq 'SCALAR') {
    100          
    50          
1021 8         15 $v = $values->{$k};
1022             }
1023             elsif( ref $values->{$k} eq 'SCALAR') {
1024 1         3 $v = ${ $values->{$k} };
  1         19  
1025             }
1026             elsif( $op->{def} ) {
1027 1 50       5 push @logs, "-->\tl.$. ". 'NOT HASH.ARRAY.SCALAR %%%V:@->{'.$k."} in %%%VAR:". $key if $DEBUG;
1028              
1029 1         4 next;
1030             }
1031              
1032 9 50       37 $end = &_hvt_print( $fh, $k, $v, $el, $op, $border ) and last _var_output_M0;
1033 9         22 $border = 0;
1034             }
1035              
1036 2 50       9 $end ? last : next;
1037             }
1038             elsif( $op->{def} ) {
1039 6 50       14 push @logs, "~~> l.$. NOT HASH.SCALAR or NOT defined %%%V:". $ki if $DEBUG;
1040              
1041 6         12 next;
1042             }
1043             }
1044             else {
1045             # empty parameter -- at the very end of the columns (parameters)
1046 3         6 $ki = '';
1047             }
1048              
1049 31 100       66 $end = &_hvt_print( $fh, $ki, $val, $el, $op, $border ) and last;
1050             }
1051             continue {
1052 38         75 ++$col;
1053             }
1054             }
1055              
1056 46         105 return $end;
1057             }
1058              
1059             1;
1060              
1061             __END__