416a7a4379b6e2c6d9618eb29ef6f929c7575220
[koha.git] / misc / translator / tmpl_process.pl
1 #!/usr/bin/perl
2
3 use strict;
4 use Getopt::Long;
5
6 my (@in_files, $str_file, $split_char, $recursive, $type, $out_dir, $in_dir, @excludes, $filter);
7 my $help;
8 my $exclude_regex;
9
10 $split_char = ' ';
11
12 GetOptions(
13         'input|i=s'     => \@in_files,
14         'outputdir|o=s' => \$out_dir,
15         'str-file|s=s' => \$str_file,
16         'recursive|r' => \$recursive,
17         'filter=s' => \$filter,
18         'type=s' => \$type,
19         'exclude=s' => \@excludes,
20         'sep=s' => \$split_char,
21         'help'  => sub { help() },
22 ) || (usage(), exit(-1));
23
24 # utiliser glob() pour tous les fichiers d'un repertoire
25
26 my $action = shift or usage();
27 my %strhash = ();
28
29 # Checks for missing input and string list arguments
30
31 if( !@in_files || !defined($str_file) )
32 {
33         usage("You must at least specify input and string list filenames.");
34 }
35
36 # Type match defaults to *.tmpl if not specified
37 $type = "tmpl|inc" if !defined($type);
38
39 $filter = "./text-extract.pl -f" if !defined($filter);
40 # Input is not a file nor a directory
41 if( !(-d $in_files[0]) && !(-f $in_files[0]))
42 {
43         usage("Unknown input. Input must a file or a directory. (Symbolic links are not supported for the moment.)");
44 }
45 elsif( -d $in_files[0] )
46 {
47         # input is a directory, generates list of files to process
48         $in_dir = $in_files[0];
49         $in_dir =~ s/\/$//; # strips the trailing / if any
50
51         print "Generating list of files to process...\n";
52         
53         @in_files = ();
54         @in_files = &listfiles(\@in_files, $in_dir, $type, $recursive);
55
56         if(scalar(@in_files) == 0)
57         {
58                 warn "Nothing to process in $in_dir matching *.$type.";
59                 exit -1;
60         }
61 }
62
63 # Generates the global exclude regular expression
64 $exclude_regex =  "(".join("|", @excludes).")" if @excludes;
65
66 if( $action eq "create" )
67 {
68         # updates the list. As the list is empty, every entry will be added
69         %strhash = &update_strhash(\%strhash, \@in_files, $exclude_regex, $filter);
70         # saves the list to the file
71         write_strhash(\%strhash, $str_file, "\t");
72 }
73 elsif( $action eq "update" )
74 {
75         # restores the string list from file
76         %strhash = &restore_strhash(\%strhash, $str_file, $split_char);
77         # updates the list, adding new entries if any
78         %strhash = &update_strhash(\%strhash, \@in_files, $exclude_regex, $filter);
79         # saves the list to the file
80         write_strhash(\%strhash, $str_file, $split_char);
81 }
82 elsif( $action eq "install" )
83 {
84         if(!defined($out_dir))
85         {
86                 usage("You must specify an output directory when using the install method.");
87         }
88         
89         if( $in_dir eq $out_dir )
90         {
91                 warn "You must specify a different input and output directory.\n";
92                 exit -1;
93         }
94
95         # restores the string list from file
96         %strhash = &restore_strhash(\%strhash, $str_file, $split_char);
97         # creates the new tmpl file using the new translation
98         &install_strhash(\%strhash, \@in_files, $in_dir, $out_dir);
99 }
100 else
101 {
102         usage("Unknown action specified.");
103 }
104
105 exit 0;
106
107 ##########################################################
108 # Creates the new template files in the output directory #
109 ##########################################################
110
111 sub install_strhash
112 {
113         my($strhash, $in_files, $in_dir, $out_dir) = @_;
114
115         my $fh_in; my $fh_out; # handles for input and output files
116         my $tmp_dir; # temporary directory name (used to create destination dir)
117
118         $out_dir =~ s/\/$//; # chops the trailing / if any.
119
120         # Processes every entry found.
121         foreach my $file (@{$in_files})
122         {
123                 if( !open($fh_in, "< $file") )
124                 {
125                         warn "Can't open $file : $!\n";
126                         next;
127                 }
128
129                 # generates the name of the output file
130                 my $out_file = $file;
131
132                 if(!defined $in_dir)
133                 {
134                         # processing single files not an entire directory
135                         $out_file = "$out_dir/$file";
136                 }
137                 else
138                 {
139                         $out_file =~ s/^$in_dir/$out_dir/;
140                 }
141
142                 my $slash = rindex($out_file, "\/");
143                 $tmp_dir = substr($out_file, 0, $slash); #gets the directory where the file will be saved
144
145                 # the file doesn't exist
146                 if( !(-f $tmp_dir) && !(-l $tmp_dir) && !(-e $tmp_dir) )
147                 {
148                         if(!mkdir($tmp_dir,0775)) # creates with rwxrwxr-x permissions
149                         {
150                                 warn("Make directory $tmp_dir : $!");
151                                 close($fh_in);
152                                 exit(1);
153                         }
154                 }
155                 elsif((-f $tmp_dir) || (-l $tmp_dir))
156                 {
157                         warn("Unable to create directory $tmp_dir.\n A file or symbolic link with the same name already exists.");
158                         close($fh_in);
159                         exit(1);
160                 }
161                 
162                 # opens handle for output
163                 if( !open($fh_out, "> $out_file") )
164                 {
165                         warn "Can't write $out_file : $!\n";
166                         close($fh_in);
167                         next;
168                 }
169
170                 print "Generating $out_file...\n";
171
172                 while(my $line = <$fh_in>)
173                 {
174                         foreach my $text (sort  {length($b) <=> length($a)} keys %{$strhash})
175                         {
176                                 # Test if the key has been translated
177                                 if( %{$strhash}->{$text} != 1 )
178                                 {
179                                         # Does the line contains text that needs to be changed ?
180                                         if( $line =~ /$text/ && %{$strhash}->{$text} ne "IGNORE")
181                                         {
182                                                 # changing text
183                                                 my $subst = %{$strhash}->{$text};
184                                                 $line =~ s/(\W)$text(\W)/$1$subst$2/g;
185                                         }
186                                 }
187                         }
188                         $line =~ s/\<TMPL_(.*?)\>/\<\!-- TMPL_$1 --\>/g;
189                         $line =~ s/\<\/TMPL_(.*?)\>/\<\!-- \/TMPL_$1 --\>/g;
190                         # Writing the modified (or not) line to output
191                         printf($fh_out "%s", $line);
192                 }
193
194                 close($fh_in);
195                 close($fh_out);
196         }
197 }
198
199 ########################################################
200 # Updates the string list hash with the new components #
201 ########################################################
202
203 sub update_strhash
204 {
205         my($strhash, $in_files, $exclude, $filter)= @_;
206
207         my $fh;
208
209         # Processes every file entries
210         foreach my $in (@{$in_files})
211         {
212
213                 print "Processing $in...\n";
214
215                 # Creates a filehandle containing all the strings returned by
216                 # the plain text program extractor
217                 open($fh, "$filter $in |") or print "$filter $in : $!";
218                 next $in if !defined $fh;
219
220                 # Processes every string returned
221                 while(my $str = <$fh>)
222                 {
223                         $str =~ s/[\n\r\f]+$//; # chomps the trailing \n (or <cr><lf> if file was edited with Windows)
224                         $str =~ s/^\s+//; # remove trailing blanks, ':' or '*'
225                         $str =~ s/\s*\**:*\s*$//;
226
227                         # the line begins with letter(s) followed by optional words and/or spaces
228                         if($str =~ /^[ ]*[\w]+[ \w]*/)
229                         {
230                                 # the line is to be excluded ?
231                                 if( !(defined($exclude) && ($str =~ /$exclude/o) && $str>0) )
232                                 {
233                                         if( !defined(%{$strhash}->{$str}) )
234                                         {
235                                                 # the line is not already in the list so add it
236                                                 %{$strhash}->{$str}=1;
237                                         }
238                                 }
239                         }
240                 }
241
242                 close($fh);
243         }
244
245         return %{$strhash};
246 }
247
248 #####################################################
249 # Reads the input file and returns a generated hash #
250 #####################################################
251
252 sub restore_strhash
253 {
254         my($strhash, $str_file, $split_char) = @_;
255         
256         my $fh;
257         
258         open($fh, "< $str_file") or die "$str_file : $!";
259         
260         print "Restoring string list from $str_file...\n";
261         
262         while( my $line = <$fh> )
263         {
264                 chomp $line;
265
266                 # extracts the two fields
267                 my ($original, $translated) = split(/$split_char/, $line, 2);
268
269                 if($translated ne "*****")
270                 {
271                         # the key has been translated
272                         %{$strhash}->{$original} = $translated;
273                 }
274                 else
275                 {
276                         # the key exist but has no translation.
277                         %{$strhash}->{$original} = 1;
278                 }
279
280         }
281
282         close($fh);
283
284         return %{$strhash};
285 }
286
287 #########################################
288 # Writes the string hashtable to a file #
289 #########################################
290
291 sub write_strhash
292 {
293         my($strhash, $str_file, $split_char) = @_;
294
295         my $fh;
296
297         # Opens a handle for saving the list
298         open($fh, "> $str_file") or die "$str_file : $!";
299
300         print "Writing string list to $str_file...\n";
301
302         foreach my $str(sort {uc($a) cmp uc($b) || length($a) <=> length($b)} keys %{$strhash})
303         {
304                 if(%{$strhash}->{$str} != 1)
305                 {
306                         printf($fh "%s%s%s\n", $str, $split_char, %{$strhash}->{$str});
307                 }
308                 else
309                 {
310                         printf($fh "%s%s%s\n", $str, $split_char,"*****") unless ($str >0);
311                 }
312         }
313
314         close($fh);
315 }
316
317 ########################################################
318 # List the contents of dir matching the pattern *.type #
319 ########################################################
320
321 sub listfiles
322 {
323         my($in_files, $dir, $type, $recursive) = @_;
324
325         my $dir_h;
326 #       my @types = split(/ /,$type);
327         opendir($dir_h, $dir) or warn("Can't open $dir : $!\n");
328
329         my @tmp_list = grep(!/^\.\.?$/, readdir($dir_h));
330
331         closedir($dir_h);
332
333         foreach my $tmp_file (@tmp_list)
334         {
335
336                 if( $recursive && (-d "$dir/$tmp_file") ) # entry is a directory
337                 {
338                         @{$in_files} = listfiles($in_files, "$dir/$tmp_file", $type);
339                 }
340                 elsif( $tmp_file =~ /\.$type$/ )
341                 {
342                         push(@{$in_files}, "$dir/$tmp_file");
343                 }
344         }
345         return @{$in_files};
346 }
347
348 ######################################
349 # DEBUG ROUTINE                      #
350 # Prints the contents of a hashtable #
351 ######################################
352
353 sub print_strhash
354 {
355         my($strhash, $split_char) = @_;
356         
357         foreach my $str(sort keys %{$strhash})
358         {
359                 if(%{$strhash}->{$str} != 1)
360                 {
361                         printf("%s%s%s\n", $str, $split_char, %{$strhash}->{$str});
362                 }
363                 else
364                 {
365                         printf("%s%s\n", $str, $split_char);
366                 }
367         }
368 }       
369
370 #########################################
371 # Short help messsage printing function #
372 #########################################
373
374 sub usage
375 {
376         warn join(" ", @_)."\n" if @_;
377         warn <<EOF;
378
379 Usage : $0 method -i input.tmpl|/input/dir -s strlist.file
380         [-o /output/dir] [options]
381
382 where method can be :
383   * create : creates the string list from scratch using the input files.
384   * update : updates an existing string list, adding the new strings to
385              the list, leaving the others alone.
386   * install : creates the new .tmpl files using the string list config file
387               (--outputdir must be used to specify the output directory).
388
389 Use $0 --help for a complete listing of options.
390 EOF
391         exit(1);
392 }
393
394 ##############################################
395 # Long help message describing every options #
396 ##############################################
397
398 sub help
399 {
400         warn <<EOF;
401 Usage : $0 method [options]
402         
403 where method can be :
404   * create : creates the string list from scratch using the input files.
405   * update : updates an existing string list, adding the new strings to
406              the list, leaving the others alone.
407   * install : creates the new .tmpl files using the string list config file
408               (-o must be used to specify the output directory).
409
410 options can be :
411
412   -i or --input=
413      Specify the input to process. Input can be a file or a directory.
414      When input is a directory, files matching the --type option will be
415      processed.
416      When using files, the parameter can be repeated to process a list
417      of files.
418    
419   Example: $0 create -i foo.tmpl --input=bar.tmpl -s foobar.txt
420
421   -s or --str-file=
422      Specify the file where the different strings will be stored.
423
424   -o or --outputdir=
425      Specify the output directory to use when generating the translated
426      input files.
427
428   -r or --recursive
429      Use the recursive mode to process every entry in subdirectories.
430      Note: Symbolic links used to link directories are not supported.
431
432   --type=
433      Defines the type of files to match when input is a directory.
434      By default --type=tmpl
435
436   --exclude=regex
437      Use this option to exclude some entries extracted by the program.
438      This option can be repeated to exclude many types of strings.
439
440   Example: $0 create -i foo.tmpl -s foo.txt --exclude=^\[0-9\]+\$
441    will create a list from foo.tmpl called foo.txt where lines
442    composed of numbers only are excluded. Special characters need to
443    be escaped.
444
445   --filter=
446      Specify the program to use to extract plain text from files.
447      Default is str-extract which means str-extract must be in the path
448      in order to use it.
449
450   --sep=char
451      Use this option to specify the char to be used to separate entries
452      in the string list file.
453
454   --help
455      This help message.
456 EOF
457         exit(0);
458 }