translator tool. see translator_doc.txt
[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'  => \$help);
22
23 help() if $help;
24
25 # utiliser glob() pour tous les fichiers d'un repertoire
26
27 my $action = shift or usage();
28 my %strhash = ();
29
30 # Checks for missing input and string list arguments
31
32 if( !defined(@in_files) || !defined($str_file) )
33 {
34         usage("You must at least specify input and string list filenames.");
35 }
36
37 # Type match defaults to *.tmpl if not specified
38 $type = "tmpl|inc" if !defined($type);
39
40 $filter = "./text-extract.pl -f" if !defined($filter);
41 # Input is not a file nor a directory
42 if( !(-d $in_files[0]) && !(-f $in_files[0]))
43 {
44         usage("Unknow input. Input must a file or a directory. (Symbolic links are not supported for the moment).");
45 }
46 elsif( -d $in_files[0] )
47 {
48         # input is a directory, generates list of files to process
49         $in_dir = $in_files[0];
50         $in_dir =~ s/\/$//; # strips the trailing / if any
51
52         print "Generating list of files to process...\n";
53         
54         @in_files = ();
55         @in_files = &listfiles(\@in_files, $in_dir, $type, $recursive);
56         
57         if(scalar(@in_files) == 0)
58         {
59                 warn "Nothing to process in $in_dir matching *.$type.";
60                 exit -1;
61         }
62 }
63
64 # Generates the global exclude regular expression
65 $exclude_regex =  "(".join("|", @excludes).")" if @excludes;
66
67 if( $action eq "create" )
68 {
69         # updates the list. As the list is empty, every entry will be added
70         %strhash = &update_strhash(\%strhash, \@in_files, $exclude_regex, $filter);
71         # saves the list to the file
72         write_strhash(\%strhash, $str_file, "\t");
73 }
74 elsif( $action eq "update" )
75 {
76         # restores the string list from file
77         %strhash = &restore_strhash(\%strhash, $str_file, $split_char);
78         # updates the list, adding new entries if any
79         %strhash = &update_strhash(\%strhash, \@in_files, $exclude_regex, $filter);
80         # saves the list to the file
81         write_strhash(\%strhash, $str_file, $split_char);
82 }
83 elsif( $action eq "install" )
84 {
85         if(!defined($out_dir))
86         {
87                 usage("You must specify an output directory when using the install method.");
88         }
89         
90         if( $in_dir eq $out_dir )
91         {
92                 warn "You must specify a different input and output directory.\n";
93                 exit -1;
94         }
95
96         # restores the string list from file
97         %strhash = &restore_strhash(\%strhash, $str_file, $split_char);
98         # creates the new tmpl file using the new translation
99         &install_strhash(\%strhash, \@in_files, $in_dir, $out_dir);
100 }
101 else
102 {
103         usage("Unknown action specified.");
104 }
105
106 exit 0;
107
108 ##########################################################
109 # Creates the new template files in the output directory #
110 ##########################################################
111
112 sub install_strhash
113 {
114         my($strhash, $in_files, $in_dir, $out_dir) = @_;
115
116         my $fh_in; my $fh_out; # handles for input and output files
117         my $tmp_dir; # temporary directory name (used to create destination dir)
118
119         $out_dir =~ s/\/$//; # chops the trailing / if any.
120
121         # Processes every entry found.
122         foreach my $file (@{$in_files})
123         {
124                 if( !open($fh_in, "< $file") )
125                 {
126                         warn "Can't open $file : $!\n";
127                         next;
128                 }
129
130                 # generates the name of the output file
131                 my $out_file = $file;
132
133                 if(!defined $in_dir)
134                 {
135                         # processing single files not an entire directory
136                         $out_file = "$out_dir/$file";
137                 }
138                 else
139                 {
140                         $out_file =~ s/^$in_dir/$out_dir/;
141                 }
142
143                 my $slash = rindex($out_file, "\/");
144                 $tmp_dir = substr($out_file, 0, $slash); #gets the directory where the file will be saved
145
146                 # the file doesn't exist
147                 if( !(-f $tmp_dir) && !(-l $tmp_dir) && !(-e $tmp_dir) )
148                 {
149                         if(!mkdir($tmp_dir,0775)) # creates with rwxrwxr-x permissions
150                         {
151                                 warn("Make directory $tmp_dir : $!");
152                                 close($fh_in);
153                                 exit(1);
154                         }
155                 }
156                 elsif((-f $tmp_dir) || (-l $tmp_dir))
157                 {
158                         warn("Unable to create directory $tmp_dir.\n A file or symbolic link with the same name already exists.");
159                         close($fh_in);
160                         exit(1);
161                 }
162                 
163                 # opens handle for output
164                 if( !open($fh_out, "> $out_file") )
165                 {
166                         warn "Can't write $out_file : $!\n";
167                         close($fh_in);
168                         next;
169                 }
170
171                 print "Generating $out_file...\n";
172
173                 while(my $line = <$fh_in>)
174                 {
175                         foreach my $text (sort  {length($b) <=> length($a)} keys %{$strhash})
176                         {
177                                 # Test if the key has been translated
178                                 if( %{$strhash}->{$text} != 1 )
179                                 {
180                                         # Does the line contains text that needs to be changed ?
181                                         if( $line =~ /$text/ && %{$strhash}->{$text} ne "IGNORE")
182                                         {
183                                                 # changing text
184                                                 my $subst = %{$strhash}->{$text};
185                                                 $line =~ s/(\W)$text(\W)/$1$subst$2/g;
186                                         }
187                                 }
188                         }
189
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
225                         $str =~ 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\n", $str, $split_char);
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 }