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

Last change on this file since 13216 was 13216, checked in by mdewsnip, 17 years ago

Now makes the chunk keys XML safe also, to prevent XML errors with the GLI dictionary.

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