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

Last change on this file since 35656 was 35656, checked in by davidb, 3 years ago

Comments updated to specify https://svn.greenstone rather then http://svn.greenstone. Need to keep an eye on whether or not there are Perl variables used by gti that need comparable update

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 97.4 KB
Line 
1#!/usr/bin/perl -w
2
3###########################################################################
4#
5# gti.pl
6#
7# A component of the Greenstone digital library software
8# from the New Zealand Digital Library Project at the
9# University of Waikato, New Zealand.
10#
11# Copyright (C) 2005 New Zealand Digital Library Project
12#
13# This program is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation; either version 2 of the License, or
16# (at your option) any later version.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26#
27###########################################################################
28
29
30BEGIN {
31 die "GSDLHOME not set\n" unless defined $ENV{'GSDLHOME'};
32 unshift (@INC, "$ENV{'GSDLHOME'}/perllib");
33}
34
35
36use iso639;
37use strict;
38use util;
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 https://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 https://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", "core_servlet_dictionary", "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 #&log_message("**** Next chunk to compare search term $query_string to: $target_file_text.");
542 if ($target_file_text =~ /$query_string/i) {
543 # &log_message("Chunk with key $chunk_key matches query.");
544 push(@target_file_keys_matching_query, $chunk_key);
545 }
546 }
547
548 # Form an XML response to the command
549 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
550 $xml_response .= "<GTIResponse>\n";
551
552 $xml_response .= " <ChunksMatchingQuery size=\"" . scalar(@target_file_keys_matching_query) . "\">\n";
553 foreach my $chunk_key (@target_file_keys_matching_query) {
554 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
555
556 $xml_response .= " <Chunk key=\"$chunk_key\">\n";
557 $xml_response .= " <TargetFileText>$target_file_chunk_text</TargetFileText>\n";
558 $xml_response .= " </Chunk>\n";
559 }
560 $xml_response .= " </ChunksMatchingQuery>\n";
561
562 $xml_response .= "</GTIResponse>\n";
563 return $xml_response;
564}
565
566
567sub submit_translations
568{
569 # The code of the target language (ensure it is lowercase)
570 my $target_language_code = lc(shift(@_));
571 # The key of the file to translate (ensure it is lowercase)
572 my $translation_file_key = lc(shift(@_));
573 # The username of the translation submitter
574 my $submitter_username = shift(@_);
575 # Whether to submit a target chunk even if it hasn't changed
576 my $force_submission_flag = shift(@_);
577
578 # Check that the necessary arguments were supplied
579 if (!$target_language_code || !$translation_file_key || !$submitter_username) {
580 &log_message("Fatal error (but cannot be thrown): Missing command argument.");
581 die "\n";
582 }
583
584 my %source_file_key_to_text_mapping = ();
585 my %source_file_key_to_gti_comment_mapping = ();
586 my %target_file_key_to_text_mapping = ();
587 my %target_file_key_to_gti_comment_mapping = ();
588
589 my (@source_file_lines, @target_file_lines) = ();
590 my ($source_file, $target_file, $translation_file_type);
591
592
593 if ($translation_file_key !~ m/^gs3/) {
594 # Get (and check) the translation configuration
595 ($source_file, $target_file, $translation_file_type)
596 = &get_translation_configuration($target_language_code, $translation_file_key);
597
598 #&log_message("********************** START SOURCE ***********************************");
599
600 # Parse the source language and target language files
601 @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
602 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
603 %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
604 %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);
605
606 #&log_message("********************** START TARGET ***********************************");
607
608 @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
609 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
610 %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
611 %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);
612 }
613 else {
614 &build_gs3_configuration($translation_file_key, $target_language_code, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping,
615 \%source_file_key_to_gti_comment_mapping, \%target_file_key_to_gti_comment_mapping, 1);
616 }
617 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
618 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
619
620 # Submission date
621 my $day = (localtime)[3];
622 my $month = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[(localtime)[4]];
623 my $year = (localtime)[5] + 1900;
624 my $submission_date = "$day-$month-$year";
625
626 open(SUBMISSION, "-");
627 my @submission_lines = <SUBMISSION>;
628 close(SUBMISSION);
629
630 # Remove any nasty carriage returns
631 # &log_message("Submission:");
632 foreach my $submission_line (@submission_lines) {
633 $submission_line =~ s/\r$//;
634 $submission_line =~ s/ +$//;
635 #&log_message(" $submission_line");
636 }
637
638 my %source_file_key_to_submission_mapping = ();
639 my %target_file_key_to_submission_mapping = ();
640 for (my $i = 0; $i < scalar(@submission_lines); $i++) {
641 # Read source file part of submission
642 if ($submission_lines[$i] =~ /^\<SourceFileText key=\"(.+)\"\>/) {
643 my $chunk_key = $1;
644
645 # Read the source file text
646 my $source_file_chunk_text = "";
647 $i++;
648 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/SourceFileText\>/) {
649 $source_file_chunk_text .= $submission_lines[$i];
650 $i++;
651 }
652 $source_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
653 $source_file_chunk_text = &unmake_text_xml_safe($source_file_chunk_text);
654
655 #&log_message("Source file key: |$chunk_key|");
656 #&log_message("Source file text: |$source_file_chunk_text|");
657 $source_file_key_to_submission_mapping{$chunk_key} = $source_file_chunk_text;
658 }
659
660 # Read target file part of submission
661 if ($submission_lines[$i] =~ /^\<TargetFileText key=\"(.+)\"\>/) {
662 my $chunk_key = $1;
663
664 # Read the target file text
665 my $target_file_chunk_text = "";
666 $i++;
667 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/TargetFileText\>/) {
668 $target_file_chunk_text .= $submission_lines[$i];
669 $i++;
670 }
671 $target_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
672 $target_file_chunk_text = &unmake_text_xml_safe($target_file_chunk_text);
673
674 #&log_message("Target file key: |$chunk_key|");
675 #&log_message("Target file text: |$target_file_chunk_text|");
676 $target_file_key_to_submission_mapping{$chunk_key} = $target_file_chunk_text;
677 }
678 }
679
680 # -----------------------------------------
681 # Validate the translation submissions
682 # -----------------------------------------
683
684 # Check that the translations are valid
685 foreach my $chunk_key (keys(%source_file_key_to_submission_mapping)) {
686
687 # Kathy introduced escaped colons ("\:") into chunk keys in properties files (greenstone3/metadata_names),
688 # but they're not escaped in the submitted XML versions, nor are they escaped in memory (in the $chunk_key)
689
690 # Make sure the submitted chunk still exists in the source file
691 if (!defined($source_file_key_to_text_mapping{$chunk_key})) {
692 &log_message("Warning: Source chunk $chunk_key no longer exists (ignoring submission).");
693 delete $source_file_key_to_submission_mapping{$chunk_key};
694 delete $target_file_key_to_submission_mapping{$chunk_key};
695 next;
696 }
697 ## DEBUG
698 # 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})) {
699 # &log_message("**** ORIG SRC: " . &unmake_text_xml_safe($source_file_key_to_text_mapping{$chunk_key}) ."\n");
700 # &log_message("**** SUBMIT SRC: " . &unmake_text_xml_safe($source_file_key_to_submission_mapping{$chunk_key}) . "\n");
701 # } else {
702 # &log_message("unmake-XML-safe versions of source chunk before and for submission match");
703 # }
704 ## END DEBUG
705
706 # Make sure the submitted source chunk matches the source file chunk
707 if ($source_file_key_to_submission_mapping{$chunk_key} ne $source_file_key_to_text_mapping{$chunk_key}
708 && $source_file_key_to_submission_mapping{$chunk_key} ne &unmake_text_xml_safe($source_file_key_to_text_mapping{$chunk_key})) {
709 #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})) {
710 #&log_message("**** submission source:\n|$source_file_key_to_submission_mapping{$chunk_key}|\n");
711 #&log_message("**** unmake xml safe source:\n|" . &unmake_text_xml_safe($source_file_key_to_text_mapping{$chunk_key}) ."|\n");
712
713 &log_message("Warning: Source chunk $chunk_key has changed (ignoring submission).");
714 &log_message("Submission source: |$source_file_key_to_submission_mapping{$chunk_key}|");
715 &log_message(" Source text: |$source_file_key_to_text_mapping{$chunk_key}|");
716 delete $source_file_key_to_submission_mapping{$chunk_key};
717 delete $target_file_key_to_submission_mapping{$chunk_key};
718 next;
719 }
720 }
721
722 # Apply the submitted translations
723 foreach my $chunk_key (keys(%target_file_key_to_submission_mapping)) {
724 # Only apply the submission if it is a change, unless -force_submission has been specified
725 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}) {
726 $target_file_key_to_text_mapping{$chunk_key} = $target_file_key_to_submission_mapping{$chunk_key};
727 $target_file_key_to_gti_comment_mapping{$chunk_key} = "Updated $submission_date by $submitter_username";
728 }
729 }
730
731 if ($translation_file_key !~ m/^gs3/) {
732 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)";
733 } else {
734 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)";
735 }
736}
737
738
739sub create_glihelp_zip_file
740{
741 my $target_language_code = shift(@_);
742 my $translation_file_key = "glihelp";
743
744 &log_message("Creating GLI Help zip file for $target_language_code");
745
746 my ($source_file, $target_file, $translation_file_type) = &get_translation_data_for($target_language_code, $translation_file_key);
747
748 my $classpath = &util::filename_cat($gsdl_root_directory, "gti-lib");
749 my $oldclasspath = $classpath;
750 if ( ! -e $classpath) {
751 $classpath = &util::filename_cat($gsdl_root_directory, "gli", "shared");
752 }
753 if ( ! -e $classpath) {
754 &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");
755 }
756
757
758 my $perllib_path = &util::filename_cat($gsdl_root_directory, "perllib"); # strings.properties
759 my $gliclasses_path = &util::filename_cat($gsdl_root_directory, "gli", "classes"); # dictionary.properties
760 my $os = $^O;
761 my $path_separator = ($^O =~ m/mswin/i) ? ";" : ":";
762 my $xalan_path = &util::filename_cat($classpath, "xalan.jar");
763 $classpath = "$perllib_path$path_separator$gliclasses_path$path_separator$classpath$path_separator$xalan_path";
764
765 my $gli_help_directory = &util::filename_cat($gsdl_root_directory, "gli");
766 $gli_help_directory = &util::filename_cat($gli_help_directory, "help");
767
768 my $gen_many_html_xsl_filepath = &util::filename_cat($gli_help_directory, "gen-many-html.xsl");
769 if ( ! -e $gen_many_html_xsl_filepath) {
770 &throw_fatal_error("$gen_many_html_xsl_filepath doesn't exist! Need this file to create the zip file for GLI Help");
771 }
772
773 my $gen_index_xml_xsl_filepath = &util::filename_cat($gli_help_directory, "gen-index-xml.xsl");
774 my $split_script_filepath = &util::filename_cat($gli_help_directory, "splithelpdocument.pl");
775
776 my $target_file_directory = &util::filename_cat($gli_help_directory, $target_language_code);
777 $target_file_directory = $target_file_directory."/";
778
779 my $target_filepath = &util::filename_cat($gsdl_root_directory, $target_file);
780
781 # if gli/help/nl doesn't exist, create it by copying over gli/help/en/help.xml, then process the copied file
782 my ($tailname, $glihelp_lang_dir, $suffix) = &File::Basename::fileparse($target_filepath, "\\.[^\\.]+\$");
783 if(!&FileUtils::directoryExists($glihelp_lang_dir)) {
784
785 # copy across the gli/help/en/help.xml into a new folder for the new language gli/help/<newlang>
786 my $en_glihelp_dir = &util::filename_cat($gli_help_directory, "en");
787 my $en_helpxml_file = &util::filename_cat($en_glihelp_dir, "$tailname$suffix"); #$tailname$suffix="help.xml"
788 &FileUtils::copyFilesRecursiveNoSVN($en_helpxml_file, $glihelp_lang_dir);
789
790 # The following file reading section is a candidate to use FileUtils::readUTF8File()
791 # in place of calling sysread() directly. But only if we can reason we'd be working with UTF8
792 # In gli/help/<newlang>/help.xml, replace all occurrences of
793 # <Text id="1">This text in en will be removed for new langcode</Text>
794 # with <!-- Missing translation: 1 -->
795 open(FIN,"<$target_filepath") or &throw_fatal_error("Could not open $target_filepath for READING after creating it");
796 my $help_xml_contents;
797 # Read in the entire contents of the file in one hit
798 sysread(FIN, $help_xml_contents, -s FIN);
799 close(FIN);
800
801 $help_xml_contents =~ s@<Text id="([^"]+?)">(.*?)</Text>@<!-- Missing translation: $1 -->@sg;
802
803 open(FOUT, ">$target_filepath") or &throw_fatal_error("Could not open $target_filepath for WRITING after creating it");
804 print FOUT $help_xml_contents;
805 close(FOUT);
806 }
807
808 my $perl_exec = &util::get_perl_exec();
809 my $java_exec = "java";
810 if(defined($ENV{'JAVA_HOME'}) && $ENV{'JAVA_HOME'} ne ""){
811 $java_exec = &util::filename_cat($ENV{'JAVA_HOME'}, "bin", "java");
812 } elsif(defined($ENV{'JRE_HOME'}) && $ENV{'JRE_HOME'} ne ""){
813 $java_exec = &util::filename_cat($ENV{'JRE_HOME'}, "bin", "java");
814 }
815
816 #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";
817 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";
818 #&throw_fatal_error("RAN gti command: $cmd");
819 my $response = `$cmd`;
820
821 #$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";
822 $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";
823 $response = `$cmd`;
824
825 # create a gti/tmp folder, if one doesn't already exist, and store the downloadable zip file in there
826 my $tmpdir = &util::filename_cat($gsdl_root_directory, "tmp");
827 if(!&FileUtils::directoryExists($tmpdir)) {
828 &FileUtils::makeDirectory($tmpdir);
829 }
830 #my $zip_file_path = "/greenstone/custom/gti/" . $target_language_code . "_GLIHelp.zip";
831 my $zip_file_path = &util::filename_cat($tmpdir, $target_language_code . "_GLIHelp.zip");
832 $cmd = "zip -rj $zip_file_path $target_file_directory -i \*.htm \*.xml";
833
834 $response = `$cmd`;
835}
836
837
838sub get_translation_configuration
839{
840 # Get the code of the target language
841 my $target_language_code = shift(@_);
842 # Get the key of the file to translate
843 my $translation_file_key = shift(@_);
844
845 # Read the translation data from the gti.cfg file
846 my ($source_file, $target_file, $translation_file_type) =
847 &get_translation_data_for($target_language_code, $translation_file_key);
848
849 # Check that the file to translate is defined in the gti.cfg file
850 if (!$source_file || !$target_file || !$translation_file_type) {
851 &throw_fatal_error("Missing or incomplete specification for translation file \"$translation_file_key\" in gti.pl.");
852 }
853
854 # Check that the source file exists
855 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
856 if (!-e $source_file_path) {
857 &throw_fatal_error("Source file $source_file_path does not exist.");
858 }
859
860 # Check that the source file is up to date
861 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
862 # unless ($translation_file_is_not_in_cvs) {
863 #my $source_file_cvs_status = `cd $gsdl_root_directory; cvs -d $anonymous_cvs_root update $source_file 2>/dev/null`;
864 my $source_file_cvs_status = `cd $gsdl_root_directory; svn status $source_file 2>/dev/null`;
865 if ($source_file_cvs_status =~ /^C /) {
866 &throw_fatal_error("Source file $source_file_path conflicts with the repository.");
867 }
868 if ($source_file_cvs_status =~ /^M /) {
869 &throw_fatal_error("Source file $source_file_path contains uncommitted changes.");
870 }
871 # }
872
873 return ($source_file, $target_file, $translation_file_type);
874}
875
876
877sub get_translation_data_for
878{
879 my ($target_language_code, $translation_file_key) = @_;
880
881 foreach my $translation_file (@$gti_translation_files) {
882 # If this isn't the correct translation file, move onto the next one
883 next if ($translation_file_key ne $translation_file->{'key'});
884
885 # Resolve the target language file
886 my $target_language_file = $translation_file->{'target_file'};
887 if ($target_language_file =~ /(\{.+\;.+\})/) {
888 my $unresolved_target_language_file_part = $1;
889
890 # Check for a special case for the target language code
891 if ($unresolved_target_language_file_part =~ /(\{|\;)$target_language_code:([^\;]+)(\;|\})/) {
892 my $resolved_target_language_file_part = $2;
893 $target_language_file =~ s/$unresolved_target_language_file_part/$resolved_target_language_file_part/;
894 }
895 # Otherwise use the last part as the default value
896 else {
897 my ($default_target_language_file_part) = $unresolved_target_language_file_part =~ /([^\;]+)\}/;
898 $target_language_file =~ s/$unresolved_target_language_file_part/\{$default_target_language_file_part\}/;
899 }
900 }
901
902 # Resolve instances of {iso_639_1_target_language_name}
903 my $iso_639_1_target_language_name = $iso639::fromiso639{$target_language_code};
904 $iso_639_1_target_language_name =~ tr/A-Z/a-z/ if $iso_639_1_target_language_name;
905 $target_language_file =~ s/\{iso_639_1_target_language_name\}/$iso_639_1_target_language_name/g;
906
907 # Resolve instances of {target_language_code}
908 $target_language_file =~ s/\{target_language_code\}/$target_language_code/g;
909
910 return ($translation_file->{'source_file'}, $target_language_file, $translation_file->{'file_type'});
911}
912
913return ();
914}
915
916
917sub read_file_lines
918{
919 my ($file_path) = @_;
920
921 if (!open(FILE_IN, "<$file_path")) {
922 &log_message("Note: Could not open file $file_path.");
923 return ();
924 }
925 my @file_lines = <FILE_IN>;
926 close(FILE_IN);
927
928 return @file_lines;
929}
930
931
932sub build_key_to_line_mapping
933{
934 my ($file_lines, $translation_file_type) = @_;
935 eval "return &build_key_to_line_mapping_for_${translation_file_type}(\@\$file_lines)";
936}
937
938
939sub build_key_to_text_mapping
940{
941 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
942
943 my %key_to_text_mapping = ();
944 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
945 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
946 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
947
948 my $chunk_text = @$file_lines[$chunk_starting_line];
949 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
950 $chunk_text .= @$file_lines[$l];
951 }
952
953 # Map from chunk key to text
954 eval "\$key_to_text_mapping{\${chunk_key}} = &import_chunk_from_${translation_file_type}(\$chunk_text)";
955
956 #&log_message("@@@ chunk key: $chunk_key");
957 #&log_message("@@@ source string: |" . $key_to_text_mapping{$chunk_key} . "|");
958
959
960 #if($chunk_key =~ m/document\\/) {
961 #&log_message("Submission source: $source_file_key_to_submission_mapping{$chunk_key}");
962 #&log_message("@@@ chunk key: $chunk_key");
963 #}
964
965 }
966
967 return %key_to_text_mapping;
968}
969
970
971sub build_key_to_last_update_date_mapping
972{
973 my ($file, $file_lines, $key_to_line_mapping, $translation_file_type) = @_;
974
975 # If the files aren't in CVS then we can't tell anything about what needs updating
976 # return () if ($translation_file_is_not_in_cvs);
977
978 # Build a mapping from key to CVS date
979 # Need to be careful with this mapping because the chunk keys won't necessarily all be valid
980 my %key_to_cvs_date_mapping = &build_key_to_cvs_date_mapping($file, $translation_file_type);
981
982 # Build a mapping from key to comment date
983 my %key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping($file_lines, $key_to_line_mapping, $translation_file_type);
984
985 # Build a mapping from key to last update date (the latter of the CVS date and comment date)
986 my %key_to_last_update_date_mapping = ();
987 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
988 # Use the CVS date as a starting point
989 my $chunk_cvs_date = $key_to_cvs_date_mapping{$chunk_key};
990 $key_to_last_update_date_mapping{$chunk_key} = $chunk_cvs_date;
991
992 # If a comment date exists and it is after the CVS date, use that instead
993 # need to convert the comment date format to SVN format
994 my $chunk_gti_comment = $key_to_gti_comment_mapping{$chunk_key};
995 if (defined($chunk_gti_comment) && $chunk_gti_comment =~ /(\d?\d-\D\D\D-\d\d\d\d)/) {
996 my $chunk_comment_date = $1;
997 if ((!defined($chunk_cvs_date) || &is_date_after($chunk_comment_date, $chunk_cvs_date))) {
998 $key_to_last_update_date_mapping{$chunk_key} = $chunk_comment_date;
999 }
1000 }
1001 }
1002
1003 return %key_to_last_update_date_mapping;
1004}
1005
1006
1007sub build_key_to_cvs_date_mapping
1008{
1009 my ($filename, $translation_file_type) = @_;
1010
1011 # Use SVN to annotate each line of the file with the date it was last edited
1012 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
1013 my $cvs_annotated_file = `cd $gsdl_root_directory; svn annotate -v --force $filename 2>/dev/null`;
1014
1015 my @cvs_annotated_file_lines = split(/\n/, $cvs_annotated_file);
1016
1017 my @cvs_annotated_file_lines_date = ();
1018 foreach my $cvs_annotated_file_line (@cvs_annotated_file_lines) {
1019 # Extract the date from the SVN annotation at the front
1020 # svn format : 2007-07-16
1021 $cvs_annotated_file_line =~ s/^\s+\S+\s+\S+\s(\S+)//;
1022
1023 push(@cvs_annotated_file_lines_date, $1);
1024
1025 # trim extra date information in svn annotation format
1026 # 15:42:49 +1200 (Wed, 21 Jun 2006)
1027 $cvs_annotated_file_line =~ s/^\s+\S+\s\S+\s\((.+?)\)\s//;
1028 }
1029
1030 # Build a key to line mapping for the CVS annotated file, for matching the chunk key to the CVS date
1031 my %key_to_line_mapping = &build_key_to_line_mapping(\@cvs_annotated_file_lines, $translation_file_type);
1032
1033 my %key_to_cvs_date_mapping = ();
1034 foreach my $chunk_key (keys(%key_to_line_mapping)) {
1035 my $chunk_starting_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[0];
1036 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[1];
1037
1038 # Find the date this chunk was last edited, from the CVS annotation
1039 my $chunk_date = $cvs_annotated_file_lines_date[$chunk_starting_line];
1040 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
1041 if (&is_date_after($cvs_annotated_file_lines_date[$l], $chunk_date)) {
1042 # This part of the chunk has been updated more recently
1043 $chunk_date = $cvs_annotated_file_lines_date[$l];
1044
1045 }
1046 }
1047
1048 # Map from chunk key to CVS date
1049 $key_to_cvs_date_mapping{$chunk_key} = $chunk_date;
1050 }
1051
1052 return %key_to_cvs_date_mapping;
1053}
1054
1055
1056sub build_key_to_gti_comment_mapping
1057{
1058 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
1059
1060 my %key_to_gti_comment_mapping = ();
1061 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
1062 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
1063 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
1064
1065 my $chunk_text = @$file_lines[$chunk_starting_line];
1066 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
1067 $chunk_text .= @$file_lines[$l];
1068 }
1069
1070 # Map from chunk key to GTI comment
1071 my $chunk_gti_comment;
1072 eval "\$chunk_gti_comment = &get_${translation_file_type}_chunk_gti_comment(\$chunk_text)";
1073 $key_to_gti_comment_mapping{$chunk_key} = $chunk_gti_comment if (defined($chunk_gti_comment));
1074 }
1075
1076 return %key_to_gti_comment_mapping;
1077}
1078
1079
1080sub determine_chunks_requiring_translation
1081{
1082 my $source_file_key_to_text_mapping = shift(@_);
1083 my $target_file_key_to_text_mapping = shift(@_);
1084
1085 # Chunks needing translation are those in the source file with no translation in the target file
1086 my @target_file_keys_requiring_translation = ();
1087 foreach my $chunk_key (keys(%$source_file_key_to_text_mapping)) {
1088 if ($source_file_key_to_text_mapping->{$chunk_key} && !$target_file_key_to_text_mapping->{$chunk_key}) {
1089 # &log_message("Chunk with key $chunk_key needs translating.");
1090 push(@target_file_keys_requiring_translation, $chunk_key);
1091 }
1092 }
1093
1094 return @target_file_keys_requiring_translation;
1095}
1096
1097
1098sub determine_chunks_requiring_updating
1099{
1100 my $source_file_key_to_last_update_date_mapping = shift(@_);
1101 my $target_file_key_to_last_update_date_mapping = shift(@_);
1102
1103 # Chunks needing updating are those in the target file that have been more recently edited in the source file
1104 my @target_file_keys_requiring_updating = ();
1105 foreach my $chunk_key (keys(%$source_file_key_to_last_update_date_mapping)) {
1106 my $source_chunk_last_update_date = $source_file_key_to_last_update_date_mapping->{$chunk_key};
1107 my $target_chunk_last_update_date = $target_file_key_to_last_update_date_mapping->{$chunk_key};
1108
1109 # 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";
1110
1111 if (defined($target_chunk_last_update_date) && &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
1112 # &log_message("Chunk with key $chunk_key needs updating.");
1113 # &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");
1114 push(@target_file_keys_requiring_updating, $chunk_key);
1115 }
1116 }
1117
1118 return @target_file_keys_requiring_updating;
1119}
1120
1121
1122sub is_chunk_automatically_translated
1123{
1124 my ($chunk_key, $translation_file_type) = @_;
1125 eval "return &is_${translation_file_type}_chunk_automatically_translated(\$chunk_key)";
1126}
1127
1128
1129sub make_text_xml_safe
1130{
1131 my $text = shift(@_);
1132 $text =~ s/\&/\&amp\;/g;
1133 $text =~ s/\&amp\;lt\;/\&amp\;amp\;lt\;/g;
1134 $text =~ s/\&amp\;gt\;/\&amp\;amp\;gt\;/g;
1135 $text =~ s/\&amp\;rarr\;/\&amp\;amp\;rarr\;/g;
1136 $text =~ s/\&amp\;mdash\;/\&amp\;amp\;mdash\;/g;
1137 $text =~ s/</\&lt\;/g;
1138 $text =~ s/>/\&gt\;/g;
1139 return $text;
1140}
1141
1142
1143sub unmake_text_xml_safe
1144{
1145 my $text = shift(@_);
1146 $text =~ s/\&lt\;/</g;
1147 $text =~ s/\&gt\;/>/g;
1148 $text =~ s/\&amp\;/\&/g;
1149 return $text;
1150}
1151
1152
1153# Returns 1 if $date1 is after $date2, 0 otherwise
1154sub is_date_after_cvs
1155{
1156 my ($date1, $date2) = @_;
1157 my %months = ("Jan", 1, "Feb", 2, "Mar", 3, "Apr", 4, "May", 5, "Jun", 6,
1158 "Jul", 7, "Aug", 8, "Sep", 9, "Oct", 10, "Nov", 11, "Dec", 12);
1159
1160 if(!defined $date1) {
1161 return 1;
1162 }
1163
1164 my @date1parts = split(/-/, $date1);
1165 my @date2parts = split(/-/, $date2);
1166
1167 # Compare year - nasty because we have rolled over into a new century
1168 my $year1 = $date1parts[2];
1169 if ($year1 < 80) {
1170 $year1 += 2000;
1171 }
1172 my $year2 = $date2parts[2];
1173 if ($year2 < 80) {
1174 $year2 += 2000;
1175 }
1176
1177 # Compare year
1178 if ($year1 > $year2) {
1179 return 1;
1180 }
1181 elsif ($year1 == $year2) {
1182 # Year is the same, so compare month
1183 if ($months{$date1parts[1]} > $months{$date2parts[1]}) {
1184 return 1;
1185 }
1186 elsif ($months{$date1parts[1]} == $months{$date2parts[1]}) {
1187 # Month is the same, so compare day
1188 if ($date1parts[0] > $date2parts[0]) {
1189 return 1;
1190 }
1191 }
1192 }
1193
1194 return 0;
1195}
1196
1197sub is_date_after
1198{
1199 my ($date1, $date2) = @_;
1200
1201 if(!defined $date1) {
1202 return 1;
1203 }
1204 if(!defined $date2) {
1205 return 0;
1206 }
1207
1208 # 16-Aug-2006
1209 if($date1=~ /(\d+?)-(\S\S\S)-(\d\d\d\d)/){
1210 my %months = ("Jan", "01", "Feb", "02", "Mar", "03", "Apr", "04", "May", "05", "Jun", "06",
1211 "Jul", "07", "Aug", "08", "Sep", "09", "Oct", "10", "Nov", "11", "Dec", "12");
1212 $date1=$3 . "-" . $months{$2} . "-" . $1;
1213 # print "** converted date1: $date1\n";
1214 }
1215 if($date2=~ /(\d+?)-(\S\S\S)-(\d\d\d\d)/){
1216 my %months = ("Jan", "01", "Feb", "02", "Mar", "03", "Apr", "04", "May", "05", "Jun", "06",
1217 "Jul", "07", "Aug", "08", "Sep", "09", "Oct", "10", "Nov", "11", "Dec", "12");
1218 $date2=$3 . "-" . $months{$2} . "-" . $1;
1219 # print "** converted date2: $date2\n";
1220 }
1221
1222
1223 # 2006-08-16
1224 my @date1parts = split(/-/, $date1);
1225 my @date2parts = split(/-/, $date2);
1226
1227 # Compare year
1228 if ($date1parts[0] > $date2parts[0]) {
1229 return 1;
1230 }
1231 elsif ($date1parts[0] == $date2parts[0]) {
1232 # Year is the same, so compare month
1233 if ($date1parts[1] > $date2parts[1]) {
1234 return 1;
1235 }
1236 elsif ($date1parts[1] == $date2parts[1]) {
1237 # Month is the same, so compare day
1238 if ($date1parts[2] > $date2parts[2]) {
1239 return 1;
1240 }
1241 }
1242 }
1243
1244 return 0;
1245}
1246
1247
1248sub create_xml_response_for_chunks_requiring_work
1249{
1250 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) = @_;
1251
1252 # Form an XML response to the command
1253 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1254 $xml_response .= "<GTIResponse>\n";
1255 $xml_response .= " <TranslationFile"
1256 . " key=\"" . $translation_file_key . "\""
1257 . " target_file_path=\"" . $target_file . "\""
1258 . " num_chunks_translated=\"" . ($total_num_chunks - scalar(@$target_files_keys_requiring_translation)) . "\""
1259 . " num_chunks_requiring_translation=\"" . scalar(@$target_files_keys_requiring_translation) . "\""
1260 . " num_chunks_requiring_updating=\"" . scalar(@$target_files_keys_requiring_updating) . "\"\/>\n";
1261
1262 # Do chunks requiring translation first
1263 if ($num_chunks_to_return > scalar(@$target_files_keys_requiring_translation)) {
1264 $xml_response .= " <ChunksRequiringTranslation size=\"" . scalar(@$target_files_keys_requiring_translation) . "\">\n";
1265 }
1266 else {
1267 $xml_response .= " <ChunksRequiringTranslation size=\"" . $num_chunks_to_return . "\">\n";
1268 }
1269
1270 my @sorted_chunk_keys = sort (@$target_files_keys_requiring_translation);
1271 foreach my $chunk_key (@sorted_chunk_keys) {
1272 last if ($num_chunks_to_return == 0);
1273
1274 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1275 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1276
1277 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1278 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1279 $xml_response .= " <TargetFileText></TargetFileText>\n";
1280 $xml_response .= " </Chunk>\n";
1281
1282 $num_chunks_to_return--;
1283 }
1284
1285 $xml_response .= " </ChunksRequiringTranslation>\n";
1286
1287 # Then do chunks requiring updating
1288 if ($num_chunks_to_return > scalar(@$target_files_keys_requiring_updating)) {
1289 $xml_response .= " <ChunksRequiringUpdating size=\"" . scalar(@$target_files_keys_requiring_updating) . "\">\n";
1290 }
1291 else {
1292 $xml_response .= " <ChunksRequiringUpdating size=\"" . $num_chunks_to_return . "\">\n";
1293 }
1294
1295 # foreach my $chunk_key (@target_file_keys_requiring_updating) {
1296 @sorted_chunk_keys = sort (@$target_files_keys_requiring_updating);
1297 foreach my $chunk_key (@sorted_chunk_keys) {
1298 last if ($num_chunks_to_return == 0);
1299
1300 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1301 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1302 my $target_file_chunk_date = $target_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1303 my $target_file_chunk_text = &make_text_xml_safe($target_files_key_to_text_mapping->{$chunk_key});
1304
1305 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1306 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1307 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1308 $xml_response .= " </Chunk>\n";
1309
1310 $num_chunks_to_return--;
1311 }
1312
1313 $xml_response .= " </ChunksRequiringUpdating>\n";
1314
1315 $xml_response .= "</GTIResponse>\n";
1316
1317 return $xml_response;
1318}
1319
1320sub create_xml_response_for_uptodate_chunks
1321{
1322 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) = @_;
1323
1324 # Form an XML response to the command
1325 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1326 $xml_response .= "<GTIResponse>\n";
1327 $xml_response .= " <TranslationFile"
1328 . " key=\"" . $translation_file_key . "\""
1329 . " target_file_path=\"" . $target_file . "\""
1330 . " num_chunks_uptodate=\"" . scalar(@$uptodate_target_files_keys) . "\"\/>\n";
1331
1332
1333 # Then do chunks requiring updating
1334 $xml_response .= " <UptodateChunks size=\"" . scalar(@$uptodate_target_files_keys) . "\">\n";
1335
1336
1337 # foreach my $chunk_key (@uptodate_target_file_keys) {
1338 my @sorted_chunk_keys = sort (@$uptodate_target_files_keys);
1339 foreach my $chunk_key (@sorted_chunk_keys) {
1340
1341 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1342 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1343 my $target_file_chunk_date = $target_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1344 my $target_file_chunk_text = &make_text_xml_safe($target_files_key_to_text_mapping->{$chunk_key});
1345
1346 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1347 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1348 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1349 $xml_response .= " </Chunk>\n";
1350
1351 }
1352
1353 $xml_response .= " </UptodateChunks>\n";
1354
1355 $xml_response .= "</GTIResponse>\n";
1356
1357 return $xml_response;
1358}
1359
1360sub create_xml_response_for_all_chunks
1361{
1362 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) = @_;
1363
1364 # Form an XML response to the command
1365 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1366 $xml_response .= "<GTIResponse>\n";
1367 $xml_response .= " <TranslationFile"
1368 . " key=\"" . $translation_file_key . "\""
1369 . " target_file_path=\"" . $target_file . "\"\/>\n";
1370
1371 # Do all the chunks
1372 $xml_response .= " <Chunks size=\"" . scalar(keys(%$source_file_key_to_text_mapping)) . "\">\n";
1373
1374 my @sorted_chunk_keys = sort (keys(%$source_file_key_to_text_mapping));
1375 foreach my $chunk_key (@sorted_chunk_keys) {
1376 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping->{$chunk_key} || "";
1377 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping->{$chunk_key});
1378
1379 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1380 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1381 if (defined($target_file_key_to_text_mapping->{$chunk_key})) {
1382 my $target_file_chunk_date = $target_file_key_to_last_update_date_mapping->{$chunk_key} || "";
1383 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping->{$chunk_key});
1384 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1385 }
1386 else {
1387 $xml_response .= " <TargetFileText></TargetFileText>\n";
1388 }
1389
1390 $xml_response .= " </Chunk>\n";
1391 }
1392 $xml_response .= " </Chunks>\n";
1393
1394 $xml_response .= "</GTIResponse>\n";
1395 return $xml_response;
1396}
1397
1398
1399
1400# ==========================================================================================
1401# MACROFILE FUNCTIONS
1402
1403sub build_key_to_line_mapping_for_macrofile
1404{
1405 my (@file_lines) = @_;
1406
1407 my $macro_package;
1408 my %chunk_key_to_line_mapping = ();
1409 # Process the contents of the file, line by line
1410 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1411 my $line = $file_lines[$i];
1412 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1413
1414 # Check if a new package is being defined
1415 if ($line =~ m/^package\s+(.+)/) {
1416 $macro_package = $1;
1417 }
1418
1419 # Line contains a macro name
1420 elsif ($line =~ m/^(_\w+_)/) {
1421 my $macro_key = $1;
1422 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
1423
1424 # While there is still text of the macro to go...
1425 my $startline = $i;
1426 while ($line !~ /\}$/) {
1427 $i++;
1428 if ($i == scalar(@file_lines)) {
1429 &throw_fatal_error("Could not find end of macro $macro_key.");
1430 }
1431 $line = $file_lines[$i];
1432 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
1433 }
1434
1435 # The chunk key consists of the package name and the macro key
1436 my $chunk_key = $macro_package . "." . $macro_key;
1437 # Map from chunk key to line
1438 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1439 }
1440
1441 # Icon: line in format ## "image text" ## image_type ## macro_name ##
1442 elsif ($line =~ m/^\#\# .* \#\# .* \#\# (.*) \#\#/) {
1443 # The chunk key consists of package name and macro key
1444 my $chunk_key = $macro_package . "." . $1;
1445 # Map from chunk key to line
1446 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
1447}
1448}
1449
1450return %chunk_key_to_line_mapping;
1451}
1452
1453
1454sub import_chunk_from_macrofile
1455{
1456 my ($chunk_text) = @_;
1457
1458 # Is this an icon macro??
1459 if ($chunk_text =~ /^\#\# (.*)/) {
1460 # Extract image macro text
1461 $chunk_text =~ /^\#\#\s+([^\#]+)\s+\#\#/;
1462 $chunk_text = $1;
1463
1464 # Remove enclosing quotes
1465 $chunk_text =~ s/^\"//;
1466 $chunk_text =~ s/\"$//;
1467 }
1468
1469 # No, so it must be a text macro
1470 else {
1471 # Remove macro key
1472 $chunk_text =~ s/^_([^_]+)_(\s*)//;
1473
1474 # Remove language specifier
1475 $chunk_text =~ s/^\[l=[^\]]*\](\s*)//; # only remove until first closing square bracket, ]
1476
1477 # Remove braces enclosing text
1478 $chunk_text =~ s/^{(\s*)((.|\n)*)}(\s*)(\#.+\s*)?/$2/;
1479 }
1480
1481 return $chunk_text;
1482}
1483
1484
1485sub get_macrofile_chunk_gti_comment
1486{
1487 my ($chunk_text) = @_;
1488
1489 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1490 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1491 return $1;
1492}
1493
1494return undef;
1495}
1496
1497
1498sub is_macrofile_chunk_automatically_translated
1499{
1500 my ($chunk_key) = @_;
1501
1502 # The _httpiconX_, _widthX_ and _heightX_ image macros are automatically translated
1503 if ($chunk_key =~ /\._(httpicon|width|height)/) {
1504 return 1;
1505 }
1506
1507 return 0;
1508}
1509
1510
1511# Use the source file to generate a target file that is formatted the same
1512sub write_translated_macrofile
1513{
1514 my $source_file = shift(@_); # Not used
1515 my @source_file_lines = @{shift(@_)};
1516 my $source_file_key_to_text_mapping = shift(@_);
1517 my $target_file = shift(@_);
1518 my @target_file_lines = @{shift(@_)};
1519 my $target_file_key_to_text_mapping = shift(@_);
1520 my $target_file_key_to_gti_comment_mapping = shift(@_);
1521 my $target_language_code = shift(@_);
1522
1523 # Build a mapping from source file line to chunk key
1524 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@source_file_lines);
1525 my %source_file_line_to_key_mapping = ();
1526 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1527 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1528 }
1529 my @source_file_line_keys = (sort sort_by_line (keys(%source_file_line_to_key_mapping)));
1530 my $source_file_line_number = 0;
1531
1532 # Build a mapping from target file line to chunk key
1533 my %target_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@target_file_lines);
1534 my %target_file_line_to_key_mapping = ();
1535 foreach my $chunk_key (keys(%target_file_key_to_line_mapping)) {
1536 $target_file_line_to_key_mapping{$target_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1537 }
1538 my @target_file_line_keys = (sort sort_by_line (keys(%target_file_line_to_key_mapping)));
1539
1540 # Write the new target file
1541 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1542 if (!open(TARGET_FILE, ">$target_file_path")) {
1543 &throw_fatal_error("Could not write target file $target_file_path.");
1544 }
1545
1546 # Use the header from the target file, to keep language and author information
1547 if (scalar(@target_file_line_keys) > 0) {
1548 my $target_file_line_number = 0;
1549 my $target_file_chunk_starting_line_number = (split(/-/, $target_file_line_keys[0]))[0];
1550 while ($target_file_line_number < $target_file_chunk_starting_line_number) {
1551 my $target_file_line = $target_file_lines[$target_file_line_number];
1552 last if ($target_file_line =~ /^\# -- Missing translation: /); # We don't want to get into the macros
1553 print TARGET_FILE $target_file_line;
1554 $target_file_line_number++;
1555 }
1556
1557 $source_file_line_number = (split(/-/, $source_file_line_keys[0]))[0];
1558 }
1559
1560 # Model the new target file on the source file, with the target file translations
1561 foreach my $line_key (@source_file_line_keys) {
1562 # Fill in the gaps before this chunk starts
1563 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1564 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1565 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1566 print TARGET_FILE $source_file_lines[$source_file_line_number];
1567 $source_file_line_number++;
1568 }
1569 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1570
1571 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1572 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1573 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1574
1575 my $macrofile_key = $chunk_key;
1576 $macrofile_key =~ s/^(.+?)\.//;
1577
1578 # If no translation exists for this chunk, show this, and move on
1579 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1580 print TARGET_FILE "# -- Missing translation: $macrofile_key\n";
1581 next;
1582 }
1583
1584 # Grab the source chunk text
1585 my $source_file_chunk = $source_file_lines[$source_file_chunk_starting_line_number];
1586 for (my $l = ($source_file_chunk_starting_line_number + 1); $l <= $source_file_chunk_finishing_line_number; $l++) {
1587 $source_file_chunk .= $source_file_lines[$l];
1588 }
1589
1590 # Is this an icon macro??
1591 if ($source_file_chunk =~ /^\#\# (.*)/) {
1592 # Escape any newline and question mark characters so the source text is replaced correctly
1593 $source_file_chunk_text =~ s/\\/\\\\/g;
1594 $source_file_chunk_text =~ s/\?/\\\?/g;
1595
1596 # Build the new target chunk from the source chunk
1597 my $target_file_chunk = $source_file_chunk;
1598 $target_file_chunk =~ s/$source_file_chunk_text/$target_file_chunk_text/;
1599 $target_file_chunk =~ s/(\s)*$//;
1600 print TARGET_FILE "$target_file_chunk";
1601 }
1602
1603 # No, it is just a normal text macro
1604 else {
1605 print TARGET_FILE "$macrofile_key [l=$target_language_code] {$target_file_chunk_text}";
1606 }
1607
1608 # Add the "updated" comment, if one exists
1609 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1610 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1611 }
1612 print TARGET_FILE "\n";
1613}
1614
1615close(TARGET_FILE);
1616}
1617
1618
1619sub sort_by_line
1620{
1621 return ((split(/-/, $a))[0] <=> (split(/-/, $b))[0]);
1622}
1623
1624
1625# ==========================================================================================
1626# RESOURCE BUNDLE FUNCTIONS
1627
1628# need to handle multi-line properties. A multiline ends on \ if it continues over the next line
1629sub build_key_to_line_mapping_for_resource_bundle
1630{
1631 my (@file_lines) = @_;
1632
1633 my %chunk_key_to_line_mapping = ();
1634
1635 my $chunk_key;
1636 my $startindex = -1;
1637
1638 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1639 my $line = $file_lines[$i];
1640 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1641 #&log_message("Got line: ".$line);
1642
1643 # a property line has a colon/equals sign as separator that is NOT escaped with a backslash (both keys and values
1644 # can use the colon or = sign. But in the key, such a char is always escaped. Unfortunately, they've not always been
1645 # escaped in the values. So we get the left most occurrence by not doing a greedy match (use ? to not be greedy).
1646 # So find the first :/= char not preceded by \. That will be the true separator of a chunk_key and its value chunk_text
1647
1648 if ($line =~ m/^(\S*?[^\\])[:|=](.*)$/) {
1649 # Line contains a dictionary string
1650
1651 # Unused but useful: http://stackoverflow.com/questions/87380/how-can-i-find-the-location-of-a-regex-match-in-perl
1652 # http://perldoc.perl.org/perlvar.html
1653
1654 $chunk_key = $1;
1655 # remove the escaping of any :/= property separator from the chunk_key in memory,
1656 # to make comparison with its unescaped version during submissions easier. Will write out with escaping.
1657 $chunk_key =~ s/\\([:=])/$1/g;
1658
1659 $startindex = $i;
1660 }
1661 if ($startindex != -1) {
1662 if($line !~ m/\\$/) { # line finished
1663 # $i keeps track of the line at which this property (chunk_key) finishes
1664
1665 # Map from chunk key to line
1666 $chunk_key_to_line_mapping{$chunk_key} = $startindex . "-" . $i;
1667 $startindex = -1;
1668 $chunk_key = "";
1669 }
1670 }
1671 }
1672
1673 return %chunk_key_to_line_mapping;
1674}
1675
1676
1677sub import_chunk_from_resource_bundle
1678{
1679 my ($chunk_text) = @_;
1680
1681 # Simple: just remove string key.
1682 # But key can contain an escaped separator (\: or \=).
1683 # So just as in the previous subroutine, find the first (leftmost) : or = char not preceded by \.
1684 # That will be the true separator of a chunk_key and its value chunk_text
1685 $chunk_text =~ s/^(\S*?[^\\])[:|=](\s*)//s;
1686
1687 $chunk_text =~ s/(\s*)$//s; # Remove any nasty whitespace, carriage returns etc.
1688 $chunk_text =~ s/(\s*)\#\s+Updated\s+(\d?\d-\D\D\D-\d\d\d\d.*)\s*$//is;
1689
1690 return $chunk_text;
1691}
1692
1693
1694sub get_resource_bundle_chunk_gti_comment
1695{
1696 my ($chunk_text) = @_;
1697
1698 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1699 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1700 return $1;
1701 }
1702
1703 return undef;
1704}
1705
1706
1707sub is_resource_bundle_chunk_automatically_translated
1708{
1709 # No resource bundle chunks are automatically translated
1710 return 0;
1711}
1712
1713
1714sub write_translated_resource_bundle
1715{
1716 my $source_file = shift(@_); # Not used
1717 my @source_file_lines = @{shift(@_)};
1718 my $source_file_key_to_text_mapping = shift(@_);
1719 my $target_file = shift(@_);
1720 my @target_file_lines = @{shift(@_)}; # Not used
1721 my $target_file_key_to_text_mapping = shift(@_);
1722 my $target_file_key_to_gti_comment_mapping = shift(@_);
1723 my $target_language_code = shift(@_); # Not used
1724
1725 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1726 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
1727 my %source_file_line_to_key_mapping = ();
1728 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1729 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1730 }
1731
1732 # Write the new target file
1733 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1734 if (!open(TARGET_FILE, ">$target_file_path")) {
1735 &throw_fatal_error("Could not write target file $target_file_path.");
1736 }
1737
1738 # Model the new target file on the source file, with the target file translations
1739 my $source_file_line_number = 0;
1740 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1741 # Fill in the gaps before this chunk starts
1742 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1743 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1744 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1745 print TARGET_FILE $source_file_lines[$source_file_line_number];
1746 $source_file_line_number++;
1747 }
1748 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1749
1750 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1751 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1752 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1753
1754 # make sure any : or = sign in the chunk key is escaped again (with \) when written out
1755 # since the key-value separator in a property resource bundle file is : or =
1756 my $escaped_chunk_key = $chunk_key;
1757 $escaped_chunk_key =~ s/(:|=)/\\$1/g; #$escaped_chunk_key =~ s/([^\\])(:|=)/\\$1$2/g;
1758
1759 # If no translation exists for this chunk, show this, and move on
1760 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1761 print TARGET_FILE "# -- Missing translation: $escaped_chunk_key\n";
1762 next;
1763 }
1764
1765 print TARGET_FILE "$escaped_chunk_key:$target_file_chunk_text";
1766 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1767 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1768 }
1769 print TARGET_FILE "\n";
1770 }
1771
1772 close(TARGET_FILE);
1773}
1774
1775
1776# ==========================================================================================
1777# GREENSTONE XML FUNCTIONS
1778
1779sub build_key_to_line_mapping_for_greenstone_xml
1780{
1781 my (@file_lines) = @_;
1782
1783 my %chunk_key_to_line_mapping = ();
1784 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1785 my $line = $file_lines[$i];
1786 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1787 #&log_message("Got line: ".$line);
1788 # Line contains a string to translate
1789 if ($line =~ /^\s*<Text id=\"(.*?)\">/) {
1790 my $chunk_key = $1;
1791 $line =~ s/\s*$//; # Remove any nasty whitespace
1792 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1793
1794 # While there is still text of the string to go...
1795 my $startline = $i;
1796 while ($line !~ /<\/Text>\s*$/) {
1797 $i++;
1798 if ($i == scalar(@file_lines)) {
1799 &throw_fatal_error("Could not find end of string $chunk_key.");
1800 }
1801 $line = $file_lines[$i];
1802 $line =~ s/\s*$//; # Remove any nasty whitespace
1803 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1804 }
1805 #&log_message("@@@ Line is now: ".$line);
1806 # Map from chunk key to line
1807 if (!defined($chunk_key_to_line_mapping{$chunk_key})) {
1808 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1809 }
1810 else {
1811 &throw_fatal_error("Duplicate key $chunk_key.");
1812 }
1813 }
1814 }
1815
1816 return %chunk_key_to_line_mapping;
1817}
1818
1819
1820sub import_chunk_from_greenstone_xml
1821{
1822 my ($chunk_text) = @_;
1823
1824 # Simple: just remove the Text tags
1825 $chunk_text =~ s/^\s*<Text id=\"(.*?)\">(\s*)//;
1826 $chunk_text =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1827 $chunk_text =~ s/\s*<\/Text>\s*$//;
1828
1829 return $chunk_text;
1830}
1831
1832
1833sub get_greenstone_xml_chunk_gti_comment
1834{
1835 my ($chunk_text) = @_;
1836
1837 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1838 if ($chunk_text =~ /<Updated date=\"(\d?\d-\D\D\D-\d\d\d\d.*)\"\/>$/i) {
1839 return $1;
1840 }
1841
1842 return undef;
1843}
1844
1845
1846sub is_greenstone_xml_chunk_automatically_translated
1847{
1848 # No greenstone XML chunks are automatically translated
1849 return 0;
1850}
1851
1852
1853sub write_translated_greenstone_xml
1854{
1855 my $source_file = shift(@_); # Not used
1856 my @source_file_lines = @{shift(@_)};
1857 my $source_file_key_to_text_mapping = shift(@_);
1858 my $target_file = shift(@_);
1859 my @target_file_lines = @{shift(@_)}; # Not used
1860 my $target_file_key_to_text_mapping = shift(@_);
1861 my $target_file_key_to_gti_comment_mapping = shift(@_);
1862 my $target_language_code = shift(@_); # Not used
1863
1864 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1865 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_greenstone_xml(@source_file_lines);
1866 my %source_file_line_to_key_mapping = ();
1867 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1868 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1869 }
1870
1871 # Write the new target file
1872 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1873 if (!open(TARGET_FILE, ">$target_file_path")) {
1874 &throw_fatal_error("Could not write target file $target_file_path.");
1875 }
1876
1877 # Model the new target file on the source file, with the target file translations
1878 my $source_file_line_number = 0;
1879 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1880 # Fill in the gaps before this chunk starts
1881 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1882 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1883 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1884 print TARGET_FILE $source_file_lines[$source_file_line_number];
1885 $source_file_line_number++;
1886 }
1887 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1888
1889 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1890 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1891 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1892 $target_file_chunk_text =~ s/(\n)*$//g;
1893
1894 # If no translation exists for this chunk, show this, and move on
1895 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1896 print TARGET_FILE "<!-- Missing translation: $chunk_key -->\n";
1897 next;
1898 }
1899
1900 print TARGET_FILE "<Text id=\"$chunk_key\">$target_file_chunk_text</Text>";
1901 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1902 my $chunk_gti_comment = $target_file_key_to_gti_comment_mapping->{$chunk_key};
1903 $chunk_gti_comment =~ s/^Updated //;
1904 print TARGET_FILE "<Updated date=\"" . $chunk_gti_comment . "\"\/>";
1905 }
1906 print TARGET_FILE "\n";
1907 }
1908
1909 # Fill in the end of the file
1910 while ($source_file_line_number < scalar(@source_file_lines)) {
1911 print TARGET_FILE $source_file_lines[$source_file_line_number];
1912 $source_file_line_number++;
1913 }
1914
1915 close(TARGET_FILE);
1916}
1917
1918
1919# ==========================================================================================
1920# GREENSTONE3 FUNCTIONS
1921
1922sub get_all_chunks_for_gs3
1923{
1924 # The code of the target language (ensure it is lowercase)
1925 my $target_language_code = lc(shift(@_));
1926 my $translation_file_key = lc(shift(@_));
1927
1928 # Check that the necessary arguments were supplied
1929 if (!$target_language_code) {
1930 &throw_fatal_error("Missing command argument.");
1931 }
1932
1933 # Get (and check) the translation configuration
1934 # my ($source_file_dir, $target_file, $translation_file_type) = &get_translation_configuration($target_language_code, $translation_file_key);
1935
1936 my %source_files_key_to_text_mapping = ();
1937 my %target_files_key_to_text_mapping = ();
1938 my %source_files_key_to_last_update_date_mapping = ();
1939 my %target_files_key_to_last_update_date_mapping = ();
1940
1941 &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);
1942
1943 &log_message("Total number of source chunks: " . scalar(keys(%source_files_key_to_text_mapping)));
1944 &log_message("Total number of target chunks: " . scalar(keys(%target_files_key_to_text_mapping)));
1945
1946 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);
1947 return $xml_response;
1948}
1949
1950
1951sub get_first_n_chunks_requiring_work_for_gs3
1952{
1953 # The code of the target language (ensure it is lowercase)
1954 my $target_language_code = lc(shift(@_));
1955 # The key of the file to translate (ensure it is lowercase)
1956 my $translation_file_key = lc(shift(@_));
1957 # The number of chunks to return (defaults to one if not specified)
1958 my $num_chunks_to_return = shift(@_) || "1";
1959
1960 # Check that the necessary arguments were supplied
1961 if (!$target_language_code || !$translation_file_key) {
1962 &throw_fatal_error("Missing command argument.");
1963 }
1964
1965 my %source_files_key_to_text_mapping = ();
1966 my %target_files_key_to_text_mapping = ();
1967 my %source_files_key_to_last_update_date_mapping = ();
1968 my %target_files_key_to_last_update_date_mapping = ();
1969
1970 &build_gs3_configuration($translation_file_key, $target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
1971 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1972
1973 # Determine the target file chunks requiring translation
1974 my @target_files_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping);
1975 # Determine the target file chunks requiring updating
1976 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);
1977 &log_message("Total number of target chunks requiring translation: " . scalar(@target_files_keys_requiring_translation));
1978 &log_message("Total number of target chunks requiring updating: " . scalar(@target_files_keys_requiring_updating));
1979
1980 my $download_target_filepath = "";
1981
1982
1983 # ****** DOWNLOADING LANGUAGE FILES WAS NOT YET IMPLEMENTED FOR GS3. RUDIMENTARY VERSION ****** #
1984
1985 # if there is no copy of the language files for download, there's also no link to the spreadsheet
1986 # for translating offline. So GS3's download option, we will zip up all the relevant greenstone 3
1987 # interface *.properties files,and link to that zip as the file for offline translation.
1988 # Selecting only properties files for English and the language they're working on (if the last exists)
1989
1990 # tar -cvzf gs3interface.tar.gz greenstone3/AbstractBrowse.properties greenstone3/AbstractBrowse_nl.properties
1991 # will generate a tar file containing a folder called "greenstone3" with the specified *.properties files
1992
1993 my $zip = &FileUtils::filenameConcatenate("tmp", "gs3interface_".$target_language_code.".tar.gz");
1994 my $tar_cmd = "tar -cvzf $zip";
1995
1996
1997 # store cur dir and cd to gsdlhome to generate the correct path in the zip file
1998 my $curdir = `pwd`;
1999 chdir $gsdl_root_directory;
2000
2001 $tar_cmd .= " " . &get_gs3_zip_file_listing($target_language_code, "greenstone3", \@gs3_interface_files);
2002 $tar_cmd .= " " . &get_gs3_zip_file_listing($target_language_code, "gs3-collection-configs", \@gs3_col_cfg_files);
2003
2004 # tar command will overwrite the previous version, but want to check we've created it
2005 if(&FileUtils::fileExists($zip)) {
2006 &FileUtils::removeFiles($zip);
2007 }
2008
2009 #my $tar_result = system($tar_cmd); # works but then interface breaks
2010 `$tar_cmd`;
2011 my $tar_result = $?;
2012
2013 if(&FileUtils::fileExists($zip)) { ## if($tar_result == 0) {, # breaks the interface
2014 $download_target_filepath = $zip;
2015 } else {
2016 &log_message("Unable to generate zip containing gs3interface files " . $download_target_filepath . "$!");
2017 }
2018
2019 # change back to original working directory (cgi-bin/linux probably)
2020 chdir $curdir;
2021
2022 # ************** END RUDIMENTARY VERSION OF DOWNLOADING LANGUAGE FILES FOR GS3 ************* #
2023
2024
2025 my $xml_response = &create_xml_response_for_chunks_requiring_work($translation_file_key, $download_target_filepath, scalar(keys(%source_files_key_to_text_mapping)),
2026 \@target_files_keys_requiring_translation, \@target_files_keys_requiring_updating,
2027 $num_chunks_to_return, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
2028 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
2029
2030 return $xml_response;
2031}
2032
2033# helper function
2034# gets the listing of gs3 files for a gs3 interface module (gs3interface, gs3colcfg)
2035# formatted correctly to go into a zip file
2036sub get_gs3_zip_file_listing
2037{
2038 my $target_language_code = shift(@_);
2039 my $sourcedir = shift(@_);
2040 my $files_array = shift(@_); # reference to an array of the interfaces files for the gs3 module
2041
2042 my $filelisting = "";
2043 foreach my $interface_file (@$files_array) {
2044
2045 my $source_filepath = &FileUtils::filenameConcatenate($sourcedir, $interface_file.".properties");
2046 my $target_filepath = &FileUtils::filenameConcatenate($sourcedir, $interface_file."_".$target_language_code.".properties");
2047
2048 $filelisting = "$filelisting $source_filepath";
2049 if(&FileUtils::fileExists($target_filepath)) {
2050 $filelisting = "$filelisting $target_filepath";
2051 }
2052 }
2053
2054 return $filelisting;
2055}
2056
2057sub get_uptodate_chunks_for_gs3
2058{
2059 # The code of the target language (ensure it is lowercase)
2060 my $target_language_code = lc(shift(@_));
2061 # The key of the file to translate (ensure it is lowercase)
2062 my $translation_file_key = lc(shift(@_));
2063 # The number of chunks to return (defaults to one if not specified)
2064 my $num_chunks_to_return = shift(@_) || "1";
2065
2066 # Check that the necessary arguments were supplied
2067 if (!$target_language_code || !$translation_file_key) {
2068 &throw_fatal_error("Missing command argument.");
2069 }
2070
2071 my %source_files_key_to_text_mapping = ();
2072 my %target_files_key_to_text_mapping = ();
2073 my %source_files_key_to_last_update_date_mapping = ();
2074 my %target_files_key_to_last_update_date_mapping = ();
2075
2076 &build_gs3_configuration($translation_file_key, $target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
2077 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
2078
2079
2080 # Chunks needing updating are those in the target file that have been more recently edited in the source file
2081 # All others are uptodate (which implies that they have certainly been translated at some point and would not be empty)
2082 my @uptodate_target_file_keys = ();
2083 foreach my $chunk_key (keys(%source_files_key_to_last_update_date_mapping)) {
2084 my $source_chunk_last_update_date = $source_files_key_to_last_update_date_mapping{$chunk_key};
2085 my $target_chunk_last_update_date = $target_files_key_to_last_update_date_mapping{$chunk_key};
2086
2087 # 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";
2088
2089 if (defined($target_chunk_last_update_date) && !&is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
2090 # &log_message("Chunk with key $chunk_key needs updating.");
2091 push(@uptodate_target_file_keys, $chunk_key);
2092 }
2093 }
2094
2095 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);
2096
2097 return $xml_response;
2098}
2099
2100
2101sub build_gs3_configuration
2102{
2103 my ($translation_file_key, $target_language_code, $source_files_key_to_text_mapping, $target_files_key_to_text_mapping,
2104 $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) = @_;
2105
2106 my $source_file_directory = "greenstone3"; # my $source_file_directory = &util::filename_cat("WEB-INF","classes");
2107 my $files_array = \@gs3_interface_files;
2108
2109 if($translation_file_key eq "gs3colcfg") {
2110 $source_file_directory = "gs3-collection-configs";
2111 $files_array = \@gs3_col_cfg_files;
2112 }
2113 my $translation_file_type = "resource_bundle";
2114
2115 foreach my $interface_file_key (@$files_array) {
2116
2117 &log_message("Greenstone 3 interface file: " . $interface_file_key);
2118
2119 # Parse the source language and target language files
2120 my $source_file = &util::filename_cat($source_file_directory, $interface_file_key.".properties");
2121 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
2122 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
2123 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
2124 #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);
2125
2126 my %source_file_key_to_gti_comment_or_last_updated_mapping;
2127 if($get_gti_comments_not_last_updated) {
2128 %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);
2129 } else {
2130 %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);
2131 }
2132
2133 my $target_file = &util::filename_cat($source_file_directory, $interface_file_key."_".$target_language_code.".properties");
2134 my @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
2135 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
2136 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
2137 #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);
2138
2139 my %target_file_key_to_gti_comment_or_last_updated_mapping;
2140 if($get_gti_comments_not_last_updated) {
2141 %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);
2142 } else {
2143 %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);
2144 }
2145
2146
2147 # Filter out any automatically translated chunks
2148 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
2149 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
2150 delete $source_file_key_to_line_mapping{$chunk_key};
2151 delete $target_file_key_to_line_mapping{$chunk_key};
2152 }
2153 }
2154
2155 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
2156 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
2157
2158 foreach my $chunk_key (keys(%source_file_key_to_text_mapping)) {
2159 my $global_chunk_key = "$interface_file_key.$chunk_key";
2160 $source_files_key_to_text_mapping->{$global_chunk_key} = $source_file_key_to_text_mapping{$chunk_key};
2161 $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};
2162
2163 if (defined $target_file_key_to_text_mapping{$chunk_key}) {
2164 $target_files_key_to_text_mapping->{$global_chunk_key} = $target_file_key_to_text_mapping{$chunk_key};
2165 $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};
2166 }
2167 }
2168 }
2169}
2170
2171
2172sub write_translated_gs3interface
2173{
2174 my $translation_file_key = shift(@_);
2175 my $source_file_key_to_text_mapping = shift(@_);
2176 my $target_file_key_to_text_mapping = shift(@_);
2177 my $target_file_key_to_gti_comment_mapping = shift(@_);
2178 my $target_language_code = shift(@_);
2179
2180 my @sorted_chunk_keys = sort (keys(%$source_file_key_to_text_mapping));
2181
2182 my %translated_interface_file_keys = ();
2183 foreach my $chunk_key (keys(%$target_file_key_to_text_mapping)) {
2184 $chunk_key =~ /^([^\.]+)?\.(.*)$/;
2185 if (!defined $translated_interface_file_keys{$1}) {
2186 &log_message("Updated interface file: " . $1);
2187 $translated_interface_file_keys{$1}="";
2188 }
2189 }
2190 &log_message("Updated interface files: " . scalar(keys(%translated_interface_file_keys)));
2191
2192 my $source_file_directory = "greenstone3";
2193 $source_file_directory = "gs3-collection-configs" if $translation_file_key eq "gs3colcfg";
2194
2195 foreach my $interface_file_key (keys(%translated_interface_file_keys)) {
2196
2197 # Build a mapping from chunk key to source file line, and from source file line to chunk key
2198 my $source_file = &util::filename_cat($source_file_directory, "$interface_file_key.properties");
2199 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
2200 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
2201 my %source_file_line_to_key_mapping = ();
2202 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
2203 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
2204 }
2205
2206 # Write the new target file
2207 my $target_file = &util::filename_cat($source_file_directory, $interface_file_key . "_" . $target_language_code . ".properties");
2208 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
2209 if (!open(TARGET_FILE, ">$target_file_path")) {
2210 &throw_fatal_error("Could not write target file $target_file_path.");
2211 }
2212
2213 # Model the new target file on the source file, with the target file translations
2214 my $source_file_line_number = 0;
2215 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
2216 # Fill in the gaps before this chunk starts
2217 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
2218 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
2219 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
2220 print TARGET_FILE $source_file_lines[$source_file_line_number];
2221 $source_file_line_number++;
2222 }
2223 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
2224
2225 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
2226 my $global_chunk_key = "$interface_file_key.$chunk_key";
2227 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$global_chunk_key};
2228 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$global_chunk_key} || "";
2229
2230 # make sure any : or = sign in the chunk key is escaped again (with \) when written out
2231 # since the key-value separator in a property resource bundle file is : or =
2232 my $escaped_chunk_key = $chunk_key;
2233 $escaped_chunk_key =~ s/(:|=)/\\$1/g; #$escaped_chunk_key =~ s/([^\\])(:|=)/\\$1$2/g;
2234
2235 # If no translation exists for this chunk, show this, and move on
2236 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
2237 print TARGET_FILE "# -- Missing translation: $escaped_chunk_key\n";
2238 next;
2239 }
2240
2241 print TARGET_FILE "$escaped_chunk_key:$target_file_chunk_text";
2242 if ($target_file_key_to_gti_comment_mapping->{$global_chunk_key}) {
2243 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$global_chunk_key};
2244 }
2245 print TARGET_FILE "\n";
2246 }
2247
2248 close(TARGET_FILE);
2249 }
2250}
2251
2252&main(@ARGV);
Note: See TracBrowser for help on using the repository browser.