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

Last change on this file since 33436 was 33436, checked in by ak19, 5 years ago

3 important changes for 2 separate bugfixes where one bugfix is experimental and additional commented out debug statements. The first bug was when every source string for glihelp was marked as modified and translations wouldn't go through. Code fixed to cope with newlines/general whitespace introduced to the end of the English source strings in xml translation modules (gli help.xml files) and to trim whitespace at the start as well. The second bug is related to commits 28503 and 28518, which made improvements for special cases of processing translations that had to be rolled back because they adversely affected the general case. The bug this time is that in some cases, we don't want to care the unmake_xml_safe version of the original source string with the submission source string, but just compare the plain version of both to detect if the source string has been modified. Any modification to the source string and its translation won't go through. But in some special cases there's no actual change detectible except when the original source string has unmake_xml_safe applied to it in the comparison. This bugfix is a trial and error thing, in that only future cases of processing spreadsheets will tell us if the fix can stay or has to be removed if it too negatively affects the general functioning of processing spreadsheets.

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