Lines 1-238
Link Here
|
1 |
#!/usr/bin/perl |
|
|
2 |
|
3 |
use Modern::Perl; |
4 |
use Carp qw( croak ); |
5 |
|
6 |
use Getopt::Long qw( GetOptions ); |
7 |
use File::Copy qw( copy ); |
8 |
|
9 |
my $help_msg = <<EOH; |
10 |
This script does a first-cut conversion of koha HTML::Template template files (.tmpl). |
11 |
It creates a mirror of koha-tmpl called koha-tt where converted files will be placed. |
12 |
By default all files will be converted: use the --file (-f) argument to specify |
13 |
individual files to process. |
14 |
|
15 |
Options: |
16 |
--koharoot (-r): Root directory of koha installation. |
17 |
--type (-t): template file extenstions to match |
18 |
(defaults to tmpl|inc|xsl). |
19 |
--copyall (-c): Also copy across all files in template directory |
20 |
--file (-f): specify individual files to process |
21 |
--debug (-d): output more information. |
22 |
EOH |
23 |
|
24 |
my $tmpl_in_dir = 'koha-tmpl'; |
25 |
my $tmpl_out_dir = 'koha-tt'; |
26 |
|
27 |
# template toolkit variables NOT to scope, in other words, variables that need to remain global (case sensitive) |
28 |
my @globals = ( "themelang","JacketImages","OPACAmazonCoverImages","GoogleJackets","BakerTaylorEnabled", |
29 |
"SyndeticsEnabled", "OpacRenewalAllowed", "item_level_itypes","noItemTypeImages", |
30 |
"virtualshelves", "OPACHoldRequests", "COinSinOPACResults", "OPACXSLTResultsDisplay", |
31 |
"OPACItemsResultsDisplay", "LibraryThingForLibrariesID", "opacuserlogin", "TagsEnabled", |
32 |
"TagsShowOnList", "TagsInputOnList","loggedinusername","opacbookbag", |
33 |
"OPACAmazonEnabled", "SyndeticsCoverImages" ); |
34 |
|
35 |
# Arguments: |
36 |
my $KOHA_ROOT; |
37 |
my $tmpl_extn_match = "tmpl|inc|xsl"; # Type match defaults to *.tmpl plus *.inc if not specified |
38 |
my $copy_other_files = 0; |
39 |
my @template_files; |
40 |
my @files_w_tmpl_loops; |
41 |
my $verbose = 0; |
42 |
GetOptions ( |
43 |
"koharoot=s" => \$KOHA_ROOT, |
44 |
"type|t=s" => \$tmpl_extn_match, |
45 |
"copyall|c" => \$copy_other_files, |
46 |
"file|f=s" => \@template_files, # array of filenames |
47 |
"verbose+" => \$verbose, # incremental flag |
48 |
) or die $help_msg; |
49 |
|
50 |
if ( ! $KOHA_ROOT || ! -d $KOHA_ROOT ) { |
51 |
croak "Koha root not passed or is not correct."; |
52 |
} |
53 |
if ( ! -d "$KOHA_ROOT/$tmpl_in_dir" ) { |
54 |
croak "Cannot find template dir ($tmpl_in_dir)"; |
55 |
} |
56 |
|
57 |
# Attempt to create koha-tt dir.. |
58 |
if ( ! -d "$KOHA_ROOT/$tmpl_out_dir" ) { |
59 |
mkdir("$KOHA_ROOT/$tmpl_out_dir") #, '0755' |
60 |
or croak "Cannot create $tmpl_out_dir directory in $KOHA_ROOT: $!"; |
61 |
} |
62 |
|
63 |
# Obtain list of files to process - go recursively through tmpl_in_dir and subdirectories.. |
64 |
unless ( scalar(@template_files) ) { |
65 |
@template_files = mirror_template_dir_structure_return_files("$KOHA_ROOT/$tmpl_in_dir", "$tmpl_extn_match"); |
66 |
} |
67 |
foreach my $file (@template_files) { |
68 |
(my $new_path = $file) =~ s/$tmpl_in_dir/$tmpl_out_dir/; |
69 |
$new_path =~ s/\.tmpl/.tt/; |
70 |
$new_path = "$KOHA_ROOT/$new_path" unless ( $new_path =~ m/^$KOHA_ROOT/ ); |
71 |
|
72 |
open my $ITMPL, '<', $file or croak "Can't open $file for input: $!"; |
73 |
open my $OTT, '>', $new_path or croak "Can't open $new_path for output: $!"; |
74 |
|
75 |
# allows 'proper' handling of for loop scope |
76 |
# cur_scope is a stack of scopes, the last being the current |
77 |
# when opening a for loop push scope onto end, when closing for loop pop |
78 |
my @cur_scope = (""); |
79 |
# flag representing if we've found a for loop this iteration |
80 |
my $for_loop_found = 0; |
81 |
|
82 |
for my $input_tmpl(<$ITMPL>){ |
83 |
my @parts = split "<", $input_tmpl; |
84 |
for( my $i=0; $i<=$#parts; ++$i ){ |
85 |
my $input_tmpl = $i ? "<" . $parts[$i] : $parts[$i]; # add < sign back in to every part except the first |
86 |
$for_loop_found = 0; |
87 |
|
88 |
# handle poorly names variable such as f1!, f1+, f1-, f1| and mod |
89 |
$input_tmpl =~ s/"(\w+)\|"/"$1pipe"/ig; |
90 |
$input_tmpl =~ s/"(\w+)\+"/"$1plus"/ig; |
91 |
$input_tmpl =~ s/"(\w+)\-"/"$1minus"/ig; |
92 |
$input_tmpl =~ s/"(\w+)!"/"$1exclamation"/ig; |
93 |
$input_tmpl =~ s/"(\w+),(\w+)"/"$1comma$2"/ig; #caused a problem in patron search |
94 |
$input_tmpl =~ s/NAME="mod"/NAME="modname"/ig; |
95 |
# handle 'naked' TMPL_VAR "parameter" by turning them into what they should be, TMPL_VAR NAME="parameter" |
96 |
$input_tmpl =~ s/TMPL_VAR\s+"(\w+)"/TMPL_VAR NAME="$1"/ig; |
97 |
# make an end (ESCAPE NAME DEFAULT) into a ned (NAME ESCAPE DEFAULT) |
98 |
$input_tmpl =~ s/ESCAPE="(\w+?)"\s+NAME=['"](\w+?)['"]\s+DEFAULT=['"](.+?)['"]/NAME="$2" ESCAPE="$1" DEFAULT="$3"/ig; |
99 |
|
100 |
# Process.. |
101 |
# NB: if you think you're seeing double, you probably are, *some* (read:most) patterns appear twice: once with quotations marks, once without. |
102 |
# trying to combine them into a single pattern proved troublesome as a regex like ['"]?(.*?)['"]? was causing problems and fixing the problem caused (a lot) more complex regex |
103 |
|
104 |
# variables |
105 |
$input_tmpl =~ s/<[!-]*\s*TMPL_VAR\s+NAME\s?=\s?['"]?\s*(\w*?)\s*['"]?\s+ESCAPE=['"](\w*?)['"]\s+DEFAULT=['"]?(.*?)['"]?\s*-*>/[% DEFAULT $cur_scope[-1]$1="$3" |$2 %]/ig; |
106 |
$input_tmpl =~ s/<[!-]*\s*TMPL_VAR\s+NAME\s?=\s?['"]\s*(\w*?)\s*['"]\s+ESCAPE=['"]?(\w*?)['"]?\s*-*>/[% $cur_scope[-1]$1 |$2 %]/ig; |
107 |
$input_tmpl =~ s/<[!-]*\s*TMPL_VAR\s+NAME\s?=\s?(\w*?)\s+ESCAPE=['"]?(\w*?)['"]?\s*-*>/[% $cur_scope[-1]$1 |$2 %]/ig; |
108 |
$input_tmpl =~ s/<[!-]*\s*TMPL_VAR\s+ESCAPE=['"]?(\w*?)['"]?\s+NAME\s?=\s?['"]?([\w-]*?)['"]?\s*-*>/[% $cur_scope[-1]$2 |$1 %]/ig; |
109 |
$input_tmpl =~ s/<[!-]*\s*TMPL_VAR\s+NAME\s?=\s?['"]?(\w*?)['"]?\s+DEFAULT=['"](.*?)['"]\s*-*>/[% DEFAULT $cur_scope[-1]$1="$2" %]/ig; |
110 |
$input_tmpl =~ s/<[!-]*\s*TMPL_VAR\s+NAME\s?=\s?['"]?\s*(\w*?)\s*['"]?\s+DEFAULT=(.*?)\s*-*>/[% DEFAULT $cur_scope[-1]$1=$2 %]/ig; |
111 |
$input_tmpl =~ s/<[!-]*\s*TMPL[_\s]VAR\s+NAME\s?=\s?['"]?\s*(\w*?)\s*['"]?\s*-*>/[% $cur_scope[-1]$1 %]/ig; |
112 |
$input_tmpl =~ s/<[!-]*\s*TMPL[_\s]VAR\s+EXPR\s?=\s?['"](.*?)['"]\s*-*>/[% $1 %]/ig; # TMPL_VAR NAME and TMPL_VAR EXPR are logically equiv |
113 |
$input_tmpl =~ s/<[!-]*\s*TMPL[_\s]VAR\s+EXPR\s?=\s?(.*?)\s*-*>/[% $1 %]/ig; |
114 |
|
115 |
# if, elseif and unless blocks |
116 |
$input_tmpl =~ s/<[!-]*\s*TMPL_IF\s+EXPR\s?=\s?['"](.*?)['"]\s*-*>/[% IF ( $1 ) %]/ig; |
117 |
$input_tmpl =~ s/<[!-]*\s*TMPL_IF\s+EXPR\s?=\s?(.*?)\s*-*>/[% IF ( $1 ) %]/ig; |
118 |
$input_tmpl =~ s/<[!-]*\s*TMPL_IF\s+NAME\s?=\s?['"]\s*(\w*?)\s*['"]\s*-*>/[% IF ( $cur_scope[-1]$1 ) %]/ig; |
119 |
$input_tmpl =~ s/<[!-]*\s*TMPL_IF\s+NAME\s?=\s?(\w*?)\s*-*>/[% IF ( $cur_scope[-1]$1 ) %]/ig; |
120 |
$input_tmpl =~ s/<[!-]*\s*TMPL_IF\s+['"](.*?)['"]\s*-*>/[% IF ( $cur_scope[-1]$1 ) %]/ig; |
121 |
$input_tmpl =~ s/<[!-]*\s*TMPL_IF\s+([\w\s]*?)\s*-*>/[% IF ( $cur_scope[-1]$1 ) %]/ig; |
122 |
|
123 |
$input_tmpl =~ s/<[!-]*\s*TMPL_ELSIF\s+EXPR\s?=\s?['"](.*?)['"]\s*-*>/[% ELSIF ( $1 ) %]/ig; |
124 |
$input_tmpl =~ s/<[!-]*\s*TMPL_ELSIF\s+EXPR\s?=\s?(.*?)\s*-*>/[% ELSIF ( $1 ) %]/ig; |
125 |
$input_tmpl =~ s/<[!-]*\s*TMPL_ELSIF\s+NAME\s?=\s?['"](\w*?)['"]\s*-*>/[% ELSIF ( $cur_scope[-1]$1 ) %]/ig; |
126 |
$input_tmpl =~ s/<[!-]*\s*TMPL_ELSIF\s+NAME\s?=\s?(\w*?)\s*-*>/[% ELSIF ( $cur_scope[-1]$1 ) %]/ig; |
127 |
$input_tmpl =~ s/<[!-]*\s*TMPL_ELSIF\s+['"](\w*?)['"]\s*-*>/[% ELSIF ( $cur_scope[-1]$1 ) %]/ig; |
128 |
$input_tmpl =~ s/<[!-]*\s*TMPL_ELSIF\s+(\w*?)\s*-*>/[% ELSIF ( $cur_scope[-1]$1 ) %]/ig; |
129 |
|
130 |
$input_tmpl =~ s/<[!-]*\s*TMPL_ELSE\s*-*>/[% ELSE %]/ig; |
131 |
$input_tmpl =~ s/<[!-]*\s*\/TMPL_IF\s*-*>/[% END %]/ig; |
132 |
|
133 |
$input_tmpl =~ s/<[!-]*\s*TMPL_UNLESS\s+NAME\s?=\s?['"]?(\w*?)['"]?\s*-*>/[% UNLESS ( $cur_scope[-1]$1 ) %]/ig; |
134 |
$input_tmpl =~ s/<[!-]*\s*\/TMPL_UNLESS\s*-*>/[% END %]/ig; |
135 |
# includes |
136 |
$input_tmpl =~ s/<[!-]*\s*TMPL_INCLUDE\s+NAME\s?=\s?"(.*?\.inc)"\s*-*>/[% INCLUDE '$1' %]/ig; |
137 |
$input_tmpl =~ s/<[!-]*\s*TMPL_INCLUDE\s+NAME\s?=\s?"(.*?)"\s*-*>/[% INCLUDE $1 %]/ig; |
138 |
|
139 |
#reverse scoping bug fix |
140 |
for my $tag (@globals){ |
141 |
next unless $cur_scope[-1]; |
142 |
$input_tmpl =~ s/$cur_scope[-1]$tag/$tag/g; |
143 |
} |
144 |
|
145 |
if ($input_tmpl =~ m/<[!-]*\s*TMPL_LOOP/i ){ |
146 |
$for_loop_found = 1; |
147 |
} |
148 |
|
149 |
$input_tmpl =~ s/<[!-]*\s*TMPL_LOOP\s+NAME\s?=\s?['"](?<SCOPE>.*?)['"]\s*-*>/"[% FOREACH " . substr($+{SCOPE}, 0, -1) . " IN $cur_scope[-1]$1 %]"/ieg; |
150 |
$input_tmpl =~ s/<[!-]*\s*TMPL_LOOP\s+NAME\s?=\s?(?<SCOPE>.*?)\s*-*>/"[% FOREACH " . substr($+{SCOPE}, 0, -1) . " IN $cur_scope[-1]$1 %]"/ieg; |
151 |
|
152 |
# handle new scope |
153 |
if($for_loop_found){ |
154 |
my $scope = substr($+{SCOPE}, 0, -1) . "."; |
155 |
push(@cur_scope, $scope); |
156 |
$for_loop_found = 0; |
157 |
} |
158 |
|
159 |
# handle loops and old scope |
160 |
if ( $input_tmpl =~ m/<!--[\s\/]*TMPL_LOOP\s*-->/i ) { |
161 |
push(@files_w_tmpl_loops, $new_path); |
162 |
pop(@cur_scope); |
163 |
} |
164 |
|
165 |
$input_tmpl =~ s/<[!-]*\s*\/TMPL_LOOP\s*-*>/[% END %]/ig; |
166 |
|
167 |
# misc 'patches' |
168 |
$input_tmpl =~ s/\seq\s/ == /ig; |
169 |
$input_tmpl =~ s/HTML/html/g; |
170 |
$input_tmpl =~ s/URL/url/g; |
171 |
$input_tmpl =~ s/dhtmlcalendar_dateformat/DHTMLcalendar_dateformat/ig; |
172 |
$input_tmpl =~ s/\w*\.__first__/loop.first/ig; |
173 |
$input_tmpl =~ s/\w*\.__last__/loop.last/ig; |
174 |
$input_tmpl =~ s/\w*\.__odd__/loop.odd/ig; |
175 |
$input_tmpl =~ s/\w*\.__even__/loop.even/ig; |
176 |
$input_tmpl =~ s/\w*\.__counter__/loop.count/ig; #loop.count gives the range (0..max) whereas loop.index gives the range (1..max+1), __counter__ is unknown |
177 |
|
178 |
# hack to get around lack of javascript filter |
179 |
$input_tmpl =~ s/\|\s*JS/|replace("'", "\\'") |replace('"', '\\"') |replace('\\n', '\\\\n') |replace('\\r', '\\\\r')/ig; |
180 |
|
181 |
# Write out.. |
182 |
print $OTT $input_tmpl; |
183 |
} |
184 |
} |
185 |
close $ITMPL; |
186 |
close $OTT; |
187 |
} |
188 |
|
189 |
if ( scalar(@files_w_tmpl_loops) && $verbose ) { |
190 |
print "\nThese files contain TMPL_LOOPs that need double checking:\n"; |
191 |
foreach my $file (@files_w_tmpl_loops) { |
192 |
print "$file\n"; |
193 |
} |
194 |
} |
195 |
|
196 |
## SUB-ROUTINES ## |
197 |
|
198 |
# Create new directory structure and return list of template files |
199 |
sub mirror_template_dir_structure_return_files { |
200 |
my($dir, $type) = @_; |
201 |
|
202 |
my @files = (); |
203 |
if ( opendir(DIR, $dir) ) { |
204 |
my @dirent = readdir DIR; # because DIR is shared when recursing |
205 |
closedir DIR; |
206 |
for my $dirent (@dirent) { |
207 |
my $path = "$dir/$dirent"; |
208 |
if ( $dirent =~ /^\./ ) { |
209 |
; |
210 |
} |
211 |
elsif ( -f $path ) { |
212 |
(my $new_path = $path) =~ s/$tmpl_in_dir/$tmpl_out_dir/; |
213 |
$new_path = "$KOHA_ROOT/$new_path" unless ( $new_path =~ m/^$KOHA_ROOT/ ); |
214 |
if ( !defined $type || $dirent =~ /\.(?:$type)$/) { |
215 |
push(@files, $path); |
216 |
} |
217 |
elsif ( $copy_other_files ) { |
218 |
copy($path, $new_path) |
219 |
or croak "Failed to copy $path to $new_path: $!"; |
220 |
} |
221 |
} |
222 |
elsif ( -d $path ) { |
223 |
(my $new_path = $path) =~ s/$tmpl_in_dir/$tmpl_out_dir/; |
224 |
$new_path = "$KOHA_ROOT/$new_path" unless ( $new_path =~ m/^$KOHA_ROOT/ ); |
225 |
if ( ! -d $new_path ) { |
226 |
mkdir($new_path) #, '0755' |
227 |
or croak "Failed to create " . $new_path ." directory: $!"; |
228 |
} |
229 |
my @sub_files = mirror_template_dir_structure_return_files($path, $type); |
230 |
push(@files, @sub_files) if ( scalar(@sub_files) ); |
231 |
} |
232 |
} |
233 |
} else { |
234 |
warn("Cannot open $dir: $! ... skipping"); |
235 |
} |
236 |
|
237 |
return @files; |
238 |
} |
239 |
- |