source: gsdl/trunk/bin/script/gti.pl@ 14119

Last change on this file since 14119 was 13948, checked in by nzdl, 17 years ago

Last updatation lost the get-all-chunks command and code. don't know why.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 55.7 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
40
41my $anonymous_cvs_root = ":pserver:cvs_anon\@cvs.scms.waikato.ac.nz:2402/usr/local/global-cvs/gsdl-src";
42my $gsdl_root_directory = "$ENV{'GSDLHOME'}";
43my $gti_log_file = &util::filename_cat($gsdl_root_directory, "etc", "gti.log");
44my $source_language_code = "en"; # This is non-negiotable
45
46my $gti_translation_files =
47 [ # Greenstone macrofiles
48 { 'key' => "coredm",
49 'file_type' => "macrofile",
50 'source_file' => "macros/english.dm",
51 '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" },
52 { 'key' => "auxdm",
53 'file_type' => "macrofile",
54 'source_file' => "macros/english2.dm",
55 '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" },
56
57 # GLI dictionary
58 { 'key' => "glidict",
59 'file_type' => "resource_bundle",
60 'source_file' => "gli/classes/dictionary.properties",
61 'target_file' => "gli/classes/dictionary_{target_language_code}.properties" },
62
63 # GLI help
64 { 'key' => "glihelp",
65 'file_type' => "greenstone_xml",
66 'source_file' => "gli/help/en/help.xml",
67 'target_file' => "gli/help/{target_language_code}/help.xml" },
68
69 # Greenstone Perl modules
70 { 'key' => "perlmodules",
71 'file_type' => "resource_bundle",
72 'source_file' => "perllib/strings.properties",
73 'target_file' => "perllib/strings_{target_language_code}.properties" },
74
75 # Greenstone tutorial exercises
76 # { 'key' => "tutorials",
77 # 'file_type' => "greenstone_xml",
78 # 'source_file' => "gsdl-documentation/tutorials/xml-source/tutorial_en.xml",
79 # 'target_file' => "gsdl-documentation/tutorials/xml-source/tutorial_{target_language_code}.xml" },
80
81 # Greenstone.org
82 { 'key' => "greenorg",
83 'file_type' => "macrofile",
84 'source_file' => "greenorg/macros/english.dm",
85 'target_file' => "greenorg/macros/{iso_639_1_target_language_name}.dm" }
86 ];
87
88
89sub main
90{
91 # Get the command to process, and any arguments
92 my $gti_command = shift(@_);
93 my @gti_command_arguments = @_;
94
95 # Open the GTI log file for appending, or write to STDERR if that fails
96 if (!open(GTI_LOG, ">>$gti_log_file")) {
97 open(GTI_LOG, ">&STDERR");
98 }
99
100 # Log the command that launched this script
101 &log_message("Command: $0 @ARGV");
102
103 # Check that a command was supplied
104 if (!$gti_command) {
105 &throw_fatal_error("Missing command.");
106 }
107
108 # Process the command
109 if ($gti_command =~ /^get-all-chunks$/i) {
110 print &get_all_chunks(@gti_command_arguments);
111 }
112 if ($gti_command =~ /^get-first-n-chunks-requiring-work$/i) {
113 print &get_first_n_chunks_requiring_work(@gti_command_arguments);
114 }
115 if ($gti_command =~ /^get-language-status$/i) {
116 print &get_language_status(@gti_command_arguments);
117 }
118 if ($gti_command =~ /^search-chunks$/i) {
119 print &search_chunks(@gti_command_arguments);
120 }
121 if ($gti_command =~ /^submit-translations$/i) {
122 # This command cannot produce any output since it reads input
123 &submit_translations(@gti_command_arguments);
124 }
125
126 # The command was not recognized
127 # &throw_fatal_error("Unknown command \"$gti_command\".");
128}
129
130
131sub throw_fatal_error
132{
133 my $error_message = shift(@_);
134
135 # Write an XML error response
136 print "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
137 print "<GTIResponse>\n";
138 print " <GTIError time=\"" . time() . "\">" . $error_message . "</GTIError>\n";
139 print "</GTIResponse>\n";
140
141 # Log the error message, then die
142 &log_message("Error: $error_message");
143 die "\n";
144}
145
146
147sub log_message
148{
149 my $log_message = shift(@_);
150 print GTI_LOG time() . " -- " . $log_message . "\n";
151}
152
153
154sub get_all_chunks
155{
156 # The code of the target language (ensure it is lowercase)
157 my $target_language_code = lc(shift(@_));
158 # The key of the file to translate (ensure it is lowercase)
159 my $translation_file_key = lc(shift(@_));
160
161 # Check that the necessary arguments were supplied
162 if (!$target_language_code || !$translation_file_key) {
163 &throw_fatal_error("Missing command argument.");
164 }
165
166 # Get (and check) the translation configuration
167 my ($source_file, $target_file, $translation_file_type)
168 = &get_translation_configuration($target_language_code, $translation_file_key);
169
170 # Parse the source language and target language files
171 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
172 my @source_file_lines = &read_file_lines($source_file_path);
173 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
174
175 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
176 my @target_file_lines = &read_file_lines($target_file_path);
177 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
178
179 # Filter out any automatically translated chunks
180 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
181 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
182 delete $source_file_key_to_line_mapping{$chunk_key};
183 delete $target_file_key_to_line_mapping{$chunk_key};
184 }
185 }
186
187 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
188 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
189 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
190 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
191
192 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);
193 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);
194
195 # Form an XML response to the command
196 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
197 $xml_response .= "<GTIResponse>\n";
198 $xml_response .= " <TranslationFile"
199 . " key=\"" . $translation_file_key . "\""
200 . " target_file_path=\"" . $target_file . "\"\/>\n";
201
202 # Do all the chunks
203 $xml_response .= " <Chunks size=\"" . scalar(keys(%source_file_key_to_text_mapping)) . "\">\n";
204 foreach my $chunk_key (keys(%source_file_key_to_text_mapping)) {
205 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping{$chunk_key};
206 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping{$chunk_key});
207
208 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
209 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
210 if (defined($target_file_key_to_text_mapping{$chunk_key})) {
211 my $target_file_chunk_date = $target_file_key_to_last_update_date_mapping{$chunk_key};
212 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
213 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
214 }
215 else {
216 $xml_response .= " <TargetFileText></TargetFileText>\n";
217 }
218
219 $xml_response .= " </Chunk>\n";
220 }
221 $xml_response .= " </Chunks>\n";
222
223 $xml_response .= "</GTIResponse>\n";
224 return $xml_response;
225}
226
227
228sub get_first_n_chunks_requiring_work
229{
230 # The code of the target language (ensure it is lowercase)
231 my $target_language_code = lc(shift(@_));
232 # The key of the file to translate (ensure it is lowercase)
233 my $translation_file_key = lc(shift(@_));
234 # The number of chunks to return (defaults to one if not specified)
235 my $num_chunks_to_return = shift(@_) || "1";
236
237 # Check that the necessary arguments were supplied
238 if (!$target_language_code || !$translation_file_key) {
239 &throw_fatal_error("Missing command argument.");
240 }
241
242 # Get (and check) the translation configuration
243 my ($source_file, $target_file, $translation_file_type)
244 = &get_translation_configuration($target_language_code, $translation_file_key);
245
246 # Parse the source language and target language files
247 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
248 my @source_file_lines = &read_file_lines($source_file_path);
249 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
250
251 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
252 my @target_file_lines = &read_file_lines($target_file_path);
253 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
254
255 # Filter out any automatically translated chunks
256 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
257 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
258 delete $source_file_key_to_line_mapping{$chunk_key};
259 delete $target_file_key_to_line_mapping{$chunk_key};
260 }
261 }
262
263 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
264 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
265 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
266 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
267
268 # Determine the target file chunks requiring translation
269 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
270 &log_message("Number of target chunks requiring translation: " . scalar(@target_file_keys_requiring_translation));
271
272 # Determine the target file chunks requiring updating
273 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);
274 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);
275 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);
276 &log_message("Number of target chunks requiring updating: " . scalar(@target_file_keys_requiring_updating));
277
278 # Form an XML response to the command
279 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
280 $xml_response .= "<GTIResponse>\n";
281 $xml_response .= " <TranslationFile"
282 . " key=\"" . $translation_file_key . "\""
283 . " target_file_path=\"" . $target_file . "\""
284 . " num_chunks_translated=\"" . (scalar(keys(%source_file_key_to_text_mapping)) - scalar(@target_file_keys_requiring_translation)) . "\""
285 . " num_chunks_requiring_translation=\"" . scalar(@target_file_keys_requiring_translation) . "\""
286 . " num_chunks_requiring_updating=\"" . scalar(@target_file_keys_requiring_updating) . "\"\/>\n";
287
288 # Do chunks requiring translation first
289 if ($num_chunks_to_return > scalar(@target_file_keys_requiring_translation)) {
290 $xml_response .= " <ChunksRequiringTranslation size=\"" . scalar(@target_file_keys_requiring_translation) . "\">\n";
291 }
292 else {
293 $xml_response .= " <ChunksRequiringTranslation size=\"" . $num_chunks_to_return . "\">\n";
294 }
295
296 foreach my $chunk_key (@target_file_keys_requiring_translation) {
297 last if ($num_chunks_to_return == 0);
298
299 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping{$chunk_key};
300 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping{$chunk_key});
301
302 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
303 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
304 $xml_response .= " <TargetFileText></TargetFileText>\n";
305 $xml_response .= " </Chunk>\n";
306
307 $num_chunks_to_return--;
308 }
309
310 $xml_response .= " </ChunksRequiringTranslation>\n";
311
312 # Then do chunks requiring updating
313 if ($num_chunks_to_return > scalar(@target_file_keys_requiring_updating)) {
314 $xml_response .= " <ChunksRequiringUpdating size=\"" . scalar(@target_file_keys_requiring_updating) . "\">\n";
315 }
316 else {
317 $xml_response .= " <ChunksRequiringUpdating size=\"" . $num_chunks_to_return . "\">\n";
318 }
319
320 foreach my $chunk_key (@target_file_keys_requiring_updating) {
321 last if ($num_chunks_to_return == 0);
322
323 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping{$chunk_key};
324 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping{$chunk_key});
325 my $target_file_chunk_date = $target_file_key_to_last_update_date_mapping{$chunk_key};
326 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
327
328 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
329 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
330 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
331 $xml_response .= " </Chunk>\n";
332
333 $num_chunks_to_return--;
334 }
335
336 $xml_response .= " </ChunksRequiringUpdating>\n";
337
338 $xml_response .= "</GTIResponse>\n";
339 return $xml_response;
340}
341
342
343sub get_language_status
344{
345 # The code of the target language (ensure it is lowercase)
346 my $target_language_code = lc(shift(@_));
347
348 # Check that the necessary arguments were supplied
349 if (!$target_language_code) {
350 &throw_fatal_error("Missing command argument.");
351 }
352
353 # Form an XML response to the command
354 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
355 $xml_response .= "<GTIResponse>\n";
356 $xml_response .= " <LanguageStatus code=\"$target_language_code\">\n";
357
358 foreach my $translation_file (@$gti_translation_files) {
359 # Get (and check) the translation configuration
360 my ($source_file, $target_file, $translation_file_type)
361 = &get_translation_configuration($target_language_code, $translation_file->{'key'});
362
363 # Parse the source language and target language files
364 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
365 my @source_file_lines = &read_file_lines($source_file_path);
366 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
367
368 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
369 my @target_file_lines = &read_file_lines($target_file_path);
370 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
371
372 # Filter out any automatically translated chunks
373 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
374 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
375 delete $source_file_key_to_line_mapping{$chunk_key};
376 delete $target_file_key_to_line_mapping{$chunk_key};
377 }
378 }
379
380 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
381 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
382 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
383 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
384
385 # Determine the target file chunks requiring translation
386 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
387 &log_message("Number of target chunks requiring translation: " . scalar(@target_file_keys_requiring_translation));
388
389 # Determine the target file chunks requiring updating
390 my @target_file_keys_requiring_updating = ();
391 if (-e $target_file_path) {
392 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);
393 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);
394 @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);
395 &log_message("Number of target chunks requiring updating: " . scalar(@target_file_keys_requiring_updating));
396 }
397
398 $xml_response .= " <TranslationFile"
399 . " key=\"" . $translation_file->{'key'} . "\""
400 . " target_file_path=\"" . $target_file . "\""
401 . " num_chunks_translated=\"" . (scalar(keys(%source_file_key_to_text_mapping)) - scalar(@target_file_keys_requiring_translation)) . "\""
402 . " num_chunks_requiring_translation=\"" . scalar(@target_file_keys_requiring_translation) . "\""
403 . " num_chunks_requiring_updating=\"" . scalar(@target_file_keys_requiring_updating) . "\"\/>\n";
404 }
405
406 $xml_response .= " </LanguageStatus>\n";
407
408 $xml_response .= "</GTIResponse>\n";
409 return $xml_response;
410}
411
412
413sub search_chunks
414{
415 # The code of the target language (ensure it is lowercase)
416 my $target_language_code = lc(shift(@_));
417 # The key of the file to translate (ensure it is lowercase)
418 my $translation_file_key = lc(shift(@_));
419 # The query string
420 my $query_string = join(' ', @_);
421
422 # Check that the necessary arguments were supplied
423 if (!$target_language_code || !$translation_file_key || !$query_string) {
424 &throw_fatal_error("Missing command argument.");
425 }
426
427 # Get (and check) the translation configuration
428 my ($source_file, $target_file, $translation_file_type)
429 = &get_translation_configuration($target_language_code, $translation_file_key);
430
431 # Parse the source language and target language files
432 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
433 my @source_file_lines = &read_file_lines($source_file_path);
434 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
435
436 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
437 my @target_file_lines = &read_file_lines($target_file_path);
438 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
439
440 # Filter out any automatically translated chunks
441 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
442 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
443 delete $source_file_key_to_line_mapping{$chunk_key};
444 delete $target_file_key_to_line_mapping{$chunk_key};
445 }
446 }
447
448 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
449 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
450 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
451 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
452
453 # Determine the target file chunks matching the query
454 my @target_file_keys_matching_query = ();
455 foreach my $chunk_key (keys(%target_file_key_to_text_mapping)) {
456 my $target_file_text = $target_file_key_to_text_mapping{$chunk_key};
457 if ($target_file_text =~ /$query_string/i) {
458 # &log_message("Chunk with key $chunk_key matches query.");
459 push(@target_file_keys_matching_query, $chunk_key);
460 }
461 }
462
463 # Form an XML response to the command
464 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
465 $xml_response .= "<GTIResponse>\n";
466
467 $xml_response .= " <ChunksMatchingQuery size=\"" . scalar(@target_file_keys_matching_query) . "\">\n";
468 foreach my $chunk_key (@target_file_keys_matching_query) {
469 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
470
471 $xml_response .= " <Chunk key=\"$chunk_key\">\n";
472 $xml_response .= " <TargetFileText>$target_file_chunk_text</TargetFileText>\n";
473 $xml_response .= " </Chunk>\n";
474 }
475 $xml_response .= " </ChunksMatchingQuery>\n";
476
477 $xml_response .= "</GTIResponse>\n";
478 return $xml_response;
479}
480
481
482sub submit_translations
483{
484 # The code of the target language (ensure it is lowercase)
485 my $target_language_code = lc(shift(@_));
486 # The key of the file to translate (ensure it is lowercase)
487 my $translation_file_key = lc(shift(@_));
488 # The username of the translation submitter
489 my $submitter_username = shift(@_);
490 # Whether to submit a target chunk even if it hasn't changed
491 my $force_submission_flag = shift(@_);
492
493 # Check that the necessary arguments were supplied
494 if (!$target_language_code || !$translation_file_key || !$submitter_username) {
495 &log_message("Fatal error (but cannot be thrown): Missing command argument.");
496 die "\n";
497 }
498
499 # Get (and check) the translation configuration
500 my ($source_file, $target_file, $translation_file_type)
501 = &get_translation_configuration($target_language_code, $translation_file_key);
502
503 # Parse the source language and target language files
504 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
505 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
506 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
507 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);
508 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
509
510 my @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
511 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
512 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
513 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);
514 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
515
516 # Submission date
517 my $day = (localtime)[3];
518 my $month = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[(localtime)[4]];
519 my $year = (localtime)[5] + 1900;
520 my $submission_date = "$day-$month-$year";
521
522 open(SUBMISSION, "-");
523 my @submission_lines = <SUBMISSION>;
524 close(SUBMISSION);
525
526 # Remove any nasty carriage returns
527 &log_message("Submission:");
528 foreach my $submission_line (@submission_lines) {
529 $submission_line =~ s/\r$//;
530 &log_message(" $submission_line");
531 }
532
533 my %source_file_key_to_submission_mapping = ();
534 my %target_file_key_to_submission_mapping = ();
535 for (my $i = 0; $i < scalar(@submission_lines); $i++) {
536 # Read source file part of submission
537 if ($submission_lines[$i] =~ /^\<SourceFileText key=\"(.+)\"\>/) {
538 my $chunk_key = $1;
539
540 # Read the source file text
541 my $source_file_chunk_text = "";
542 $i++;
543 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/SourceFileText\>/) {
544 $source_file_chunk_text .= $submission_lines[$i];
545 $i++;
546 }
547 $source_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
548 $source_file_chunk_text = &unmake_text_xml_safe($source_file_chunk_text);
549
550 &log_message("Source file key: $chunk_key");
551 &log_message("Source file text: $source_file_chunk_text");
552 $source_file_key_to_submission_mapping{$chunk_key} = $source_file_chunk_text;
553 }
554
555 # Read target file part of submission
556 if ($submission_lines[$i] =~ /^\<TargetFileText key=\"(.+)\"\>/) {
557 my $chunk_key = $1;
558
559 # Read the target file text
560 my $target_file_chunk_text = "";
561 $i++;
562 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/TargetFileText\>/) {
563 $target_file_chunk_text .= $submission_lines[$i];
564 $i++;
565 }
566 $target_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
567 $target_file_chunk_text = &unmake_text_xml_safe($target_file_chunk_text);
568
569 &log_message("Target file key: $chunk_key");
570 &log_message("Target file text: $target_file_chunk_text");
571 $target_file_key_to_submission_mapping{$chunk_key} = $target_file_chunk_text;
572 }
573 }
574
575 # -----------------------------------------
576 # Validate the translation submissions
577 # -----------------------------------------
578
579 # Check that the translations are valid
580 foreach my $chunk_key (keys(%source_file_key_to_submission_mapping)) {
581 # Make sure the submitted chunk still exists in the source file
582 if (!defined($source_file_key_to_text_mapping{$chunk_key})) {
583 &log_message("Warning: Source chunk $chunk_key no longer exists (ignoring submission).");
584 delete $source_file_key_to_submission_mapping{$chunk_key};
585 delete $target_file_key_to_submission_mapping{$chunk_key};
586 next;
587 }
588
589 # Make sure the submitted source chunk matches the source file chunk
590 if ($source_file_key_to_submission_mapping{$chunk_key} ne &unmake_text_xml_safe($source_file_key_to_text_mapping{$chunk_key})) {
591 &log_message("Warning: Source chunk $chunk_key has changed (ignoring submission).");
592 &log_message("Submission source: $source_file_key_to_submission_mapping{$chunk_key}");
593 &log_message(" Source text: $source_file_key_to_text_mapping{$chunk_key}");
594 delete $source_file_key_to_submission_mapping{$chunk_key};
595 delete $target_file_key_to_submission_mapping{$chunk_key};
596 next;
597 }
598 }
599
600 # Apply the submitted translations
601 foreach my $chunk_key (keys(%target_file_key_to_submission_mapping)) {
602 # Only apply the submission if it is a change, unless -force_submission has been specified
603 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}) {
604 $target_file_key_to_text_mapping{$chunk_key} = $target_file_key_to_submission_mapping{$chunk_key};
605 $target_file_key_to_gti_comment_mapping{$chunk_key} = "Updated $submission_date by $submitter_username";
606 }
607 }
608
609 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)";
610}
611
612
613sub get_translation_configuration
614{
615 # Get the code of the target language
616 my $target_language_code = shift(@_);
617 # Get the key of the file to translate
618 my $translation_file_key = shift(@_);
619
620 # Read the translation data from the gti.cfg file
621 my ($source_file, $target_file, $translation_file_type) =
622 &get_translation_data_for($target_language_code, $translation_file_key);
623
624 # Check that the file to translate is defined in the gti.cfg file
625 if (!$source_file || !$target_file || !$translation_file_type) {
626 &throw_fatal_error("Missing or incomplete specification for translation file \"$translation_file_key\" in gti.pl.");
627 }
628
629 # Check that the source file exists
630 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
631 if (!-e $source_file_path) {
632 &throw_fatal_error("Source file $source_file_path does not exist.");
633 }
634
635 # Check that the source file is up to date
636 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
637 # unless ($translation_file_is_not_in_cvs) {
638 my $source_file_cvs_status = `cd $gsdl_root_directory; cvs -d $anonymous_cvs_root update $source_file 2>/dev/null`;
639 if ($source_file_cvs_status =~ /^C /) {
640 &throw_fatal_error("Source file $source_file_path conflicts with the repository.");
641 }
642 if ($source_file_cvs_status =~ /^M /) {
643 &throw_fatal_error("Source file $source_file_path contains uncommitted changes.");
644 }
645 # }
646
647 return ($source_file, $target_file, $translation_file_type);
648}
649
650
651sub get_translation_data_for
652{
653 my ($target_language_code, $translation_file_key) = @_;
654
655 foreach my $translation_file (@$gti_translation_files) {
656 # If this isn't the correct translation file, move onto the next one
657 next if ($translation_file_key ne $translation_file->{'key'});
658
659 # Resolve the target language file
660 my $target_language_file = $translation_file->{'target_file'};
661 if ($target_language_file =~ /(\{.+\;.+\})/) {
662 my $unresolved_target_language_file_part = $1;
663
664 # Check for a special case for the target language code
665 if ($unresolved_target_language_file_part =~ /(\{|\;)$target_language_code:([^\;]+)(\;|\})/) {
666 my $resolved_target_language_file_part = $2;
667 $target_language_file =~ s/$unresolved_target_language_file_part/$resolved_target_language_file_part/;
668 }
669 # Otherwise use the last part as the default value
670 else {
671 my ($default_target_language_file_part) = $unresolved_target_language_file_part =~ /([^\;]+)\}/;
672 $target_language_file =~ s/$unresolved_target_language_file_part/\{$default_target_language_file_part\}/;
673 }
674 }
675
676 # Resolve instances of {iso_639_1_target_language_name}
677 my $iso_639_1_target_language_name = $iso639::fromiso639{$target_language_code};
678 $iso_639_1_target_language_name =~ tr/A-Z/a-z/ if $iso_639_1_target_language_name;
679 $target_language_file =~ s/\{iso_639_1_target_language_name\}/$iso_639_1_target_language_name/g;
680
681 # Resolve instances of {target_language_code}
682 $target_language_file =~ s/\{target_language_code\}/$target_language_code/g;
683
684 return ($translation_file->{'source_file'}, $target_language_file, $translation_file->{'file_type'});
685 }
686
687 return ();
688}
689
690
691sub read_file_lines
692{
693 my ($file_path) = @_;
694
695 if (!open(FILE_IN, "<$file_path")) {
696 &log_message("Note: Could not open file $file_path.");
697 return ();
698 }
699 my @file_lines = <FILE_IN>;
700 close(FILE_IN);
701
702 return @file_lines;
703}
704
705
706sub build_key_to_line_mapping
707{
708 my ($file_lines, $translation_file_type) = @_;
709 eval "return &build_key_to_line_mapping_for_${translation_file_type}(\@\$file_lines)";
710}
711
712
713sub build_key_to_text_mapping
714{
715 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
716
717 my %key_to_text_mapping = ();
718 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
719 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
720 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
721
722 my $chunk_text = @$file_lines[$chunk_starting_line];
723 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
724 $chunk_text .= @$file_lines[$l];
725 }
726
727 # Map from chunk key to text
728 eval "\$key_to_text_mapping{\${chunk_key}} = &import_chunk_from_${translation_file_type}(\$chunk_text)";
729 }
730
731 return %key_to_text_mapping;
732}
733
734
735sub build_key_to_last_update_date_mapping
736{
737 my ($file, $file_lines, $key_to_line_mapping, $translation_file_type) = @_;
738
739 # If the files aren't in CVS then we can't tell anything about what needs updating
740 # return () if ($translation_file_is_not_in_cvs);
741
742 # Build a mapping from key to CVS date
743 # Need to be careful with this mapping because the chunk keys won't necessarily all be valid
744 my %key_to_cvs_date_mapping = &build_key_to_cvs_date_mapping($file, $translation_file_type);
745
746 # Build a mapping from key to comment date
747 my %key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping($file_lines, $key_to_line_mapping, $translation_file_type);
748
749 # Build a mapping from key to last update date (the latter of the CVS date and comment date)
750 my %key_to_last_update_date_mapping = ();
751 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
752 # Use the CVS date as a starting point
753 my $chunk_cvs_date = $key_to_cvs_date_mapping{$chunk_key};
754 $key_to_last_update_date_mapping{$chunk_key} = $chunk_cvs_date;
755
756 # If a comment date exists and it is after the CVS date, use that instead
757 my $chunk_gti_comment = $key_to_gti_comment_mapping{$chunk_key};
758 if (defined($chunk_gti_comment) && $chunk_gti_comment =~ /(\d?\d-\D\D\D-\d\d\d\d)/) {
759 my $chunk_comment_date = $1;
760 if ((!defined($chunk_cvs_date) || &is_date_after($chunk_comment_date, $chunk_cvs_date))) {
761 $key_to_last_update_date_mapping{$chunk_key} = $chunk_comment_date;
762 }
763 }
764 }
765
766 return %key_to_last_update_date_mapping;
767}
768
769
770sub build_key_to_cvs_date_mapping
771{
772 my ($filename, $translation_file_type) = @_;
773
774 # Use CVS to annotate each line of the file with the date it was last edited
775 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
776 my $cvs_annotated_file = `cd $gsdl_root_directory; cvs -d $anonymous_cvs_root annotate -F $filename 2>/dev/null`;
777 my @cvs_annotated_file_lines = split(/\n/, $cvs_annotated_file);
778
779 my @cvs_annotated_file_lines_date = ();
780 foreach my $cvs_annotated_file_line (@cvs_annotated_file_lines) {
781 # Extract the date from the CVS annotation at the front
782 $cvs_annotated_file_line =~ s/^\S+\s+\(\S+\s+(\S+)\):\s//;
783 push(@cvs_annotated_file_lines_date, $1);
784 }
785
786 # Build a key to line mapping for the CVS annotated file, for matching the chunk key to the CVS date
787 my %key_to_line_mapping = &build_key_to_line_mapping(\@cvs_annotated_file_lines, $translation_file_type);
788
789 my %key_to_cvs_date_mapping = ();
790 foreach my $chunk_key (keys(%key_to_line_mapping)) {
791 my $chunk_starting_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[0];
792 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[1];
793
794 # Find the date this chunk was last edited, from the CVS annotation
795 my $chunk_date = $cvs_annotated_file_lines_date[$chunk_starting_line];
796 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
797 if (&is_date_after($cvs_annotated_file_lines_date[$l], $chunk_date)) {
798 # This part of the chunk has been updated more recently
799 $chunk_date = $cvs_annotated_file_lines_date[$l];
800 }
801 }
802
803 # Map from chunk key to CVS date
804 $key_to_cvs_date_mapping{$chunk_key} = $chunk_date;
805 }
806
807 return %key_to_cvs_date_mapping;
808}
809
810
811sub build_key_to_gti_comment_mapping
812{
813 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
814
815 my %key_to_gti_comment_mapping = ();
816 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
817 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
818 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
819
820 my $chunk_text = @$file_lines[$chunk_starting_line];
821 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
822 $chunk_text .= @$file_lines[$l];
823 }
824
825 # Map from chunk key to GTI comment
826 my $chunk_gti_comment;
827 eval "\$chunk_gti_comment = &get_${translation_file_type}_chunk_gti_comment(\$chunk_text)";
828 $key_to_gti_comment_mapping{$chunk_key} = $chunk_gti_comment if (defined($chunk_gti_comment));
829 }
830
831 return %key_to_gti_comment_mapping;
832}
833
834
835sub determine_chunks_requiring_translation
836{
837 my $source_file_key_to_text_mapping = shift(@_);
838 my $target_file_key_to_text_mapping = shift(@_);
839
840 # Chunks needing translation are those in the source file with no translation in the target file
841 my @target_file_keys_requiring_translation = ();
842 foreach my $chunk_key (keys(%$source_file_key_to_text_mapping)) {
843 if ($source_file_key_to_text_mapping->{$chunk_key} && !$target_file_key_to_text_mapping->{$chunk_key}) {
844 # &log_message("Chunk with key $chunk_key needs translating.");
845 push(@target_file_keys_requiring_translation, $chunk_key);
846 }
847 }
848
849 return @target_file_keys_requiring_translation;
850}
851
852
853sub determine_chunks_requiring_updating
854{
855 my $source_file_key_to_last_update_date_mapping = shift(@_);
856 my $target_file_key_to_last_update_date_mapping = shift(@_);
857
858 # Chunks needing updating are those in the target file that have been more recently edited in the source file
859 my @target_file_keys_requiring_updating = ();
860 foreach my $chunk_key (keys(%$source_file_key_to_last_update_date_mapping)) {
861 my $source_chunk_last_update_date = $source_file_key_to_last_update_date_mapping->{$chunk_key};
862 my $target_chunk_last_update_date = $target_file_key_to_last_update_date_mapping->{$chunk_key};
863 if (defined($target_chunk_last_update_date) && &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
864 # &log_message("Chunk with key $chunk_key needs updating.");
865 push(@target_file_keys_requiring_updating, $chunk_key);
866 }
867 }
868
869 return @target_file_keys_requiring_updating;
870}
871
872
873sub is_chunk_automatically_translated
874{
875 my ($chunk_key, $translation_file_type) = @_;
876 eval "return &is_${translation_file_type}_chunk_automatically_translated(\$chunk_key)";
877}
878
879
880sub make_text_xml_safe
881{
882 my $text = shift(@_);
883 $text =~ s/\&/\&amp\;/g;
884 $text =~ s/\&amp\;lt\;/\&amp\;amp\;lt\;/g;
885 $text =~ s/\&amp\;gt\;/\&amp\;amp\;gt\;/g;
886 $text =~ s/\&amp\;rarr\;/\&amp\;amp\;rarr\;/g;
887 $text =~ s/\&amp\;mdash\;/\&amp\;amp\;mdash\;/g;
888 $text =~ s/</\&lt\;/g;
889 $text =~ s/>/\&gt\;/g;
890 return $text;
891}
892
893
894sub unmake_text_xml_safe
895{
896 my $text = shift(@_);
897 $text =~ s/\&lt\;/</g;
898 $text =~ s/\&gt\;/>/g;
899 $text =~ s/\&amp\;/\&/g;
900 return $text;
901}
902
903
904# Returns 1 if $date1 is after $date2, 0 otherwise
905sub is_date_after
906{
907 my ($date1, $date2) = @_;
908 my %months = ("Jan", 1, "Feb", 2, "Mar", 3, "Apr", 4, "May", 5, "Jun", 6,
909 "Jul", 7, "Aug", 8, "Sep", 9, "Oct", 10, "Nov", 11, "Dec", 12);
910
911 my @date1parts = split(/-/, $date1);
912 my @date2parts = split(/-/, $date2);
913
914 # Compare year - nasty because we have rolled over into a new century
915 my $year1 = $date1parts[2];
916 if ($year1 < 80) {
917 $year1 += 2000;
918 }
919 my $year2 = $date2parts[2];
920 if ($year2 < 80) {
921 $year2 += 2000;
922 }
923
924 # Compare year
925 if ($year1 > $year2) {
926 return 1;
927 }
928 elsif ($year1 == $year2) {
929 # Year is the same, so compare month
930 if ($months{$date1parts[1]} > $months{$date2parts[1]}) {
931 return 1;
932 }
933 elsif ($months{$date1parts[1]} == $months{$date2parts[1]}) {
934 # Month is the same, so compare day
935 if ($date1parts[0] > $date2parts[0]) {
936 return 1;
937 }
938 }
939 }
940
941 return 0;
942}
943
944
945# ==========================================================================================
946# MACROFILE FUNCTIONS
947
948sub build_key_to_line_mapping_for_macrofile
949{
950 my (@file_lines) = @_;
951
952 my $macro_package;
953 my %chunk_key_to_line_mapping = ();
954 # Process the contents of the file, line by line
955 for (my $i = 0; $i < scalar(@file_lines); $i++) {
956 my $line = $file_lines[$i];
957 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
958
959 # Check if a new package is being defined
960 if ($line =~ m/^package\s+(.+)/) {
961 $macro_package = $1;
962 }
963
964 # Line contains a macro name
965 elsif ($line =~ m/^(_\w+_)/) {
966 my $macro_key = $1;
967 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
968
969 # While there is still text of the macro to go...
970 my $startline = $i;
971 while ($line !~ /\}$/) {
972 $i++;
973 if ($i == scalar(@file_lines)) {
974 &throw_fatal_error("Could not find end of macro $macro_key.");
975 }
976 $line = $file_lines[$i];
977 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
978 }
979
980 # The chunk key consists of the package name and the macro key
981 my $chunk_key = $macro_package . "." . $macro_key;
982 # Map from chunk key to line
983 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
984 }
985
986 # Icon: line in format ## "image text" ## image_type ## macro_name ##
987 elsif ($line =~ m/^\#\# .* \#\# .* \#\# (.*) \#\#/) {
988 # The chunk key consists of package name and macro key
989 my $chunk_key = $macro_package . "." . $1;
990 # Map from chunk key to line
991 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
992 }
993 }
994
995 return %chunk_key_to_line_mapping;
996}
997
998
999sub import_chunk_from_macrofile
1000{
1001 my ($chunk_text) = @_;
1002
1003 # Is this an icon macro??
1004 if ($chunk_text =~ /^\#\# (.*)/) {
1005 # Extract image macro text
1006 $chunk_text =~ /^\#\#\s+([^\#]+)\s+\#\#/;
1007 $chunk_text = $1;
1008
1009 # Remove enclosing quotes
1010 $chunk_text =~ s/^\"//;
1011 $chunk_text =~ s/\"$//;
1012 }
1013
1014 # No, so it must be a text macro
1015 else {
1016 # Remove macro key
1017 $chunk_text =~ s/^_([^_]+)_(\s*)//;
1018
1019 # Remove language specifier
1020 $chunk_text =~ s/^\[l=.*\](\s*)//;
1021
1022 # Remove braces enclosing text
1023 $chunk_text =~ s/^{(\s*)((.|\n)*)}(\s*)(\#.+\s*)?/$2/;
1024 }
1025
1026 return $chunk_text;
1027}
1028
1029
1030sub get_macrofile_chunk_gti_comment
1031{
1032 my ($chunk_text) = @_;
1033
1034 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1035 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1036 return $1;
1037 }
1038
1039 return undef;
1040}
1041
1042
1043sub is_macrofile_chunk_automatically_translated
1044{
1045 my ($chunk_key) = @_;
1046
1047 # The _httpiconX_, _widthX_ and _heightX_ image macros are automatically translated
1048 if ($chunk_key =~ /\._(httpicon|width|height)/) {
1049 return 1;
1050 }
1051
1052 return 0;
1053}
1054
1055
1056# Use the source file to generate a target file that is formatted the same
1057sub write_translated_macrofile
1058{
1059 my $source_file = shift(@_); # Not used
1060 my @source_file_lines = @{shift(@_)};
1061 my $source_file_key_to_text_mapping = shift(@_);
1062 my $target_file = shift(@_);
1063 my @target_file_lines = @{shift(@_)};
1064 my $target_file_key_to_text_mapping = shift(@_);
1065 my $target_file_key_to_gti_comment_mapping = shift(@_);
1066 my $target_language_code = shift(@_);
1067
1068 # Build a mapping from source file line to chunk key
1069 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@source_file_lines);
1070 my %source_file_line_to_key_mapping = ();
1071 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1072 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1073 }
1074 my @source_file_line_keys = (sort sort_by_line (keys(%source_file_line_to_key_mapping)));
1075 my $source_file_line_number = 0;
1076
1077 # Build a mapping from target file line to chunk key
1078 my %target_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@target_file_lines);
1079 my %target_file_line_to_key_mapping = ();
1080 foreach my $chunk_key (keys(%target_file_key_to_line_mapping)) {
1081 $target_file_line_to_key_mapping{$target_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1082 }
1083 my @target_file_line_keys = (sort sort_by_line (keys(%target_file_line_to_key_mapping)));
1084
1085 # Write the new target file
1086 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1087 if (!open(TARGET_FILE, ">$target_file_path")) {
1088 &throw_fatal_error("Could not write target file $target_file_path.");
1089 }
1090
1091 # Use the header from the target file, to keep language and author information
1092 if (scalar(@target_file_line_keys) > 0) {
1093 my $target_file_line_number = 0;
1094 my $target_file_chunk_starting_line_number = (split(/-/, $target_file_line_keys[0]))[0];
1095 while ($target_file_line_number < $target_file_chunk_starting_line_number) {
1096 my $target_file_line = $target_file_lines[$target_file_line_number];
1097 last if ($target_file_line =~ /^\# -- Missing translation: /); # We don't want to get into the macros
1098 print TARGET_FILE $target_file_line;
1099 $target_file_line_number++;
1100 }
1101
1102 $source_file_line_number = (split(/-/, $source_file_line_keys[0]))[0];
1103 }
1104
1105 # Model the new target file on the source file, with the target file translations
1106 foreach my $line_key (@source_file_line_keys) {
1107 # Fill in the gaps before this chunk starts
1108 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1109 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1110 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1111 print TARGET_FILE $source_file_lines[$source_file_line_number];
1112 $source_file_line_number++;
1113 }
1114 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1115
1116 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1117 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1118 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1119
1120 my $macrofile_key = $chunk_key;
1121 $macrofile_key =~ s/^(.+?)\.//;
1122
1123 # If no translation exists for this chunk, show this, and move on
1124 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1125 print TARGET_FILE "# -- Missing translation: $macrofile_key\n";
1126 next;
1127 }
1128
1129 # Grab the source chunk text
1130 my $source_file_chunk = $source_file_lines[$source_file_chunk_starting_line_number];
1131 for (my $l = ($source_file_chunk_starting_line_number + 1); $l <= $source_file_chunk_finishing_line_number; $l++) {
1132 $source_file_chunk .= $source_file_lines[$l];
1133 }
1134
1135 # Is this an icon macro??
1136 if ($source_file_chunk =~ /^\#\# (.*)/) {
1137 # Escape any newline and question mark characters so the source text is replaced correctly
1138 $source_file_chunk_text =~ s/\\/\\\\/g;
1139 $source_file_chunk_text =~ s/\?/\\\?/g;
1140
1141 # Build the new target chunk from the source chunk
1142 my $target_file_chunk = $source_file_chunk;
1143 $target_file_chunk =~ s/$source_file_chunk_text/$target_file_chunk_text/;
1144 $target_file_chunk =~ s/(\s)*$//;
1145 print TARGET_FILE "$target_file_chunk";
1146 }
1147
1148 # No, it is just a normal text macro
1149 else {
1150 print TARGET_FILE "$macrofile_key [l=$target_language_code] {$target_file_chunk_text}";
1151 }
1152
1153 # Add the "updated" comment, if one exists
1154 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1155 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1156 }
1157 print TARGET_FILE "\n";
1158 }
1159
1160 close(TARGET_FILE);
1161}
1162
1163
1164sub sort_by_line
1165{
1166 return ((split(/-/, $a))[0] <=> (split(/-/, $b))[0]);
1167}
1168
1169
1170# ==========================================================================================
1171# RESOURCE BUNDLE FUNCTIONS
1172
1173sub build_key_to_line_mapping_for_resource_bundle
1174{
1175 my (@file_lines) = @_;
1176
1177 my %chunk_key_to_line_mapping = ();
1178 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1179 my $line = $file_lines[$i];
1180 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1181
1182 # Line contains a dictionary string
1183 if ($line =~ /^(\S+?):(.*)$/) {
1184 my $chunk_key = $1;
1185
1186 # Map from chunk key to line
1187 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
1188 }
1189 }
1190
1191 return %chunk_key_to_line_mapping;
1192}
1193
1194
1195sub import_chunk_from_resource_bundle
1196{
1197 my ($chunk_text) = @_;
1198
1199 # Simple: just remove string key
1200 $chunk_text =~ s/^(\S+?):(\s*)//;
1201 $chunk_text =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1202 $chunk_text =~ s/(\s*)\#\s+Updated\s+(\d?\d-\D\D\D-\d\d\d\d.*)\s*$//i;
1203
1204 return $chunk_text;
1205}
1206
1207
1208sub get_resource_bundle_chunk_gti_comment
1209{
1210 my ($chunk_text) = @_;
1211
1212 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1213 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1214 return $1;
1215 }
1216
1217 return undef;
1218}
1219
1220
1221sub is_resource_bundle_chunk_automatically_translated
1222{
1223 # No resource bundle chunks are automatically translated
1224 return 0;
1225}
1226
1227
1228sub write_translated_resource_bundle
1229{
1230 my $source_file = shift(@_); # Not used
1231 my @source_file_lines = @{shift(@_)};
1232 my $source_file_key_to_text_mapping = shift(@_);
1233 my $target_file = shift(@_);
1234 my @target_file_lines = @{shift(@_)}; # Not used
1235 my $target_file_key_to_text_mapping = shift(@_);
1236 my $target_file_key_to_gti_comment_mapping = shift(@_);
1237 my $target_language_code = shift(@_); # Not used
1238
1239 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1240 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
1241 my %source_file_line_to_key_mapping = ();
1242 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1243 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1244 }
1245
1246 # Write the new target file
1247 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1248 if (!open(TARGET_FILE, ">$target_file_path")) {
1249 &throw_fatal_error("Could not write target file $target_file_path.");
1250 }
1251
1252 # Model the new target file on the source file, with the target file translations
1253 my $source_file_line_number = 0;
1254 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1255 # Fill in the gaps before this chunk starts
1256 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1257 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1258 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1259 print TARGET_FILE $source_file_lines[$source_file_line_number];
1260 $source_file_line_number++;
1261 }
1262 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1263
1264 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1265 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1266 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1267
1268 # If no translation exists for this chunk, show this, and move on
1269 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1270 print TARGET_FILE "# -- Missing translation: $chunk_key\n";
1271 next;
1272 }
1273
1274 print TARGET_FILE "$chunk_key:$target_file_chunk_text";
1275 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1276 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1277 }
1278 print TARGET_FILE "\n";
1279 }
1280
1281 close(TARGET_FILE);
1282}
1283
1284
1285# ==========================================================================================
1286# GREENSTONE XML FUNCTIONS
1287
1288sub build_key_to_line_mapping_for_greenstone_xml
1289{
1290 my (@file_lines) = @_;
1291
1292 my %chunk_key_to_line_mapping = ();
1293 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1294 my $line = $file_lines[$i];
1295 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1296
1297 # Line contains a string to translate
1298 if ($line =~ /^\s*<Text id=\"(.*?)\">/) {
1299 my $chunk_key = $1;
1300 $line =~ s/\s*$//; # Remove any nasty whitespace
1301 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1302
1303 # While there is still text of the string to go...
1304 my $startline = $i;
1305 while ($line !~ /<\/Text>$/) {
1306 $i++;
1307 if ($i == scalar(@file_lines)) {
1308 &throw_fatal_error("Could not find end of string $chunk_key.");
1309 }
1310 $line = $file_lines[$i];
1311 $line =~ s/\s*$//; # Remove any nasty whitespace
1312 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1313 }
1314
1315 # Map from chunk key to line
1316 if (!defined($chunk_key_to_line_mapping{$chunk_key})) {
1317 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1318 }
1319 else {
1320 &throw_fatal_error("Duplicate key $chunk_key.");
1321 }
1322 }
1323 }
1324
1325 return %chunk_key_to_line_mapping;
1326}
1327
1328
1329sub import_chunk_from_greenstone_xml
1330{
1331 my ($chunk_text) = @_;
1332
1333 # Simple: just remove the Text tags
1334 $chunk_text =~ s/^\s*<Text id=\"(.*?)\">(\s*)//;
1335 $chunk_text =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1336 $chunk_text =~ s/<\/Text>$//;
1337
1338 return $chunk_text;
1339}
1340
1341
1342sub get_greenstone_xml_chunk_gti_comment
1343{
1344 my ($chunk_text) = @_;
1345
1346 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1347 if ($chunk_text =~ /<Updated date=\"(\d?\d-\D\D\D-\d\d\d\d.*)\"\/>$/i) {
1348 return $1;
1349 }
1350
1351 return undef;
1352}
1353
1354
1355sub is_greenstone_xml_chunk_automatically_translated
1356{
1357 # No greenstone XML chunks are automatically translated
1358 return 0;
1359}
1360
1361
1362sub write_translated_greenstone_xml
1363{
1364 my $source_file = shift(@_); # Not used
1365 my @source_file_lines = @{shift(@_)};
1366 my $source_file_key_to_text_mapping = shift(@_);
1367 my $target_file = shift(@_);
1368 my @target_file_lines = @{shift(@_)}; # Not used
1369 my $target_file_key_to_text_mapping = shift(@_);
1370 my $target_file_key_to_gti_comment_mapping = shift(@_);
1371 my $target_language_code = shift(@_); # Not used
1372
1373 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1374 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_greenstone_xml(@source_file_lines);
1375 my %source_file_line_to_key_mapping = ();
1376 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1377 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1378 }
1379
1380 # Write the new target file
1381 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1382 if (!open(TARGET_FILE, ">$target_file_path")) {
1383 &throw_fatal_error("Could not write target file $target_file_path.");
1384 }
1385
1386 # Model the new target file on the source file, with the target file translations
1387 my $source_file_line_number = 0;
1388 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1389 # Fill in the gaps before this chunk starts
1390 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1391 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1392 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1393 print TARGET_FILE $source_file_lines[$source_file_line_number];
1394 $source_file_line_number++;
1395 }
1396 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1397
1398 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1399 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1400 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1401 $target_file_chunk_text =~ s/(\n)*$//g;
1402
1403 # If no translation exists for this chunk, show this, and move on
1404 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1405 print TARGET_FILE "<!-- Missing translation: $chunk_key -->\n";
1406 next;
1407 }
1408
1409 print TARGET_FILE "<Text id=\"$chunk_key\">$target_file_chunk_text</Text>";
1410 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1411 my $chunk_gti_comment = $target_file_key_to_gti_comment_mapping->{$chunk_key};
1412 $chunk_gti_comment =~ s/^Updated //;
1413 print TARGET_FILE "<Updated date=\"" . $chunk_gti_comment . "\"\/>";
1414 }
1415 print TARGET_FILE "\n";
1416 }
1417
1418 # Fill in the end of the file
1419 while ($source_file_line_number < scalar(@source_file_lines)) {
1420 print TARGET_FILE $source_file_lines[$source_file_line_number];
1421 $source_file_line_number++;
1422 }
1423
1424 close(TARGET_FILE);
1425}
1426
1427
1428&main(@ARGV);
Note: See TracBrowser for help on using the repository browser.