source: main/trunk/greenstone2/bin/script/gti.pl@ 26547

Last change on this file since 26547 was 26547, checked in by ak19, 11 years ago

Reducing the list of GS3 interface files to be translated to the ones Kathy deemed important. Need to still create modules for GS3 so that the list of auxiliary GS3 interface files will be treated as a separate translation module.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 84.4 KB
Line 
1#!/usr/bin/perl -w
2
3###########################################################################
4#
5# gti.pl
6#
7# A component of the Greenstone digital library software
8# from the New Zealand Digital Library Project at the
9# University of Waikato, New Zealand.
10#
11# Copyright (C) 2005 New Zealand Digital Library Project
12#
13# This program is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation; either version 2 of the License, or
16# (at your option) any later version.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26#
27###########################################################################
28
29
30BEGIN {
31 die "GSDLHOME not set\n" unless defined $ENV{'GSDLHOME'};
32 unshift (@INC, "$ENV{'GSDLHOME'}/perllib");
33}
34
35
36use iso639;
37use strict;
38use util;
39
40my $gsdl_root_directory = "$ENV{'GSDLHOME'}";
41my $gti_log_file = &util::filename_cat($gsdl_root_directory, "etc", "gti.log");
42my $source_language_code = "en"; # This is non-negiotable
43
44my $gti_translation_files =
45[ # Greenstone macrofiles
46{ 'key' => "coredm",
47 'file_type' => "macrofile",
48 'source_file' => "macros/english.dm",
49 'target_file' => "macros/{bn:bengali;fa:farsi;gd:gaelic;id:indo;lv:latvian;pt-br:port-br;pt-pt:port-pt;zh-tr:chinese-trad;iso_639_1_target_language_name}.dm" },
50
51{ 'key' => "auxdm",
52 'file_type' => "macrofile",
53 'source_file' => "macros/english2.dm",
54 'target_file' => "macros/{bn:bengali;fa:farsi;gd:gaelic;id:indo;lv:latvian;pt-br:port-br;pt-pt:port-pt;zh-tr:chinese-trad;iso_639_1_target_language_name}2.dm" },
55
56{ 'key' => "paperspastdm",
57 'file_type' => "macrofile",
58 'source_file' => "macros/paperspast-english.dm",
59 'target_file' => "macros/paperspast-{bn:bengali;fa:farsi;gd:gaelic;id:indo;lv:latvian;pt-br:port-br;pt-pt:port-pt;zh-tr:chinese-trad;iso_639_1_target_language_name}.dm" },
60
61# GLI dictionary
62{ 'key' => "glidict",
63 'file_type' => "resource_bundle",
64 'source_file' => "gli/classes/dictionary.properties",
65 'target_file' => "gli/classes/dictionary_{target_language_code}.properties" },
66
67# GLI help
68{ 'key' => "glihelp",
69 'file_type' => "greenstone_xml",
70 'source_file' => "gli/help/en/help.xml",
71 'target_file' => "gli/help/{target_language_code}/help.xml" },
72
73# Greenstone Perl modules
74{ 'key' => "perlmodules",
75 'file_type' => "resource_bundle",
76 'source_file' => "perllib/strings.properties",
77 'target_file' => "perllib/strings_{target_language_code}.properties" },
78
79# Greenstone tutorial exercises
80# { 'key' => "tutorials",
81# 'file_type' => "greenstone_xml",
82# 'source_file' => "gsdl-documentation/tutorials/xml-source/tutorial_en.xml",
83# 'target_file' => "gsdl-documentation/tutorials/xml-source/tutorial_{target_language_code}.xml" },
84
85# new Greenstone.org
86{ 'key' => "greenorg",
87 'file_type' => "resource_bundle",
88 'source_file' => "greenstoneorg/website/classes/Gsc.properties",
89 'target_file' => "greenstoneorg/website/classes/Gsc_{target_language_code}.properties"
90}
91
92# { 'key' => "gs3interface",
93#'file_type' => "resource_bundle",
94#'source_file' => "greenstone3",
95#'target_file' => "greenstone3"
96#}
97];
98
99my @gs3_interface_files = ("AbstractBrowse", "AbstractGS2FieldSearch", "AbstractSearch", "Authentication", "CrossCollectionSearch", "GS2Construct", "GS2LuceneSearch", "interface_default", "interface_default2", "IViaSearch", "LuceneSearch", "MapRetrieve", "MapSearch", "metadata_names", "PhindPhraseBrowse", "SharedSoleneGS2FieldSearch");
100
101# Auxilliary GS3 interface files. This list is not used at present
102# Combine with above list if generating translation spreadsheet for all interface files
103my @gs3_aux_interface_files = ("GATEServices", "interface_basic", "interface_basic2", "interface_nzdl", "interface_gs2", "QBRWebServicesHelp", "Visualizer");
104
105# Not: i18n, log4j
106
107sub main
108{
109 # Get the command to process, and any arguments
110 my $gti_command = shift(@_);
111 my @gti_command_arguments = @_;
112 my $module = $_[1];
113
114 # for GS3, set gsdl_root_dir to GSDL3HOME
115 if($module eq "gs3interface"){
116 $gsdl_root_directory = (defined $ENV{'GSDL3HOME'}) ? $ENV{'GSDL3HOME'} : &util::filename_cat($ENV{'GSDL3SRCHOME'}, "web");
117 $gti_log_file = &util::filename_cat($gsdl_root_directory, "logs", "gti.log");
118 }
119
120 # Open the GTI log file for appending, or write to STDERR if that fails
121 if (!open(GTI_LOG, ">>$gti_log_file")) {
122 open(GTI_LOG, ">&STDERR");
123 }
124
125 # Log the command that launched this script
126 &log_message("Command: $0 @ARGV");
127
128 # Check that a command was supplied
129 if (!$gti_command) {
130 &throw_fatal_error("Missing command.");
131 }
132
133 # Process the command
134 if ($gti_command =~ /^get-all-chunks$/i) {
135 # Check that GS3 interface is the target
136 if ($module eq "gs3interface") {
137 print &get_all_chunks_for_gs3(@gti_command_arguments);
138 } else {
139 print &get_all_chunks(@gti_command_arguments);
140 }
141 }
142 elsif ($gti_command =~ /^get-first-n-chunks-requiring-work$/i) {
143 if ($module eq "gs3interface") {
144 print &get_first_n_chunks_requiring_work_for_gs3(@gti_command_arguments);
145 } else {
146 print &get_first_n_chunks_requiring_work(@gti_command_arguments);
147 }
148 }
149 elsif ($gti_command =~ /^get-uptodate-chunks$/i) {
150 if ($module eq "gs3interface") {
151 print &get_uptodate_chunks_for_gs3(@gti_command_arguments);
152 } else {
153 print &get_uptodate_chunks(@gti_command_arguments);
154 }
155 }
156 elsif ($gti_command =~ /^get-language-status$/i) {
157 print &get_language_status(@gti_command_arguments);
158 }
159 elsif ($gti_command =~ /^search-chunks$/i) {
160 print &search_chunks(@gti_command_arguments);
161 }
162 elsif ($gti_command =~ /^submit-translations$/i) {
163 # This command cannot produce any output since it reads input
164 &submit_translations(@gti_command_arguments);
165 }
166 elsif ($gti_command =~ /^create-glihelp-zip-file$/i) {
167 # This command cannot produce any output since it reads input
168 &create_glihelp_zip_file(@gti_command_arguments);
169 }
170 else {
171 # The command was not recognized
172 &throw_fatal_error("Unknown command \"$gti_command\".");
173 }
174}
175
176
177sub throw_fatal_error
178{
179 my $error_message = shift(@_);
180
181 # Write an XML error response
182 print "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
183 print "<GTIResponse>\n";
184 print " <GTIError time=\"" . time() . "\">" . $error_message . "</GTIError>\n";
185 print "</GTIResponse>\n";
186
187 # Log the error message, then die
188 &log_message("Error: $error_message");
189 die "\n";
190}
191
192
193sub log_message
194{
195 my $log_message = shift(@_);
196 print GTI_LOG time() . " -- " . $log_message . "\n";
197}
198
199
200sub get_all_chunks
201{
202 # The code of the target language (ensure it is lowercase)
203 my $target_language_code = lc(shift(@_));
204 # The key of the file to translate (ensure it is lowercase)
205 my $translation_file_key = lc(shift(@_));
206
207 # Check that the necessary arguments were supplied
208 if (!$target_language_code || !$translation_file_key) {
209 &throw_fatal_error("Missing command argument.");
210 }
211
212 # Get (and check) the translation configuration
213 my ($source_file, $target_file, $translation_file_type)
214 = &get_translation_configuration($target_language_code, $translation_file_key);
215
216 # Parse the source language and target language files
217 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
218 my @source_file_lines = &read_file_lines($source_file_path);
219 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
220
221 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
222 my @target_file_lines = &read_file_lines($target_file_path);
223 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
224
225 # Filter out any automatically translated chunks
226 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
227 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
228 delete $source_file_key_to_line_mapping{$chunk_key};
229 delete $target_file_key_to_line_mapping{$chunk_key};
230 }
231 }
232
233 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
234 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
235 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
236 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
237
238 my %source_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($source_file, \@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
239 my %target_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($target_file, \@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
240
241 my $xml_response = &create_xml_response_for_all_chunks($translation_file_key, $target_file, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
242
243 return $xml_response;
244}
245
246
247sub get_uptodate_chunks
248{
249 # The code of the target language (ensure it is lowercase)
250 my $target_language_code = lc(shift(@_));
251 # The key of the file to translate (ensure it is lowercase)
252 my $translation_file_key = lc(shift(@_));
253
254 # Check that the necessary arguments were supplied
255 if (!$target_language_code || !$translation_file_key) {
256 &throw_fatal_error("Missing command argument.");
257 }
258
259 # Get (and check) the translation configuration
260 my ($source_file, $target_file, $translation_file_type)
261 = &get_translation_configuration($target_language_code, $translation_file_key);
262
263 # Parse the source language and target language files
264 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
265 my @source_file_lines = &read_file_lines($source_file_path);
266 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
267
268 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
269 my @target_file_lines = &read_file_lines($target_file_path);
270 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
271
272 # Filter out any automatically translated chunks
273 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
274 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
275 delete $source_file_key_to_line_mapping{$chunk_key};
276 delete $target_file_key_to_line_mapping{$chunk_key};
277 }
278 }
279
280 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
281 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
282 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
283 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
284
285 my %source_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($source_file, \@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
286 my %target_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($target_file, \@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
287
288
289 # Chunks needing updating are those in the target file that have been more recently edited in the source file
290 # All others are uptodate (which implies that they have certainly been translated at some point and would not be empty)
291 my @uptodate_target_file_keys = ();
292 foreach my $chunk_key (keys(%source_file_key_to_last_update_date_mapping)) {
293 my $source_chunk_last_update_date = $source_file_key_to_last_update_date_mapping{$chunk_key};
294 my $target_chunk_last_update_date = $target_file_key_to_last_update_date_mapping{$chunk_key};
295
296 # print "key: $chunk_key\nsource date : $source_chunk_last_update_date\ntarget date : $target_chunk_last_update_date\nafter? ". &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date) . "\n\n";
297
298 if (defined($target_chunk_last_update_date) && !&is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
299 # &log_message("Chunk with key $chunk_key needs updating.");
300 push(@uptodate_target_file_keys, $chunk_key);
301 }
302 }
303
304 my $xml_response = &create_xml_response_for_uptodate_chunks($translation_file_key, $target_file, \@uptodate_target_file_keys, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
305
306 return $xml_response;
307}
308
309
310sub get_first_n_chunks_requiring_work
311{
312 # The code of the target language (ensure it is lowercase)
313 my $target_language_code = lc(shift(@_));
314 # The key of the file to translate (ensure it is lowercase)
315 my $translation_file_key = lc(shift(@_));
316 # The number of chunks to return (defaults to one if not specified)
317 my $num_chunks_to_return = shift(@_) || "1";
318
319 # Check that the necessary arguments were supplied
320 if (!$target_language_code || !$translation_file_key) {
321 &throw_fatal_error("Missing command argument.");
322 }
323
324 # Get (and check) the translation configuration
325 my ($source_file, $target_file, $translation_file_type)
326 = &get_translation_configuration($target_language_code, $translation_file_key);
327
328 # Parse the source language and target language files
329 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
330 my @source_file_lines = &read_file_lines($source_file_path);
331 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
332
333 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
334 my @target_file_lines = &read_file_lines($target_file_path);
335 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
336
337 # Filter out any automatically translated chunks
338 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
339 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
340 delete $source_file_key_to_line_mapping{$chunk_key};
341 delete $target_file_key_to_line_mapping{$chunk_key};
342 }
343 }
344
345 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
346 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
347 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
348 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
349
350 # Determine the target file chunks requiring translation
351 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
352 &log_message("Number of target chunks requiring translation: " . scalar(@target_file_keys_requiring_translation));
353
354 # Determine the target file chunks requiring updating
355 my %source_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($source_file, \@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
356 my %target_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($target_file, \@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
357 my @target_file_keys_requiring_updating = &determine_chunks_requiring_updating(\%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
358 &log_message("Number of target chunks requiring updating: " . scalar(@target_file_keys_requiring_updating));
359
360 my $xml_response = &create_xml_response_for_chunks_requiring_work($translation_file_key, $target_file, scalar(keys(%source_file_key_to_text_mapping)), \@target_file_keys_requiring_translation, \@target_file_keys_requiring_updating, $num_chunks_to_return, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
361
362 return $xml_response;
363}
364
365
366sub get_language_status
367{
368 # The code of the target language (ensure it is lowercase)
369 my $target_language_code = lc(shift(@_));
370
371 # Check that the necessary arguments were supplied
372 if (!$target_language_code) {
373 &throw_fatal_error("Missing command argument.");
374 }
375
376 # Form an XML response to the command
377 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
378 $xml_response .= "<GTIResponse>\n";
379 $xml_response .= " <LanguageStatus code=\"$target_language_code\">\n";
380
381 foreach my $translation_file (@$gti_translation_files) {
382 my ($num_source_chunks, $num_target_chunks, $num_chunks_requiring_translation, $num_chunks_requiring_updating) = 0;
383 my $target_file_name = "";
384
385 if ($translation_file->{'key'} eq "gs3interface") {
386 my (%source_file_key_to_text_mapping, %target_file_key_to_text_mapping, %source_file_key_to_last_update_date_mapping, %target_file_key_to_last_update_date_mapping ) = ();
387 &build_gs3_configuration($target_language_code, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping );
388
389 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
390 my @target_file_keys_requiring_updating = &determine_chunks_requiring_updating(\%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
391
392 $num_source_chunks = scalar(keys(%source_file_key_to_text_mapping));
393 $num_target_chunks = scalar(keys(%target_file_key_to_text_mapping));
394 $num_chunks_requiring_translation = scalar(@target_file_keys_requiring_translation);
395 $num_chunks_requiring_updating = scalar(@target_file_keys_requiring_updating);
396 }
397 else {
398 # Get (and check) the translation configuration
399 my ($source_file, $target_file, $translation_file_type) = &get_translation_configuration($target_language_code, $translation_file->{'key'});
400 $target_file_name = $target_file;
401
402 # Parse the source language and target language files
403 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
404 my @source_file_lines = &read_file_lines($source_file_path);
405 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
406
407 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
408 my @target_file_lines = &read_file_lines($target_file_path);
409 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
410
411 # Filter out any automatically translated chunks
412 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
413 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
414 delete $source_file_key_to_line_mapping{$chunk_key};
415 delete $target_file_key_to_line_mapping{$chunk_key};
416 }
417 }
418
419 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
420 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
421
422 # Determine the target file chunks requiring translation
423 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
424
425 # Determine the target file chunks requiring updating
426 my @target_file_keys_requiring_updating = ();
427 if (-e $target_file_path) {
428 my %source_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($source_file, \@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
429 my %target_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($target_file, \@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
430 @target_file_keys_requiring_updating = &determine_chunks_requiring_updating(\%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
431 }
432
433 $num_source_chunks = scalar(keys(%source_file_key_to_text_mapping));
434 $num_target_chunks = scalar(keys(%target_file_key_to_text_mapping));
435 $num_chunks_requiring_translation = scalar(@target_file_keys_requiring_translation);
436 $num_chunks_requiring_updating = scalar(@target_file_keys_requiring_updating);
437 }
438
439 &log_message("Status of " . $translation_file->{'key'});
440 &log_message("Number of source chunks: " . $num_source_chunks);
441 &log_message("Number of target chunks: " . $num_target_chunks);
442 &log_message("Number of target chunks requiring translation: " . $num_chunks_requiring_translation);
443 &log_message("Number of target chunks requiring updating: " . $num_chunks_requiring_updating);
444
445 $xml_response .= " <TranslationFile"
446 . " key=\"" . $translation_file->{'key'} . "\""
447 . " target_file_path=\"" . $target_file_name . "\""
448 . " num_chunks_translated=\"" . ($num_source_chunks - $num_chunks_requiring_translation) . "\""
449 . " num_chunks_requiring_translation=\"" . $num_chunks_requiring_translation . "\""
450 . " num_chunks_requiring_updating=\"" . $num_chunks_requiring_updating . "\"\/>\n";
451 }
452
453 $xml_response .= " </LanguageStatus>\n";
454
455 $xml_response .= "</GTIResponse>\n";
456 return $xml_response;
457}
458
459
460sub search_chunks
461{
462 # The code of the target language (ensure it is lowercase)
463 my $target_language_code = lc(shift(@_));
464 # The key of the file to translate (ensure it is lowercase)
465 my $translation_file_key = lc(shift(@_));
466 # The query string
467 my $query_string = join(' ', @_);
468
469 # Check that the necessary arguments were supplied
470 if (!$target_language_code || !$translation_file_key || !$query_string) {
471 &throw_fatal_error("Missing command argument.");
472 }
473
474 my ($source_file, $target_file, $translation_file_type) = ();
475 my %source_file_key_to_text_mapping = ();
476 my %target_file_key_to_text_mapping = ();
477
478
479 if ($translation_file_key ne "gs3interface") {
480 # Get (and check) the translation configuration
481 ($source_file, $target_file, $translation_file_type) = &get_translation_configuration($target_language_code, $translation_file_key);
482
483 # Parse the source language and target language files
484 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
485 my @source_file_lines = &read_file_lines($source_file_path);
486 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
487
488 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
489 my @target_file_lines = &read_file_lines($target_file_path);
490 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
491
492 # Filter out any automatically translated chunks
493 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
494 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
495 delete $source_file_key_to_line_mapping{$chunk_key};
496 delete $target_file_key_to_line_mapping{$chunk_key};
497 }
498 }
499
500 %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
501 %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
502 }
503 else {
504 # Not needed in this case
505 my (%source_file_key_to_gti_command_mapping, %target_file_key_to_gti_command_mapping) = ();
506 &build_gs3_configuration($target_language_code, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping,
507 \%source_file_key_to_gti_command_mapping, \%target_file_key_to_gti_command_mapping);
508 }
509
510 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
511 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
512
513 # Determine the target file chunks matching the query
514 my @target_file_keys_matching_query = ();
515 foreach my $chunk_key (keys(%target_file_key_to_text_mapping)) {
516 my $target_file_text = $target_file_key_to_text_mapping{$chunk_key};
517 if ($target_file_text =~ /$query_string/i) {
518 # &log_message("Chunk with key $chunk_key matches query.");
519 push(@target_file_keys_matching_query, $chunk_key);
520 }
521 }
522
523 # Form an XML response to the command
524 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
525 $xml_response .= "<GTIResponse>\n";
526
527 $xml_response .= " <ChunksMatchingQuery size=\"" . scalar(@target_file_keys_matching_query) . "\">\n";
528 foreach my $chunk_key (@target_file_keys_matching_query) {
529 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
530
531 $xml_response .= " <Chunk key=\"$chunk_key\">\n";
532 $xml_response .= " <TargetFileText>$target_file_chunk_text</TargetFileText>\n";
533 $xml_response .= " </Chunk>\n";
534 }
535 $xml_response .= " </ChunksMatchingQuery>\n";
536
537 $xml_response .= "</GTIResponse>\n";
538 return $xml_response;
539}
540
541
542sub submit_translations
543{
544 # The code of the target language (ensure it is lowercase)
545 my $target_language_code = lc(shift(@_));
546 # The key of the file to translate (ensure it is lowercase)
547 my $translation_file_key = lc(shift(@_));
548 # The username of the translation submitter
549 my $submitter_username = shift(@_);
550 # Whether to submit a target chunk even if it hasn't changed
551 my $force_submission_flag = shift(@_);
552
553 # Check that the necessary arguments were supplied
554 if (!$target_language_code || !$translation_file_key || !$submitter_username) {
555 &log_message("Fatal error (but cannot be thrown): Missing command argument.");
556 die "\n";
557 }
558
559 my %source_file_key_to_text_mapping = ();
560 my %source_file_key_to_gti_comment_mapping = ();
561 my %target_file_key_to_text_mapping = ();
562 my %target_file_key_to_gti_comment_mapping = ();
563
564 my (@source_file_lines, @target_file_lines) = ();
565 my ($source_file, $target_file, $translation_file_type);
566
567
568 if ($translation_file_key ne "gs3interface") {
569 # Get (and check) the translation configuration
570 ($source_file, $target_file, $translation_file_type)
571 = &get_translation_configuration($target_language_code, $translation_file_key);
572
573 # Parse the source language and target language files
574 @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
575 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
576 %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
577 %source_file_key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
578
579 @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
580 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
581 %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
582 %target_file_key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
583 }
584 else {
585 &build_gs3_configuration($target_language_code, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping,
586 \%source_file_key_to_gti_comment_mapping, \%target_file_key_to_gti_comment_mapping);
587 }
588 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
589 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
590
591 # Submission date
592 my $day = (localtime)[3];
593 my $month = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[(localtime)[4]];
594 my $year = (localtime)[5] + 1900;
595 my $submission_date = "$day-$month-$year";
596
597 open(SUBMISSION, "-");
598 my @submission_lines = <SUBMISSION>;
599 close(SUBMISSION);
600
601 # Remove any nasty carriage returns
602 # &log_message("Submission:");
603 foreach my $submission_line (@submission_lines) {
604 $submission_line =~ s/\r$//;
605 #&log_message(" $submission_line");
606 }
607
608 my %source_file_key_to_submission_mapping = ();
609 my %target_file_key_to_submission_mapping = ();
610 for (my $i = 0; $i < scalar(@submission_lines); $i++) {
611 # Read source file part of submission
612 if ($submission_lines[$i] =~ /^\<SourceFileText key=\"(.+)\"\>/) {
613 my $chunk_key = $1;
614
615 # Read the source file text
616 my $source_file_chunk_text = "";
617 $i++;
618 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/SourceFileText\>/) {
619 $source_file_chunk_text .= $submission_lines[$i];
620 $i++;
621 }
622 $source_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
623 $source_file_chunk_text = &unmake_text_xml_safe($source_file_chunk_text);
624
625 #&log_message("Source file key: $chunk_key");
626 #&log_message("Source file text: $source_file_chunk_text");
627 $source_file_key_to_submission_mapping{$chunk_key} = $source_file_chunk_text;
628 }
629
630 # Read target file part of submission
631 if ($submission_lines[$i] =~ /^\<TargetFileText key=\"(.+)\"\>/) {
632 my $chunk_key = $1;
633
634 # Read the target file text
635 my $target_file_chunk_text = "";
636 $i++;
637 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/TargetFileText\>/) {
638 $target_file_chunk_text .= $submission_lines[$i];
639 $i++;
640 }
641 $target_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
642 $target_file_chunk_text = &unmake_text_xml_safe($target_file_chunk_text);
643
644 #&log_message("Target file key: $chunk_key");
645 #&log_message("Target file text: $target_file_chunk_text");
646 $target_file_key_to_submission_mapping{$chunk_key} = $target_file_chunk_text;
647 }
648 }
649
650 # -----------------------------------------
651 # Validate the translation submissions
652 # -----------------------------------------
653
654 # Check that the translations are valid
655 foreach my $chunk_key (keys(%source_file_key_to_submission_mapping)) {
656 # Make sure the submitted chunk still exists in the source file
657 if (!defined($source_file_key_to_text_mapping{$chunk_key})) {
658 &log_message("Warning: Source chunk $chunk_key no longer exists (ignoring submission).");
659 delete $source_file_key_to_submission_mapping{$chunk_key};
660 delete $target_file_key_to_submission_mapping{$chunk_key};
661 next;
662 }
663
664 # Make sure the submitted source chunk matches the source file chunk
665 if ($source_file_key_to_submission_mapping{$chunk_key} ne &unmake_text_xml_safe($source_file_key_to_text_mapping{$chunk_key})) {
666 &log_message("Warning: Source chunk $chunk_key has changed (ignoring submission).");
667 &log_message("Submission source: $source_file_key_to_submission_mapping{$chunk_key}");
668 &log_message(" Source text: $source_file_key_to_text_mapping{$chunk_key}");
669 delete $source_file_key_to_submission_mapping{$chunk_key};
670 delete $target_file_key_to_submission_mapping{$chunk_key};
671 next;
672 }
673 }
674
675 # Apply the submitted translations
676 foreach my $chunk_key (keys(%target_file_key_to_submission_mapping)) {
677 # Only apply the submission if it is a change, unless -force_submission has been specified
678 if ($force_submission_flag || !defined($target_file_key_to_text_mapping{$chunk_key}) || $target_file_key_to_submission_mapping{$chunk_key} ne $target_file_key_to_text_mapping{$chunk_key}) {
679 $target_file_key_to_text_mapping{$chunk_key} = $target_file_key_to_submission_mapping{$chunk_key};
680 $target_file_key_to_gti_comment_mapping{$chunk_key} = "Updated $submission_date by $submitter_username";
681 }
682 }
683
684 if ($translation_file_key ne "gs3interface") {
685 eval "&write_translated_${translation_file_type}(\$source_file, \\\@source_file_lines, \\\%source_file_key_to_text_mapping, \$target_file, \\\@target_file_lines, \\\%target_file_key_to_text_mapping, \\\%target_file_key_to_gti_comment_mapping, \$target_language_code)";
686 } else {
687 eval "&write_translated_gs3interface(\\\%source_file_key_to_text_mapping, \\\%target_file_key_to_text_mapping, \\\%target_file_key_to_gti_comment_mapping, \$target_language_code)";
688 }
689}
690
691
692sub create_glihelp_zip_file
693{
694 my $target_language_code = shift(@_);
695 my $translation_file_key = "glihelp";
696
697 &log_message("Creating GLI Help zip file for $target_language_code");
698
699 my ($source_file, $target_file, $translation_file_type) = &get_translation_data_for($target_language_code, $translation_file_key);
700
701 my $classpath = &util::filename_cat($gsdl_root_directory, "gti-lib");
702 if ( ! -e $classpath) {
703 &throw_fatal_error("$classpath doesn't exist! Need the files in this directory (ApplyXLST and its related files) to create the zip file for GLI Help");
704 }
705
706 my $gli_help_directory = &util::filename_cat($gsdl_root_directory, "gli");
707 $gli_help_directory = &util::filename_cat($gli_help_directory, "help");
708
709 my $gen_many_html_xsl_filepath = &util::filename_cat($gli_help_directory, "gen-many-html.xsl");
710 if ( ! -e $gen_many_html_xsl_filepath) {
711 &throw_fatal_error("$gen_many_html_xsl_filepath doesn't exist! Need this file to create the zip file for GLI Help");
712 }
713
714 my $gen_index_xml_xsl_filepath = &util::filename_cat($gli_help_directory, "gen-index-xml.xsl");
715 my $split_script_filepath = &util::filename_cat($gli_help_directory, "splithelpdocument.pl");
716
717 my $target_file_directory = &util::filename_cat($gli_help_directory, $target_language_code);
718 $target_file_directory = $target_file_directory."/";
719
720 my $target_filepath = &util::filename_cat($gsdl_root_directory, $target_file);
721
722 my $perl_exec = &util::get_perl_exec();
723 my $java_exec = "java";
724 if(defined($ENV{'JAVA_HOME'}) && $ENV{'JAVA_HOME'} ne ""){
725 $java_exec = &util::filename_cat($ENV{'JAVA_HOME'}, "bin", "java");
726 }
727
728 my $cmd = "$java_exec -cp $classpath:$classpath/xalan.jar ApplyXSLT $target_language_code $gen_many_html_xsl_filepath $target_filepath | \"$perl_exec\" -S $split_script_filepath $target_file_directory";
729 my $response = `$cmd`;
730
731 $cmd = "$java_exec -cp $classpath:$classpath/xalan.jar ApplyXSLT $target_language_code $gen_index_xml_xsl_filepath $target_filepath > " . $target_file_directory . "help_index.xml"; # 2>/dev/null";
732 $response = `$cmd`;
733
734 my $zip_file_path = "/greenstone/custom/gti/" . $target_language_code . "_GLIHelp.zip";
735 $cmd = "zip -rj $zip_file_path $target_file_directory -i \*.htm \*.xml";
736 $response = `$cmd`;
737}
738
739
740sub get_translation_configuration
741{
742 # Get the code of the target language
743 my $target_language_code = shift(@_);
744 # Get the key of the file to translate
745 my $translation_file_key = shift(@_);
746
747 # Read the translation data from the gti.cfg file
748 my ($source_file, $target_file, $translation_file_type) =
749 &get_translation_data_for($target_language_code, $translation_file_key);
750
751 # Check that the file to translate is defined in the gti.cfg file
752 if (!$source_file || !$target_file || !$translation_file_type) {
753 &throw_fatal_error("Missing or incomplete specification for translation file \"$translation_file_key\" in gti.pl.");
754 }
755
756 # Check that the source file exists
757 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
758 if (!-e $source_file_path) {
759 &throw_fatal_error("Source file $source_file_path does not exist.");
760 }
761
762 # Check that the source file is up to date
763 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
764 # unless ($translation_file_is_not_in_cvs) {
765 #my $source_file_cvs_status = `cd $gsdl_root_directory; cvs -d $anonymous_cvs_root update $source_file 2>/dev/null`;
766 my $source_file_cvs_status = `cd $gsdl_root_directory; svn status $source_file 2>/dev/null`;
767 if ($source_file_cvs_status =~ /^C /) {
768 &throw_fatal_error("Source file $source_file_path conflicts with the repository.");
769 }
770 if ($source_file_cvs_status =~ /^M /) {
771 &throw_fatal_error("Source file $source_file_path contains uncommitted changes.");
772 }
773 # }
774
775 return ($source_file, $target_file, $translation_file_type);
776}
777
778
779sub get_translation_data_for
780{
781 my ($target_language_code, $translation_file_key) = @_;
782
783 foreach my $translation_file (@$gti_translation_files) {
784 # If this isn't the correct translation file, move onto the next one
785 next if ($translation_file_key ne $translation_file->{'key'});
786
787 # Resolve the target language file
788 my $target_language_file = $translation_file->{'target_file'};
789 if ($target_language_file =~ /(\{.+\;.+\})/) {
790 my $unresolved_target_language_file_part = $1;
791
792 # Check for a special case for the target language code
793 if ($unresolved_target_language_file_part =~ /(\{|\;)$target_language_code:([^\;]+)(\;|\})/) {
794 my $resolved_target_language_file_part = $2;
795 $target_language_file =~ s/$unresolved_target_language_file_part/$resolved_target_language_file_part/;
796 }
797 # Otherwise use the last part as the default value
798 else {
799 my ($default_target_language_file_part) = $unresolved_target_language_file_part =~ /([^\;]+)\}/;
800 $target_language_file =~ s/$unresolved_target_language_file_part/\{$default_target_language_file_part\}/;
801 }
802 }
803
804 # Resolve instances of {iso_639_1_target_language_name}
805 my $iso_639_1_target_language_name = $iso639::fromiso639{$target_language_code};
806 $iso_639_1_target_language_name =~ tr/A-Z/a-z/ if $iso_639_1_target_language_name;
807 $target_language_file =~ s/\{iso_639_1_target_language_name\}/$iso_639_1_target_language_name/g;
808
809 # Resolve instances of {target_language_code}
810 $target_language_file =~ s/\{target_language_code\}/$target_language_code/g;
811
812 return ($translation_file->{'source_file'}, $target_language_file, $translation_file->{'file_type'});
813}
814
815return ();
816}
817
818
819sub read_file_lines
820{
821 my ($file_path) = @_;
822
823 if (!open(FILE_IN, "<$file_path")) {
824 &log_message("Note: Could not open file $file_path.");
825 return ();
826 }
827 my @file_lines = <FILE_IN>;
828 close(FILE_IN);
829
830 return @file_lines;
831}
832
833
834sub build_key_to_line_mapping
835{
836 my ($file_lines, $translation_file_type) = @_;
837 eval "return &build_key_to_line_mapping_for_${translation_file_type}(\@\$file_lines)";
838}
839
840
841sub build_key_to_text_mapping
842{
843 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
844
845 my %key_to_text_mapping = ();
846 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
847 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
848 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
849
850 my $chunk_text = @$file_lines[$chunk_starting_line];
851 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
852 $chunk_text .= @$file_lines[$l];
853 }
854
855 # Map from chunk key to text
856 eval "\$key_to_text_mapping{\${chunk_key}} = &import_chunk_from_${translation_file_type}(\$chunk_text)";
857 }
858
859 return %key_to_text_mapping;
860}
861
862
863sub build_key_to_last_update_date_mapping
864{
865 my ($file, $file_lines, $key_to_line_mapping, $translation_file_type) = @_;
866
867 # If the files aren't in CVS then we can't tell anything about what needs updating
868 # return () if ($translation_file_is_not_in_cvs);
869
870 # Build a mapping from key to CVS date
871 # Need to be careful with this mapping because the chunk keys won't necessarily all be valid
872 my %key_to_cvs_date_mapping = &build_key_to_cvs_date_mapping($file, $translation_file_type);
873
874 # Build a mapping from key to comment date
875 my %key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping($file_lines, $key_to_line_mapping, $translation_file_type);
876
877 # Build a mapping from key to last update date (the latter of the CVS date and comment date)
878 my %key_to_last_update_date_mapping = ();
879 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
880 # Use the CVS date as a starting point
881 my $chunk_cvs_date = $key_to_cvs_date_mapping{$chunk_key};
882 $key_to_last_update_date_mapping{$chunk_key} = $chunk_cvs_date;
883
884 # If a comment date exists and it is after the CVS date, use that instead
885 # need to convert the comment date format to SVN format
886 my $chunk_gti_comment = $key_to_gti_comment_mapping{$chunk_key};
887 if (defined($chunk_gti_comment) && $chunk_gti_comment =~ /(\d?\d-\D\D\D-\d\d\d\d)/) {
888 my $chunk_comment_date = $1;
889 if ((!defined($chunk_cvs_date) || &is_date_after($chunk_comment_date, $chunk_cvs_date))) {
890 $key_to_last_update_date_mapping{$chunk_key} = $chunk_comment_date;
891 }
892 }
893 }
894
895 return %key_to_last_update_date_mapping;
896}
897
898
899sub build_key_to_cvs_date_mapping
900{
901 my ($filename, $translation_file_type) = @_;
902
903 # Use SVN to annotate each line of the file with the date it was last edited
904 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
905 my $cvs_annotated_file = `cd $gsdl_root_directory; svn annotate -v --force $filename 2>/dev/null`;
906
907 my @cvs_annotated_file_lines = split(/\n/, $cvs_annotated_file);
908
909 my @cvs_annotated_file_lines_date = ();
910 foreach my $cvs_annotated_file_line (@cvs_annotated_file_lines) {
911 # Extract the date from the SVN annotation at the front
912 # svn format : 2007-07-16
913 $cvs_annotated_file_line =~ s/^\s+\S+\s+\S+\s(\S+)//;
914
915 push(@cvs_annotated_file_lines_date, $1);
916
917 # trim extra date information in svn annotation format
918 # 15:42:49 +1200 (Wed, 21 Jun 2006)
919 $cvs_annotated_file_line =~ s/^\s+\S+\s\S+\s\((.+?)\)\s//;
920 }
921
922 # Build a key to line mapping for the CVS annotated file, for matching the chunk key to the CVS date
923 my %key_to_line_mapping = &build_key_to_line_mapping(\@cvs_annotated_file_lines, $translation_file_type);
924
925 my %key_to_cvs_date_mapping = ();
926 foreach my $chunk_key (keys(%key_to_line_mapping)) {
927 my $chunk_starting_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[0];
928 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[1];
929
930 # Find the date this chunk was last edited, from the CVS annotation
931 my $chunk_date = $cvs_annotated_file_lines_date[$chunk_starting_line];
932 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
933 if (&is_date_after($cvs_annotated_file_lines_date[$l], $chunk_date)) {
934 # This part of the chunk has been updated more recently
935 $chunk_date = $cvs_annotated_file_lines_date[$l];
936
937 }
938 }
939
940 # Map from chunk key to CVS date
941 $key_to_cvs_date_mapping{$chunk_key} = $chunk_date;
942 }
943
944 return %key_to_cvs_date_mapping;
945}
946
947
948sub build_key_to_gti_comment_mapping
949{
950 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
951
952 my %key_to_gti_comment_mapping = ();
953 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
954 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
955 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
956
957 my $chunk_text = @$file_lines[$chunk_starting_line];
958 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
959 $chunk_text .= @$file_lines[$l];
960 }
961
962 # Map from chunk key to GTI comment
963 my $chunk_gti_comment;
964 eval "\$chunk_gti_comment = &get_${translation_file_type}_chunk_gti_comment(\$chunk_text)";
965 $key_to_gti_comment_mapping{$chunk_key} = $chunk_gti_comment if (defined($chunk_gti_comment));
966 }
967
968 return %key_to_gti_comment_mapping;
969}
970
971
972sub determine_chunks_requiring_translation
973{
974 my $source_file_key_to_text_mapping = shift(@_);
975 my $target_file_key_to_text_mapping = shift(@_);
976
977 # Chunks needing translation are those in the source file with no translation in the target file
978 my @target_file_keys_requiring_translation = ();
979 foreach my $chunk_key (keys(%$source_file_key_to_text_mapping)) {
980 if ($source_file_key_to_text_mapping->{$chunk_key} && !$target_file_key_to_text_mapping->{$chunk_key}) {
981 # &log_message("Chunk with key $chunk_key needs translating.");
982 push(@target_file_keys_requiring_translation, $chunk_key);
983 }
984 }
985
986 return @target_file_keys_requiring_translation;
987}
988
989
990sub determine_chunks_requiring_updating
991{
992 my $source_file_key_to_last_update_date_mapping = shift(@_);
993 my $target_file_key_to_last_update_date_mapping = shift(@_);
994
995 # Chunks needing updating are those in the target file that have been more recently edited in the source file
996 my @target_file_keys_requiring_updating = ();
997 foreach my $chunk_key (keys(%$source_file_key_to_last_update_date_mapping)) {
998 my $source_chunk_last_update_date = $source_file_key_to_last_update_date_mapping->{$chunk_key};
999 my $target_chunk_last_update_date = $target_file_key_to_last_update_date_mapping->{$chunk_key};
1000
1001 # print "key: $chunk_key\nsource date : $source_chunk_last_update_date\ntarget date : $target_chunk_last_update_date\nafter? ". &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date) . "\n\n";
1002
1003 if (defined($target_chunk_last_update_date) && &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
1004 # &log_message("Chunk with key $chunk_key needs updating.");
1005 push(@target_file_keys_requiring_updating, $chunk_key);
1006 }
1007 }
1008
1009 return @target_file_keys_requiring_updating;
1010}
1011
1012
1013sub is_chunk_automatically_translated
1014{
1015 my ($chunk_key, $translation_file_type) = @_;
1016 eval "return &is_${translation_file_type}_chunk_automatically_translated(\$chunk_key)";
1017}
1018
1019
1020sub make_text_xml_safe
1021{
1022 my $text = shift(@_);
1023 $text =~ s/\&/\&amp\;/g;
1024 $text =~ s/\&amp\;lt\;/\&amp\;amp\;lt\;/g;
1025 $text =~ s/\&amp\;gt\;/\&amp\;amp\;gt\;/g;
1026 $text =~ s/\&amp\;rarr\;/\&amp\;amp\;rarr\;/g;
1027 $text =~ s/\&amp\;mdash\;/\&amp\;amp\;mdash\;/g;
1028 $text =~ s/</\&lt\;/g;
1029 $text =~ s/>/\&gt\;/g;
1030 return $text;
1031}
1032
1033
1034sub unmake_text_xml_safe
1035{
1036 my $text = shift(@_);
1037 $text =~ s/\&lt\;/</g;
1038 $text =~ s/\&gt\;/>/g;
1039 $text =~ s/\&amp\;/\&/g;
1040 return $text;
1041}
1042
1043
1044# Returns 1 if $date1 is after $date2, 0 otherwise
1045sub is_date_after_cvs
1046{
1047 my ($date1, $date2) = @_;
1048 my %months = ("Jan", 1, "Feb", 2, "Mar", 3, "Apr", 4, "May", 5, "Jun", 6,
1049 "Jul", 7, "Aug", 8, "Sep", 9, "Oct", 10, "Nov", 11, "Dec", 12);
1050
1051 if(!defined $date1) {
1052 return 1;
1053 }
1054
1055 my @date1parts = split(/-/, $date1);
1056 my @date2parts = split(/-/, $date2);
1057
1058 # Compare year - nasty because we have rolled over into a new century
1059 my $year1 = $date1parts[2];
1060 if ($year1 < 80) {
1061 $year1 += 2000;
1062 }
1063 my $year2 = $date2parts[2];
1064 if ($year2 < 80) {
1065 $year2 += 2000;
1066 }
1067
1068 # Compare year
1069 if ($year1 > $year2) {
1070 return 1;
1071 }
1072 elsif ($year1 == $year2) {
1073 # Year is the same, so compare month
1074 if ($months{$date1parts[1]} > $months{$date2parts[1]}) {
1075 return 1;
1076 }
1077 elsif ($months{$date1parts[1]} == $months{$date2parts[1]}) {
1078 # Month is the same, so compare day
1079 if ($date1parts[0] > $date2parts[0]) {
1080 return 1;
1081 }
1082 }
1083 }
1084
1085 return 0;
1086}
1087
1088sub is_date_after
1089{
1090 my ($date1, $date2) = @_;
1091
1092 if(!defined $date1) {
1093 return 1;
1094 }
1095 if(!defined $date2) {
1096 return 0;
1097 }
1098
1099 # 16-Aug-2006
1100 if($date1=~ /(\d+?)-(\S\S\S)-(\d\d\d\d)/){
1101 my %months = ("Jan", "01", "Feb", "02", "Mar", "03", "Apr", "04", "May", "05", "Jun", "06",
1102 "Jul", "07", "Aug", "08", "Sep", "09", "Oct", "10", "Nov", "11", "Dec", "12");
1103 $date1=$3 . "-" . $months{$2} . "-" . $1;
1104 # print "** converted date1: $date1\n";
1105 }
1106 if($date2=~ /(\d+?)-(\S\S\S)-(\d\d\d\d)/){
1107 my %months = ("Jan", "01", "Feb", "02", "Mar", "03", "Apr", "04", "May", "05", "Jun", "06",
1108 "Jul", "07", "Aug", "08", "Sep", "09", "Oct", "10", "Nov", "11", "Dec", "12");
1109 $date2=$3 . "-" . $months{$2} . "-" . $1;
1110 # print "** converted date2: $date2\n";
1111 }
1112
1113
1114 # 2006-08-16
1115 my @date1parts = split(/-/, $date1);
1116 my @date2parts = split(/-/, $date2);
1117
1118 # Compare year
1119 if ($date1parts[0] > $date2parts[0]) {
1120 return 1;
1121 }
1122 elsif ($date1parts[0] == $date2parts[0]) {
1123 # Year is the same, so compare month
1124 if ($date1parts[1] > $date2parts[1]) {
1125 return 1;
1126 }
1127 elsif ($date1parts[1] == $date2parts[1]) {
1128 # Month is the same, so compare day
1129 if ($date1parts[2] > $date2parts[2]) {
1130 return 1;
1131 }
1132 }
1133 }
1134
1135 return 0;
1136}
1137
1138
1139sub create_xml_response_for_chunks_requiring_work
1140{
1141 my ($translation_file_key, $target_file, $total_num_chunks, $target_files_keys_requiring_translation, $target_files_keys_requiring_updating, $num_chunks_to_return, $source_files_key_to_text_mapping, $target_files_key_to_text_mapping, $source_files_key_to_last_update_date_mapping, $target_files_key_to_last_update_date_mapping) = @_;
1142
1143 # Form an XML response to the command
1144 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1145 $xml_response .= "<GTIResponse>\n";
1146 $xml_response .= " <TranslationFile"
1147 . " key=\"" . $translation_file_key . "\""
1148 . " target_file_path=\"" . $target_file . "\""
1149 . " num_chunks_translated=\"" . ($total_num_chunks - scalar(@$target_files_keys_requiring_translation)) . "\""
1150 . " num_chunks_requiring_translation=\"" . scalar(@$target_files_keys_requiring_translation) . "\""
1151 . " num_chunks_requiring_updating=\"" . scalar(@$target_files_keys_requiring_updating) . "\"\/>\n";
1152
1153 # Do chunks requiring translation first
1154 if ($num_chunks_to_return > scalar(@$target_files_keys_requiring_translation)) {
1155 $xml_response .= " <ChunksRequiringTranslation size=\"" . scalar(@$target_files_keys_requiring_translation) . "\">\n";
1156 }
1157 else {
1158 $xml_response .= " <ChunksRequiringTranslation size=\"" . $num_chunks_to_return . "\">\n";
1159 }
1160
1161 my @sorted_chunk_keys = sort (@$target_files_keys_requiring_translation);
1162 foreach my $chunk_key (@sorted_chunk_keys) {
1163 last if ($num_chunks_to_return == 0);
1164
1165 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1166 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1167
1168 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1169 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1170 $xml_response .= " <TargetFileText></TargetFileText>\n";
1171 $xml_response .= " </Chunk>\n";
1172
1173 $num_chunks_to_return--;
1174 }
1175
1176 $xml_response .= " </ChunksRequiringTranslation>\n";
1177
1178 # Then do chunks requiring updating
1179 if ($num_chunks_to_return > scalar(@$target_files_keys_requiring_updating)) {
1180 $xml_response .= " <ChunksRequiringUpdating size=\"" . scalar(@$target_files_keys_requiring_updating) . "\">\n";
1181 }
1182 else {
1183 $xml_response .= " <ChunksRequiringUpdating size=\"" . $num_chunks_to_return . "\">\n";
1184 }
1185
1186 # foreach my $chunk_key (@target_file_keys_requiring_updating) {
1187 @sorted_chunk_keys = sort (@$target_files_keys_requiring_updating);
1188 foreach my $chunk_key (@sorted_chunk_keys) {
1189 last if ($num_chunks_to_return == 0);
1190
1191 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1192 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1193 my $target_file_chunk_date = $target_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1194 my $target_file_chunk_text = &make_text_xml_safe($target_files_key_to_text_mapping->{$chunk_key});
1195
1196 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1197 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1198 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1199 $xml_response .= " </Chunk>\n";
1200
1201 $num_chunks_to_return--;
1202 }
1203
1204 $xml_response .= " </ChunksRequiringUpdating>\n";
1205
1206 $xml_response .= "</GTIResponse>\n";
1207
1208 return $xml_response;
1209}
1210
1211sub create_xml_response_for_uptodate_chunks
1212{
1213 my ($translation_file_key, $target_file, $uptodate_target_files_keys, $source_files_key_to_text_mapping, $target_files_key_to_text_mapping, $source_files_key_to_last_update_date_mapping, $target_files_key_to_last_update_date_mapping) = @_;
1214
1215 # Form an XML response to the command
1216 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1217 $xml_response .= "<GTIResponse>\n";
1218 $xml_response .= " <TranslationFile"
1219 . " key=\"" . $translation_file_key . "\""
1220 . " target_file_path=\"" . $target_file . "\""
1221 . " num_chunks_uptodate=\"" . scalar(@$uptodate_target_files_keys) . "\"\/>\n";
1222
1223
1224 # Then do chunks requiring updating
1225 $xml_response .= " <UptodateChunks size=\"" . scalar(@$uptodate_target_files_keys) . "\">\n";
1226
1227
1228 # foreach my $chunk_key (@uptodate_target_file_keys) {
1229 my @sorted_chunk_keys = sort (@$uptodate_target_files_keys);
1230 foreach my $chunk_key (@sorted_chunk_keys) {
1231
1232 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1233 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1234 my $target_file_chunk_date = $target_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1235 my $target_file_chunk_text = &make_text_xml_safe($target_files_key_to_text_mapping->{$chunk_key});
1236
1237 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1238 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1239 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1240 $xml_response .= " </Chunk>\n";
1241
1242 }
1243
1244 $xml_response .= " </UptodateChunks>\n";
1245
1246 $xml_response .= "</GTIResponse>\n";
1247
1248 return $xml_response;
1249}
1250
1251sub create_xml_response_for_all_chunks
1252{
1253 my ($translation_file_key, $target_file, $source_file_key_to_text_mapping, $target_file_key_to_text_mapping, $source_file_key_to_last_update_date_mapping, $target_file_key_to_last_update_date_mapping) = @_;
1254
1255 # Form an XML response to the command
1256 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1257 $xml_response .= "<GTIResponse>\n";
1258 $xml_response .= " <TranslationFile"
1259 . " key=\"" . $translation_file_key . "\""
1260 . " target_file_path=\"" . $target_file . "\"\/>\n";
1261
1262 # Do all the chunks
1263 $xml_response .= " <Chunks size=\"" . scalar(keys(%$source_file_key_to_text_mapping)) . "\">\n";
1264
1265 my @sorted_chunk_keys = sort (keys(%$source_file_key_to_text_mapping));
1266 foreach my $chunk_key (@sorted_chunk_keys) {
1267 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping->{$chunk_key} || "";
1268 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping->{$chunk_key});
1269
1270 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1271 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1272 if (defined($target_file_key_to_text_mapping->{$chunk_key})) {
1273 my $target_file_chunk_date = $target_file_key_to_last_update_date_mapping->{$chunk_key} || "";
1274 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping->{$chunk_key});
1275 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1276 }
1277 else {
1278 $xml_response .= " <TargetFileText></TargetFileText>\n";
1279 }
1280
1281 $xml_response .= " </Chunk>\n";
1282 }
1283 $xml_response .= " </Chunks>\n";
1284
1285 $xml_response .= "</GTIResponse>\n";
1286 return $xml_response;
1287}
1288
1289
1290
1291# ==========================================================================================
1292# MACROFILE FUNCTIONS
1293
1294sub build_key_to_line_mapping_for_macrofile
1295{
1296 my (@file_lines) = @_;
1297
1298 my $macro_package;
1299 my %chunk_key_to_line_mapping = ();
1300 # Process the contents of the file, line by line
1301 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1302 my $line = $file_lines[$i];
1303 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1304
1305 # Check if a new package is being defined
1306 if ($line =~ m/^package\s+(.+)/) {
1307 $macro_package = $1;
1308 }
1309
1310 # Line contains a macro name
1311 elsif ($line =~ m/^(_\w+_)/) {
1312 my $macro_key = $1;
1313 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
1314
1315 # While there is still text of the macro to go...
1316 my $startline = $i;
1317 while ($line !~ /\}$/) {
1318 $i++;
1319 if ($i == scalar(@file_lines)) {
1320 &throw_fatal_error("Could not find end of macro $macro_key.");
1321 }
1322 $line = $file_lines[$i];
1323 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
1324 }
1325
1326 # The chunk key consists of the package name and the macro key
1327 my $chunk_key = $macro_package . "." . $macro_key;
1328 # Map from chunk key to line
1329 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1330 }
1331
1332 # Icon: line in format ## "image text" ## image_type ## macro_name ##
1333 elsif ($line =~ m/^\#\# .* \#\# .* \#\# (.*) \#\#/) {
1334 # The chunk key consists of package name and macro key
1335 my $chunk_key = $macro_package . "." . $1;
1336 # Map from chunk key to line
1337 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
1338}
1339}
1340
1341return %chunk_key_to_line_mapping;
1342}
1343
1344
1345sub import_chunk_from_macrofile
1346{
1347 my ($chunk_text) = @_;
1348
1349 # Is this an icon macro??
1350 if ($chunk_text =~ /^\#\# (.*)/) {
1351 # Extract image macro text
1352 $chunk_text =~ /^\#\#\s+([^\#]+)\s+\#\#/;
1353 $chunk_text = $1;
1354
1355 # Remove enclosing quotes
1356 $chunk_text =~ s/^\"//;
1357 $chunk_text =~ s/\"$//;
1358}
1359
1360# No, so it must be a text macro
1361else {
1362 # Remove macro key
1363 $chunk_text =~ s/^_([^_]+)_(\s*)//;
1364
1365 # Remove language specifier
1366 $chunk_text =~ s/^\[l=.*\](\s*)//;
1367
1368 # Remove braces enclosing text
1369 $chunk_text =~ s/^{(\s*)((.|\n)*)}(\s*)(\#.+\s*)?/$2/;
1370}
1371
1372return $chunk_text;
1373}
1374
1375
1376sub get_macrofile_chunk_gti_comment
1377{
1378 my ($chunk_text) = @_;
1379
1380 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1381 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1382 return $1;
1383}
1384
1385return undef;
1386}
1387
1388
1389sub is_macrofile_chunk_automatically_translated
1390{
1391 my ($chunk_key) = @_;
1392
1393 # The _httpiconX_, _widthX_ and _heightX_ image macros are automatically translated
1394 if ($chunk_key =~ /\._(httpicon|width|height)/) {
1395 return 1;
1396 }
1397
1398 return 0;
1399}
1400
1401
1402# Use the source file to generate a target file that is formatted the same
1403sub write_translated_macrofile
1404{
1405 my $source_file = shift(@_); # Not used
1406 my @source_file_lines = @{shift(@_)};
1407 my $source_file_key_to_text_mapping = shift(@_);
1408 my $target_file = shift(@_);
1409 my @target_file_lines = @{shift(@_)};
1410 my $target_file_key_to_text_mapping = shift(@_);
1411 my $target_file_key_to_gti_comment_mapping = shift(@_);
1412 my $target_language_code = shift(@_);
1413
1414 # Build a mapping from source file line to chunk key
1415 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@source_file_lines);
1416 my %source_file_line_to_key_mapping = ();
1417 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1418 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1419 }
1420 my @source_file_line_keys = (sort sort_by_line (keys(%source_file_line_to_key_mapping)));
1421 my $source_file_line_number = 0;
1422
1423 # Build a mapping from target file line to chunk key
1424 my %target_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@target_file_lines);
1425 my %target_file_line_to_key_mapping = ();
1426 foreach my $chunk_key (keys(%target_file_key_to_line_mapping)) {
1427 $target_file_line_to_key_mapping{$target_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1428 }
1429 my @target_file_line_keys = (sort sort_by_line (keys(%target_file_line_to_key_mapping)));
1430
1431 # Write the new target file
1432 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1433 if (!open(TARGET_FILE, ">$target_file_path")) {
1434 &throw_fatal_error("Could not write target file $target_file_path.");
1435 }
1436
1437 # Use the header from the target file, to keep language and author information
1438 if (scalar(@target_file_line_keys) > 0) {
1439 my $target_file_line_number = 0;
1440 my $target_file_chunk_starting_line_number = (split(/-/, $target_file_line_keys[0]))[0];
1441 while ($target_file_line_number < $target_file_chunk_starting_line_number) {
1442 my $target_file_line = $target_file_lines[$target_file_line_number];
1443 last if ($target_file_line =~ /^\# -- Missing translation: /); # We don't want to get into the macros
1444 print TARGET_FILE $target_file_line;
1445 $target_file_line_number++;
1446 }
1447
1448 $source_file_line_number = (split(/-/, $source_file_line_keys[0]))[0];
1449 }
1450
1451 # Model the new target file on the source file, with the target file translations
1452 foreach my $line_key (@source_file_line_keys) {
1453 # Fill in the gaps before this chunk starts
1454 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1455 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1456 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1457 print TARGET_FILE $source_file_lines[$source_file_line_number];
1458 $source_file_line_number++;
1459 }
1460 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1461
1462 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1463 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1464 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1465
1466 my $macrofile_key = $chunk_key;
1467 $macrofile_key =~ s/^(.+?)\.//;
1468
1469 # If no translation exists for this chunk, show this, and move on
1470 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1471 print TARGET_FILE "# -- Missing translation: $macrofile_key\n";
1472 next;
1473 }
1474
1475 # Grab the source chunk text
1476 my $source_file_chunk = $source_file_lines[$source_file_chunk_starting_line_number];
1477 for (my $l = ($source_file_chunk_starting_line_number + 1); $l <= $source_file_chunk_finishing_line_number; $l++) {
1478 $source_file_chunk .= $source_file_lines[$l];
1479 }
1480
1481 # Is this an icon macro??
1482 if ($source_file_chunk =~ /^\#\# (.*)/) {
1483 # Escape any newline and question mark characters so the source text is replaced correctly
1484 $source_file_chunk_text =~ s/\\/\\\\/g;
1485 $source_file_chunk_text =~ s/\?/\\\?/g;
1486
1487 # Build the new target chunk from the source chunk
1488 my $target_file_chunk = $source_file_chunk;
1489 $target_file_chunk =~ s/$source_file_chunk_text/$target_file_chunk_text/;
1490 $target_file_chunk =~ s/(\s)*$//;
1491 print TARGET_FILE "$target_file_chunk";
1492 }
1493
1494 # No, it is just a normal text macro
1495 else {
1496 print TARGET_FILE "$macrofile_key [l=$target_language_code] {$target_file_chunk_text}";
1497 }
1498
1499 # Add the "updated" comment, if one exists
1500 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1501 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1502 }
1503 print TARGET_FILE "\n";
1504}
1505
1506close(TARGET_FILE);
1507}
1508
1509
1510sub sort_by_line
1511{
1512 return ((split(/-/, $a))[0] <=> (split(/-/, $b))[0]);
1513}
1514
1515
1516# ==========================================================================================
1517# RESOURCE BUNDLE FUNCTIONS
1518
1519sub build_key_to_line_mapping_for_resource_bundle
1520{
1521 my (@file_lines) = @_;
1522
1523 my %chunk_key_to_line_mapping = ();
1524 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1525 my $line = $file_lines[$i];
1526 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1527
1528 # Line contains a dictionary string
1529 if ($line =~ /^(\S+?)[:|=](.*)$/) {
1530 my $chunk_key = $1;
1531
1532 # Map from chunk key to line
1533 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
1534 }
1535 }
1536
1537 return %chunk_key_to_line_mapping;
1538}
1539
1540
1541sub import_chunk_from_resource_bundle
1542{
1543 my ($chunk_text) = @_;
1544
1545 # Simple: just remove string key
1546 $chunk_text =~ s/^(\S+?)[:|=](\s*)//;
1547 $chunk_text =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1548 $chunk_text =~ s/(\s*)\#\s+Updated\s+(\d?\d-\D\D\D-\d\d\d\d.*)\s*$//i;
1549
1550 return $chunk_text;
1551}
1552
1553
1554sub get_resource_bundle_chunk_gti_comment
1555{
1556 my ($chunk_text) = @_;
1557
1558 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1559 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1560 return $1;
1561}
1562
1563return undef;
1564}
1565
1566
1567sub is_resource_bundle_chunk_automatically_translated
1568{
1569 # No resource bundle chunks are automatically translated
1570 return 0;
1571}
1572
1573
1574sub write_translated_resource_bundle
1575{
1576 my $source_file = shift(@_); # Not used
1577 my @source_file_lines = @{shift(@_)};
1578 my $source_file_key_to_text_mapping = shift(@_);
1579 my $target_file = shift(@_);
1580 my @target_file_lines = @{shift(@_)}; # Not used
1581 my $target_file_key_to_text_mapping = shift(@_);
1582 my $target_file_key_to_gti_comment_mapping = shift(@_);
1583 my $target_language_code = shift(@_); # Not used
1584
1585 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1586 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
1587 my %source_file_line_to_key_mapping = ();
1588 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1589 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1590 }
1591
1592 # Write the new target file
1593 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1594 if (!open(TARGET_FILE, ">$target_file_path")) {
1595 &throw_fatal_error("Could not write target file $target_file_path.");
1596 }
1597
1598 # Model the new target file on the source file, with the target file translations
1599 my $source_file_line_number = 0;
1600 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1601 # Fill in the gaps before this chunk starts
1602 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1603 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1604 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1605 print TARGET_FILE $source_file_lines[$source_file_line_number];
1606 $source_file_line_number++;
1607 }
1608 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1609
1610 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1611 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1612 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1613
1614 # If no translation exists for this chunk, show this, and move on
1615 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1616 print TARGET_FILE "# -- Missing translation: $chunk_key\n";
1617 next;
1618 }
1619
1620 print TARGET_FILE "$chunk_key:$target_file_chunk_text";
1621 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1622 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1623 }
1624 print TARGET_FILE "\n";
1625 }
1626
1627 close(TARGET_FILE);
1628}
1629
1630
1631# ==========================================================================================
1632# GREENSTONE XML FUNCTIONS
1633
1634sub build_key_to_line_mapping_for_greenstone_xml
1635{
1636 my (@file_lines) = @_;
1637
1638 my %chunk_key_to_line_mapping = ();
1639 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1640 my $line = $file_lines[$i];
1641 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1642
1643 # Line contains a string to translate
1644 if ($line =~ /^\s*<Text id=\"(.*?)\">/) {
1645 my $chunk_key = $1;
1646 $line =~ s/\s*$//; # Remove any nasty whitespace
1647 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1648
1649 # While there is still text of the string to go...
1650 my $startline = $i;
1651 while ($line !~ /<\/Text>$/) {
1652 $i++;
1653 if ($i == scalar(@file_lines)) {
1654 &throw_fatal_error("Could not find end of string $chunk_key.");
1655 }
1656 $line = $file_lines[$i];
1657 $line =~ s/\s*$//; # Remove any nasty whitespace
1658 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1659 }
1660
1661 # Map from chunk key to line
1662 if (!defined($chunk_key_to_line_mapping{$chunk_key})) {
1663 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1664 }
1665 else {
1666 &throw_fatal_error("Duplicate key $chunk_key.");
1667 }
1668 }
1669 }
1670
1671 return %chunk_key_to_line_mapping;
1672}
1673
1674
1675sub import_chunk_from_greenstone_xml
1676{
1677 my ($chunk_text) = @_;
1678
1679 # Simple: just remove the Text tags
1680 $chunk_text =~ s/^\s*<Text id=\"(.*?)\">(\s*)//;
1681 $chunk_text =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1682 $chunk_text =~ s/<\/Text>$//;
1683
1684 return $chunk_text;
1685}
1686
1687
1688sub get_greenstone_xml_chunk_gti_comment
1689{
1690 my ($chunk_text) = @_;
1691
1692 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1693 if ($chunk_text =~ /<Updated date=\"(\d?\d-\D\D\D-\d\d\d\d.*)\"\/>$/i) {
1694 return $1;
1695 }
1696
1697 return undef;
1698}
1699
1700
1701sub is_greenstone_xml_chunk_automatically_translated
1702{
1703 # No greenstone XML chunks are automatically translated
1704 return 0;
1705}
1706
1707
1708sub write_translated_greenstone_xml
1709{
1710 my $source_file = shift(@_); # Not used
1711 my @source_file_lines = @{shift(@_)};
1712 my $source_file_key_to_text_mapping = shift(@_);
1713 my $target_file = shift(@_);
1714 my @target_file_lines = @{shift(@_)}; # Not used
1715 my $target_file_key_to_text_mapping = shift(@_);
1716 my $target_file_key_to_gti_comment_mapping = shift(@_);
1717 my $target_language_code = shift(@_); # Not used
1718
1719 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1720 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_greenstone_xml(@source_file_lines);
1721 my %source_file_line_to_key_mapping = ();
1722 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1723 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1724 }
1725
1726 # Write the new target file
1727 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1728 if (!open(TARGET_FILE, ">$target_file_path")) {
1729 &throw_fatal_error("Could not write target file $target_file_path.");
1730 }
1731
1732 # Model the new target file on the source file, with the target file translations
1733 my $source_file_line_number = 0;
1734 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1735 # Fill in the gaps before this chunk starts
1736 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1737 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1738 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1739 print TARGET_FILE $source_file_lines[$source_file_line_number];
1740 $source_file_line_number++;
1741 }
1742 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1743
1744 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1745 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1746 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1747 $target_file_chunk_text =~ s/(\n)*$//g;
1748
1749 # If no translation exists for this chunk, show this, and move on
1750 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1751 print TARGET_FILE "<!-- Missing translation: $chunk_key -->\n";
1752 next;
1753 }
1754
1755 print TARGET_FILE "<Text id=\"$chunk_key\">$target_file_chunk_text</Text>";
1756 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1757 my $chunk_gti_comment = $target_file_key_to_gti_comment_mapping->{$chunk_key};
1758 $chunk_gti_comment =~ s/^Updated //;
1759 print TARGET_FILE "<Updated date=\"" . $chunk_gti_comment . "\"\/>";
1760 }
1761 print TARGET_FILE "\n";
1762 }
1763
1764 # Fill in the end of the file
1765 while ($source_file_line_number < scalar(@source_file_lines)) {
1766 print TARGET_FILE $source_file_lines[$source_file_line_number];
1767 $source_file_line_number++;
1768 }
1769
1770 close(TARGET_FILE);
1771}
1772
1773
1774# ==========================================================================================
1775# GREENSTONE3 FUNCTIONS
1776
1777sub get_all_chunks_for_gs3
1778{
1779 # The code of the target language (ensure it is lowercase)
1780 my $target_language_code = lc(shift(@_));
1781 my $translation_file_key = lc(shift(@_));
1782
1783 # Check that the necessary arguments were supplied
1784 if (!$target_language_code) {
1785 &throw_fatal_error("Missing command argument.");
1786 }
1787
1788 # Get (and check) the translation configuration
1789 # my ($source_file_dir, $target_file, $translation_file_type) = &get_translation_configuration($target_language_code, $translation_file_key);
1790
1791 my %source_files_key_to_text_mapping = ();
1792 my %target_files_key_to_text_mapping = ();
1793 my %source_files_key_to_last_update_date_mapping = ();
1794 my %target_files_key_to_last_update_date_mapping = ();
1795
1796 &build_gs3_configuration($target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping, \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1797
1798 &log_message("Total number of source chunks: " . scalar(keys(%source_files_key_to_text_mapping)));
1799 &log_message("Total number of target chunks: " . scalar(keys(%target_files_key_to_text_mapping)));
1800
1801 my $xml_response = &create_xml_response_for_all_chunks($translation_file_key, "", \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping, \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1802 return $xml_response;
1803}
1804
1805
1806sub get_first_n_chunks_requiring_work_for_gs3
1807{
1808 # The code of the target language (ensure it is lowercase)
1809 my $target_language_code = lc(shift(@_));
1810 # The key of the file to translate (ensure it is lowercase)
1811 my $translation_file_key = lc(shift(@_));
1812 # The number of chunks to return (defaults to one if not specified)
1813 my $num_chunks_to_return = shift(@_) || "1";
1814
1815 # Check that the necessary arguments were supplied
1816 if (!$target_language_code || !$translation_file_key) {
1817 &throw_fatal_error("Missing command argument.");
1818 }
1819
1820 my %source_files_key_to_text_mapping = ();
1821 my %target_files_key_to_text_mapping = ();
1822 my %source_files_key_to_last_update_date_mapping = ();
1823 my %target_files_key_to_last_update_date_mapping = ();
1824
1825 &build_gs3_configuration($target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
1826 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1827
1828 # Determine the target file chunks requiring translation
1829 my @target_files_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping);
1830 # Determine the target file chunks requiring updating
1831 my @target_files_keys_requiring_updating = &determine_chunks_requiring_updating(\%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1832 &log_message("Total number of target chunks requiring translation: " . scalar(@target_files_keys_requiring_translation));
1833 &log_message("Total number of target chunks requiring updating: " . scalar(@target_files_keys_requiring_updating));
1834
1835 my $xml_response = &create_xml_response_for_chunks_requiring_work($translation_file_key, "", scalar(keys(%source_files_key_to_text_mapping)),
1836 \@target_files_keys_requiring_translation, \@target_files_keys_requiring_updating,
1837 $num_chunks_to_return, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
1838 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1839
1840 return $xml_response;
1841}
1842
1843sub get_uptodate_chunks_for_gs3
1844{
1845 # The code of the target language (ensure it is lowercase)
1846 my $target_language_code = lc(shift(@_));
1847 # The key of the file to translate (ensure it is lowercase)
1848 my $translation_file_key = lc(shift(@_));
1849 # The number of chunks to return (defaults to one if not specified)
1850 my $num_chunks_to_return = shift(@_) || "1";
1851
1852 # Check that the necessary arguments were supplied
1853 if (!$target_language_code || !$translation_file_key) {
1854 &throw_fatal_error("Missing command argument.");
1855 }
1856
1857 my %source_files_key_to_text_mapping = ();
1858 my %target_files_key_to_text_mapping = ();
1859 my %source_files_key_to_last_update_date_mapping = ();
1860 my %target_files_key_to_last_update_date_mapping = ();
1861
1862 &build_gs3_configuration($target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
1863 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1864
1865
1866 # Chunks needing updating are those in the target file that have been more recently edited in the source file
1867 # All others are uptodate (which implies that they have certainly been translated at some point and would not be empty)
1868 my @uptodate_target_file_keys = ();
1869 foreach my $chunk_key (keys(%source_files_key_to_last_update_date_mapping)) {
1870 my $source_chunk_last_update_date = $source_files_key_to_last_update_date_mapping{$chunk_key};
1871 my $target_chunk_last_update_date = $target_files_key_to_last_update_date_mapping{$chunk_key};
1872
1873 # print "key: $chunk_key\nsource date : $source_chunk_last_update_date\ntarget date : $target_chunk_last_update_date\nafter? ". &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date) . "\n\n";
1874
1875 if (defined($target_chunk_last_update_date) && !&is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
1876 # &log_message("Chunk with key $chunk_key needs updating.");
1877 push(@uptodate_target_file_keys, $chunk_key);
1878 }
1879 }
1880
1881 my $xml_response = &create_xml_response_for_uptodate_chunks($translation_file_key, "", \@uptodate_target_file_keys, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping, \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1882
1883 return $xml_response;
1884}
1885
1886
1887sub build_gs3_configuration
1888{
1889 my ($target_language_code, $source_files_key_to_text_mapping, $target_files_key_to_text_mapping,
1890 $source_files_key_to_gti_comment_mapping, $target_files_key_to_gti_comment_mapping) = @_;
1891
1892 my $source_file_directory = &util::filename_cat("WEB-INF","classes"); # my $source_file_directory = "greenstone3";
1893 my $translation_file_type = "resource_bundle";
1894
1895 foreach my $interface_file_key (@gs3_interface_files) {
1896
1897 &log_message("Greenstone 3 interface file: " . $interface_file_key);
1898
1899 # Parse the source language and target language files
1900 my $source_file = &util::filename_cat($source_file_directory, $interface_file_key.".properties");
1901 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
1902 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
1903 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
1904 my %source_file_key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
1905
1906 my $target_file = &util::filename_cat($source_file_directory, $interface_file_key."_".$target_language_code.".properties");
1907 my @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
1908 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
1909 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
1910 my %target_file_key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
1911
1912
1913 # Filter out any automatically translated chunks
1914 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1915 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
1916 delete $source_file_key_to_line_mapping{$chunk_key};
1917 delete $target_file_key_to_line_mapping{$chunk_key};
1918 }
1919 }
1920
1921 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
1922 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
1923
1924 foreach my $chunk_key (keys(%source_file_key_to_text_mapping)) {
1925 my $global_chunk_key = "$interface_file_key.$chunk_key";
1926 $source_files_key_to_text_mapping->{$global_chunk_key} = $source_file_key_to_text_mapping{$chunk_key};
1927 $source_files_key_to_gti_comment_mapping->{$global_chunk_key} = $source_file_key_to_gti_comment_mapping{$chunk_key};
1928
1929 if (defined $target_file_key_to_text_mapping{$chunk_key}) {
1930 $target_files_key_to_text_mapping->{$global_chunk_key} = $target_file_key_to_text_mapping{$chunk_key};
1931 $target_files_key_to_gti_comment_mapping->{$global_chunk_key} = $target_file_key_to_gti_comment_mapping{$chunk_key};
1932 }
1933 }
1934 }
1935}
1936
1937
1938sub write_translated_gs3interface
1939{
1940 my $source_file_key_to_text_mapping = shift(@_);
1941 my $target_file_key_to_text_mapping = shift(@_);
1942 my $target_file_key_to_gti_comment_mapping = shift(@_);
1943 my $target_language_code = shift(@_);
1944
1945 my @sorted_chunk_keys = sort (keys(%$source_file_key_to_text_mapping));
1946
1947 my %translated_interface_file_keys = ();
1948 foreach my $chunk_key (keys(%$target_file_key_to_text_mapping)) {
1949 $chunk_key =~ /^([^\.]+)?\.(.*)$/;
1950 if (!defined $translated_interface_file_keys{$1}) {
1951 &log_message("Updated interface file: " . $1);
1952 $translated_interface_file_keys{$1}="";
1953 }
1954 }
1955 &log_message("Updated interface files: " . scalar(keys(%translated_interface_file_keys)));
1956
1957 my $source_file_directory = "greenstone3";
1958
1959 foreach my $interface_file_key (keys(%translated_interface_file_keys)) {
1960
1961 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1962 my $source_file = &util::filename_cat($source_file_directory, "$interface_file_key.properties");
1963 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
1964 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
1965 my %source_file_line_to_key_mapping = ();
1966 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1967 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1968 }
1969
1970 # Write the new target file
1971 my $target_file = &util::filename_cat($source_file_directory, $interface_file_key . "_" . $target_language_code . ".properties");
1972 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1973 if (!open(TARGET_FILE, ">$target_file_path")) {
1974 &throw_fatal_error("Could not write target file $target_file_path.");
1975 }
1976
1977 # Model the new target file on the source file, with the target file translations
1978 my $source_file_line_number = 0;
1979 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1980 # Fill in the gaps before this chunk starts
1981 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1982 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1983 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1984 print TARGET_FILE $source_file_lines[$source_file_line_number];
1985 $source_file_line_number++;
1986 }
1987 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1988
1989 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1990 my $global_chunk_key = "$interface_file_key.$chunk_key";
1991 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$global_chunk_key};
1992 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$global_chunk_key} || "";
1993
1994 # If no translation exists for this chunk, show this, and move on
1995 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1996 print TARGET_FILE "# -- Missing translation: $chunk_key\n";
1997 next;
1998 }
1999
2000 print TARGET_FILE "$chunk_key:$target_file_chunk_text";
2001 if ($target_file_key_to_gti_comment_mapping->{$global_chunk_key}) {
2002 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$global_chunk_key};
2003 }
2004 print TARGET_FILE "\n";
2005 }
2006
2007 close(TARGET_FILE);
2008 }
2009}
2010
2011&main(@ARGV);
Note: See TracBrowser for help on using the repository browser.