root/main/trunk/greenstone2/bin/script/gti.pl @ 25249

Revision 25249, 83.5 KB (checked in by ak19, 8 years ago)

Added code to allow us to get all the up to date strings, instead of just all the strings (but unsorted), or just the strings that require translating/updating. We can create translation memory files (TMX) from all the up to date strings so that can translators using the Google Translator Toolkit can use the strings and string segments as a reference while translating the strings that still require updating/translating. If the TMX file is shared in the GTT, these strings and their segments will also be available to help translators of other application interfaces to use common and consistent terminology.

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