Initialize Tizen 2.3
[framework/base/gconf-dbus.git] / intltool-merge.in
1 #!@INTLTOOL_PERL@ -w
2 # -*- Mode: perl; indent-tabs-mode: nil; c-basic-offset: 4  -*-
3
4 #
5 #  The Intltool Message Merger
6 #
7 #  Copyright (C) 2000, 2003 Free Software Foundation.
8 #  Copyright (C) 2000, 2001 Eazel, Inc
9 #
10 #  Intltool is free software; you can redistribute it and/or
11 #  modify it under the terms of the GNU General Public License 
12 #  version 2 published by the Free Software Foundation.
13 #
14 #  Intltool is distributed in the hope that it will be useful,
15 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 #  General Public License for more details.
18 #
19 #  You should have received a copy of the GNU General Public License
20 #  along with this program; if not, write to the Free Software
21 #  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #
23 #  As a special exception to the GNU General Public License, if you
24 #  distribute this file as part of a program that contains a
25 #  configuration script generated by Autoconf, you may include it under
26 #  the same distribution terms that you use for the rest of that program.
27 #
28 #  Authors:  Maciej Stachowiak <mjs@noisehavoc.org>
29 #            Kenneth Christiansen <kenneth@gnu.org>
30 #            Darin Adler <darin@bentspoon.com>
31 #
32 #  Proper XML UTF-8'ification written by Cyrille Chepelov <chepelov@calixo.net>
33 #
34
35 ## Release information
36 my $PROGRAM = "intltool-merge";
37 my $PACKAGE = "intltool";
38 my $VERSION = "0.36.1";
39
40 ## Loaded modules
41 use strict; 
42 use Getopt::Long;
43 use Text::Wrap;
44 use File::Basename;
45
46 my $must_end_tag      = -1;
47 my $last_depth        = -1;
48 my $translation_depth = -1;
49 my @tag_stack = ();
50 my @entered_tag = ();
51 my @translation_strings = ();
52 my $leading_space = "";
53
54 ## Scalars used by the option stuff
55 my $HELP_ARG = 0;
56 my $VERSION_ARG = 0;
57 my $BA_STYLE_ARG = 0;
58 my $XML_STYLE_ARG = 0;
59 my $KEYS_STYLE_ARG = 0;
60 my $DESKTOP_STYLE_ARG = 0;
61 my $SCHEMAS_STYLE_ARG = 0;
62 my $RFC822DEB_STYLE_ARG = 0;
63 my $QUOTED_STYLE_ARG = 0;
64 my $QUIET_ARG = 0;
65 my $PASS_THROUGH_ARG = 0;
66 my $UTF8_ARG = 0;
67 my $MULTIPLE_OUTPUT = 0;
68 my $cache_file;
69
70 ## Handle options
71 GetOptions 
72 (
73  "help" => \$HELP_ARG,
74  "version" => \$VERSION_ARG,
75  "quiet|q" => \$QUIET_ARG,
76  "oaf-style|o" => \$BA_STYLE_ARG, ## for compatibility
77  "ba-style|b" => \$BA_STYLE_ARG,
78  "xml-style|x" => \$XML_STYLE_ARG,
79  "keys-style|k" => \$KEYS_STYLE_ARG,
80  "desktop-style|d" => \$DESKTOP_STYLE_ARG,
81  "schemas-style|s" => \$SCHEMAS_STYLE_ARG,
82  "rfc822deb-style|r" => \$RFC822DEB_STYLE_ARG,
83  "quoted-style" => \$QUOTED_STYLE_ARG,
84  "pass-through|p" => \$PASS_THROUGH_ARG,
85  "utf8|u" => \$UTF8_ARG,
86  "multiple-output|m" => \$MULTIPLE_OUTPUT,
87  "cache|c=s" => \$cache_file
88  ) or &error;
89
90 my $PO_DIR;
91 my $FILE;
92 my $OUTFILE;
93
94 my %po_files_by_lang = ();
95 my %translations = ();
96 my $iconv = $ENV{"ICONV"} || "iconv";
97 my $devnull = ($^O eq 'MSWin32' ? 'NUL:' : '/dev/null');
98
99 sub isProgramInPath
100 {
101     my ($file) = @_;
102     # If either a file exists, or when run it returns 0 exit status
103     return 1 if ((-x $file) or (system("$file -l >$devnull") == 0));
104     return 0;
105 }
106
107 if (! isProgramInPath ("$iconv"))
108 {
109         print STDERR " *** iconv is not found on this system!\n".
110                      " *** Without it, intltool-merge can not convert encodings.\n";
111         exit;
112 }
113
114 # Use this instead of \w for XML files to handle more possible characters.
115 my $w = "[-A-Za-z0-9._:]";
116
117 # XML quoted string contents
118 my $q = "[^\\\"]*";
119
120 ## Check for options. 
121
122 if ($VERSION_ARG) 
123 {
124         &print_version;
125
126 elsif ($HELP_ARG) 
127 {
128         &print_help;
129
130 elsif ($BA_STYLE_ARG && @ARGV > 2) 
131 {
132         &utf8_sanity_check;
133         &preparation;
134         &print_message;
135         &ba_merge_translations;
136         &finalize;
137
138 elsif ($XML_STYLE_ARG && @ARGV > 2) 
139 {
140         &utf8_sanity_check;
141         &preparation;
142         &print_message;
143         &xml_merge_output;
144         &finalize;
145
146 elsif ($KEYS_STYLE_ARG && @ARGV > 2) 
147 {
148         &utf8_sanity_check;
149         &preparation;
150         &print_message;
151         &keys_merge_translations;
152         &finalize;
153
154 elsif ($DESKTOP_STYLE_ARG && @ARGV > 2) 
155 {
156         &utf8_sanity_check;
157         &preparation;
158         &print_message;
159         &desktop_merge_translations;
160         &finalize;
161
162 elsif ($SCHEMAS_STYLE_ARG && @ARGV > 2) 
163 {
164         &utf8_sanity_check;
165         &preparation;
166         &print_message;
167         &schemas_merge_translations;
168         &finalize;
169
170 elsif ($RFC822DEB_STYLE_ARG && @ARGV > 2) 
171 {
172         &preparation;
173         &print_message;
174         &rfc822deb_merge_translations;
175         &finalize;
176
177 elsif ($QUOTED_STYLE_ARG && @ARGV > 2) 
178 {
179         &utf8_sanity_check;
180         &preparation;
181         &print_message;
182         &quoted_merge_translations;
183         &finalize;
184
185 else 
186 {
187         &print_help;
188 }
189
190 exit;
191
192 ## Sub for printing release information
193 sub print_version
194 {
195     print <<_EOF_;
196 ${PROGRAM} (${PACKAGE}) ${VERSION}
197 Written by Maciej Stachowiak, Darin Adler and Kenneth Christiansen.
198
199 Copyright (C) 2000-2003 Free Software Foundation, Inc.
200 Copyright (C) 2000-2001 Eazel, Inc.
201 This is free software; see the source for copying conditions.  There is NO
202 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
203 _EOF_
204     exit;
205 }
206
207 ## Sub for printing usage information
208 sub print_help
209 {
210     print <<_EOF_;
211 Usage: ${PROGRAM} [OPTION]... PO_DIRECTORY FILENAME OUTPUT_FILE
212 Generates an output file that includes some localized attributes from an
213 untranslated source file.
214
215 Mandatory options: (exactly one must be specified)
216   -b, --ba-style         includes translations in the bonobo-activation style
217   -d, --desktop-style    includes translations in the desktop style
218   -k, --keys-style       includes translations in the keys style
219   -s, --schemas-style    includes translations in the schemas style
220   -r, --rfc822deb-style  includes translations in the RFC822 style
221       --quoted-style     includes translations in the quoted string style
222   -x, --xml-style        includes translations in the standard xml style
223
224 Other options:
225   -u, --utf8             convert all strings to UTF-8 before merging 
226                          (default for everything except RFC822 style)
227   -p, --pass-through     deprecated, does nothing and issues a warning
228   -m, --multiple-output  output one localized file per locale, instead of 
229                          a single file containing all localized elements
230   -c, --cache=FILE       specify cache file name
231                          (usually \$top_builddir/po/.intltool-merge-cache)
232   -q, --quiet            suppress most messages
233       --help             display this help and exit
234       --version          output version information and exit
235
236 Report bugs to http://bugzilla.gnome.org/ (product name "$PACKAGE")
237 or send email to <xml-i18n-tools\@gnome.org>.
238 _EOF_
239     exit;
240 }
241
242
243 ## Sub for printing error messages
244 sub print_error
245 {
246     print STDERR "Try `${PROGRAM} --help' for more information.\n";
247     exit;
248 }
249
250
251 sub print_message 
252 {
253     print "Merging translations into $OUTFILE.\n" unless $QUIET_ARG;
254 }
255
256
257 sub preparation 
258 {
259     $PO_DIR = $ARGV[0];
260     $FILE = $ARGV[1];
261     $OUTFILE = $ARGV[2];
262
263     &gather_po_files;
264     &get_translation_database;
265 }
266
267 # General-purpose code for looking up translations in .po files
268
269 sub po_file2lang
270 {
271     my ($tmp) = @_; 
272     $tmp =~ s/^.*\/(.*)\.po$/$1/; 
273     return $tmp; 
274 }
275
276 sub gather_po_files
277 {
278     if (my $linguas = $ENV{"LINGUAS"})
279     {
280         for my $lang (split / /, $linguas) {
281             my $po_file = $PO_DIR . "/" . $lang . ".po";
282             if (-e $po_file) {
283                 $po_files_by_lang{$lang} = $po_file;
284             }
285         }
286     }
287     else
288     {
289         if (open LINGUAS_FILE, "$PO_DIR/LINGUAS")
290         {
291             while (<LINGUAS_FILE>)
292             {
293                 next if /^#/;
294
295                 for my $lang (split)
296                 {
297                     chomp ($lang);
298                     my $po_file = $PO_DIR . "/" . $lang . ".po";
299                     if (-e $po_file) {
300                         $po_files_by_lang{$lang} = $po_file;
301                     }
302                 }
303             }
304
305             close LINGUAS_FILE;
306         }
307         else
308         {
309             for my $po_file (glob "$PO_DIR/*.po") {
310                 $po_files_by_lang{po_file2lang($po_file)} = $po_file;
311             }
312         }
313     }
314 }
315
316 sub get_local_charset
317 {
318     my ($encoding) = @_;
319     my $alias_file = $ENV{"G_CHARSET_ALIAS"} || "@INTLTOOL_LIBDIR@/charset.alias";
320
321     # seek character encoding aliases in charset.alias (glib)
322
323     if (open CHARSET_ALIAS, $alias_file) 
324     {
325         while (<CHARSET_ALIAS>) 
326         {
327             next if /^\#/;
328             return $1 if (/^\s*([-._a-zA-Z0-9]+)\s+$encoding\b/i)
329         }
330
331         close CHARSET_ALIAS;
332     }
333
334     # if not found, return input string
335
336     return $encoding;
337 }
338
339 sub get_po_encoding
340 {
341     my ($in_po_file) = @_;
342     my $encoding = "";
343
344     open IN_PO_FILE, $in_po_file or die;
345     while (<IN_PO_FILE>) 
346     {
347         ## example: "Content-Type: text/plain; charset=ISO-8859-1\n"
348         if (/Content-Type\:.*charset=([-a-zA-Z0-9]+)\\n/) 
349         {
350             $encoding = $1; 
351             last;
352         }
353     }
354     close IN_PO_FILE;
355
356     if (!$encoding) 
357     {
358         print STDERR "Warning: no encoding found in $in_po_file. Assuming ISO-8859-1\n" unless $QUIET_ARG;
359         $encoding = "ISO-8859-1";
360     }
361
362     system ("$iconv -f $encoding -t UTF-8 <$devnull 2>$devnull");
363     if ($?) {
364         $encoding = get_local_charset($encoding);
365     }
366
367     return $encoding
368 }
369
370 sub utf8_sanity_check 
371 {
372     print STDERR "Warning: option --pass-through has been removed.\n" if $PASS_THROUGH_ARG;
373     $UTF8_ARG = 1;
374 }
375
376 sub get_translation_database
377 {
378     if ($cache_file) {
379         &get_cached_translation_database;
380     } else {
381         &create_translation_database;
382     }
383 }
384
385 sub get_newest_po_age
386 {
387     my $newest_age;
388
389     foreach my $file (values %po_files_by_lang) 
390     {
391         my $file_age = -M $file;
392         $newest_age = $file_age if !$newest_age || $file_age < $newest_age;
393     }
394
395     $newest_age = 0 if !$newest_age;
396
397     return $newest_age;
398 }
399
400 sub create_cache
401 {
402     print "Generating and caching the translation database\n" unless $QUIET_ARG;
403
404     &create_translation_database;
405
406     open CACHE, ">$cache_file" || die;
407     print CACHE join "\x01", %translations;
408     close CACHE;
409 }
410
411 sub load_cache 
412 {
413     print "Found cached translation database\n" unless $QUIET_ARG;
414
415     my $contents;
416     open CACHE, "<$cache_file" || die;
417     {
418         local $/;
419         $contents = <CACHE>;
420     }
421     close CACHE;
422     %translations = split "\x01", $contents;
423 }
424
425 sub get_cached_translation_database
426 {
427     my $cache_file_age = -M $cache_file;
428     if (defined $cache_file_age) 
429     {
430         if ($cache_file_age <= &get_newest_po_age) 
431         {
432             &load_cache;
433             return;
434         }
435         print "Found too-old cached translation database\n" unless $QUIET_ARG;
436     }
437
438     &create_cache;
439 }
440
441 sub create_translation_database
442 {
443     for my $lang (keys %po_files_by_lang) 
444     {
445         my $po_file = $po_files_by_lang{$lang};
446
447         if ($UTF8_ARG) 
448         {
449             my $encoding = get_po_encoding ($po_file);
450
451             if (lc $encoding eq "utf-8") 
452             {
453                 open PO_FILE, "<$po_file";      
454             } 
455             else 
456             {
457                 print "NOTICE: $po_file is not in UTF-8 but $encoding, converting...\n" unless $QUIET_ARG;;
458
459                 open PO_FILE, "$iconv -f $encoding -t UTF-8 $po_file|"; 
460             }
461         } 
462         else 
463         {
464             open PO_FILE, "<$po_file";  
465         }
466
467         my $nextfuzzy = 0;
468         my $inmsgid = 0;
469         my $inmsgstr = 0;
470         my $msgid = "";
471         my $msgstr = "";
472
473         while (<PO_FILE>) 
474         {
475             $nextfuzzy = 1 if /^#, fuzzy/;
476        
477             if (/^msgid "((\\.|[^\\]+)*)"/ ) 
478             {
479                 $translations{$lang, $msgid} = $msgstr if $inmsgstr && $msgid && $msgstr;
480                 $msgid = "";
481                 $msgstr = "";
482
483                 if ($nextfuzzy) {
484                     $inmsgid = 0;
485                 } else {
486                     $msgid = unescape_po_string($1);
487                     $inmsgid = 1;
488                 }
489                 $inmsgstr = 0;
490                 $nextfuzzy = 0;
491             }
492
493             if (/^msgstr "((\\.|[^\\]+)*)"/) 
494             {
495                 $msgstr = unescape_po_string($1);
496                 $inmsgstr = 1;
497                 $inmsgid = 0;
498             }
499
500             if (/^"((\\.|[^\\]+)*)"/) 
501             {
502                 $msgid .= unescape_po_string($1) if $inmsgid;
503                 $msgstr .= unescape_po_string($1) if $inmsgstr;
504             }
505         }
506         $translations{$lang, $msgid} = $msgstr if $inmsgstr && $msgid && $msgstr;
507     }
508 }
509
510 sub finalize
511 {
512 }
513
514 sub unescape_one_sequence
515 {
516     my ($sequence) = @_;
517
518     return "\\" if $sequence eq "\\\\";
519     return "\"" if $sequence eq "\\\"";
520     return "\n" if $sequence eq "\\n";
521     return "\r" if $sequence eq "\\r";
522     return "\t" if $sequence eq "\\t";
523     return "\b" if $sequence eq "\\b";
524     return "\f" if $sequence eq "\\f";
525     return "\a" if $sequence eq "\\a";
526     return chr(11) if $sequence eq "\\v"; # vertical tab, see ascii(7)
527
528     return chr(hex($1)) if ($sequence =~ /\\x([0-9a-fA-F]{2})/);
529     return chr(oct($1)) if ($sequence =~ /\\([0-7]{3})/);
530
531     # FIXME: Is \0 supported as well? Kenneth and Rodney don't want it, see bug #48489
532
533     return $sequence;
534 }
535
536 sub unescape_po_string
537 {
538     my ($string) = @_;
539
540     $string =~ s/(\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\.)/unescape_one_sequence($1)/eg;
541
542     return $string;
543 }
544
545 sub entity_decode
546 {
547     local ($_) = @_;
548
549     s/&apos;/'/g; # '
550     s/&quot;/"/g; # "
551     s/&lt;/</g;
552     s/&gt;/>/g;
553     s/&amp;/&/g;
554
555     return $_;
556 }
557  
558 # entity_encode: (string)
559 #
560 # Encode the given string to XML format (encode '<' etc).
561
562 sub entity_encode
563 {
564     my ($pre_encoded) = @_;
565
566     my @list_of_chars = unpack ('C*', $pre_encoded);
567
568     # with UTF-8 we only encode minimalistic
569     return join ('', map (&entity_encode_int_minimalist, @list_of_chars));
570 }
571
572 sub entity_encode_int_minimalist
573 {
574     return "&quot;" if $_ == 34;
575     return "&amp;" if $_ == 38;
576     return "&apos;" if $_ == 39;
577     return "&lt;" if $_ == 60;
578     return chr $_;
579 }
580
581 sub entity_encoded_translation
582 {
583     my ($lang, $string) = @_;
584
585     my $translation = $translations{$lang, $string};
586     return $string if !$translation;
587     return entity_encode ($translation);
588 }
589
590 ## XML (bonobo-activation specific) merge code
591
592 sub ba_merge_translations
593 {
594     my $source;
595
596     {
597        local $/; # slurp mode
598        open INPUT, "<$FILE" or die "can't open $FILE: $!";
599        $source = <INPUT>;
600        close INPUT;
601     }
602
603     open OUTPUT, ">$OUTFILE" or die "can't open $OUTFILE: $!";
604     # Binmode so that selftest works ok if using a native Win32 Perl...
605     binmode (OUTPUT) if $^O eq 'MSWin32';
606
607     while ($source =~ s|^(.*?)([ \t]*<\s*$w+\s+($w+\s*=\s*"$q"\s*)+/?>)([ \t]*\n)?||s) 
608     {
609         print OUTPUT $1;
610
611         my $node = $2 . "\n";
612
613         my @strings = ();
614         $_ = $node;
615         while (s/(\s)_($w+\s*=\s*"($q)")/$1$2/s) {
616              push @strings, entity_decode($3);
617         }
618         print OUTPUT;
619
620         my %langs;
621         for my $string (@strings) 
622         {
623             for my $lang (keys %po_files_by_lang) 
624             {
625                 $langs{$lang} = 1 if $translations{$lang, $string};
626             }
627         }
628         
629         for my $lang (sort keys %langs) 
630         {
631             $_ = $node;
632             s/(\sname\s*=\s*)"($q)"/$1"$2-$lang"/s;
633             s/(\s)_($w+\s*=\s*")($q)"/$1 . $2 . entity_encoded_translation($lang, $3) . '"'/seg;
634             print OUTPUT;
635         }
636     }
637
638     print OUTPUT $source;
639
640     close OUTPUT;
641 }
642
643
644 ## XML (non-bonobo-activation) merge code
645
646
647 # Process tag attributes
648 #   Only parameter is a HASH containing attributes -> values mapping
649 sub getAttributeString
650 {
651     my $sub = shift;
652     my $do_translate = shift || 0;
653     my $language = shift || "";
654     my $result = "";
655     my $translate = shift;
656     foreach my $e (reverse(sort(keys %{ $sub }))) {
657         my $key    = $e;
658         my $string = $sub->{$e};
659         my $quote = '"';
660         
661         $string =~ s/^[\s]+//;
662         $string =~ s/[\s]+$//;
663         
664         if ($string =~ /^'.*'$/)
665         {
666             $quote = "'";
667         }
668         $string =~ s/^['"]//g;
669         $string =~ s/['"]$//g;
670
671         if ($do_translate && $key =~ /^_/) {
672             $key =~ s|^_||g;
673             if ($language) {
674                 # Handle translation
675                 my $decode_string = entity_decode($string);
676                 my $translation = $translations{$language, $decode_string};
677                 if ($translation) {
678                     $translation = entity_encode($translation);
679                     $string = $translation;
680                 }
681                 $$translate = 2;
682             } else {
683                  $$translate = 2 if ($translate && (!$$translate)); # watch not to "overwrite" $translate
684             }
685         }
686         
687         $result .= " $key=$quote$string$quote";
688     }
689     return $result;
690 }
691
692 # Returns a translatable string from XML node, it works on contents of every node in XML::Parser tree
693 sub getXMLstring
694 {
695     my $ref = shift;
696     my $spacepreserve = shift || 0;
697     my @list = @{ $ref };
698     my $result = "";
699
700     my $count = scalar(@list);
701     my $attrs = $list[0];
702     my $index = 1;
703
704     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
705     $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
706
707     while ($index < $count) {
708         my $type = $list[$index];
709         my $content = $list[$index+1];
710         if (! $type ) {
711             # We've got CDATA
712             if ($content) {
713                 # lets strip the whitespace here, and *ONLY* here
714                 $content =~ s/\s+/ /gs if (!$spacepreserve);
715                 $result .= $content;
716             }
717         } elsif ( "$type" ne "1" ) {
718             # We've got another element
719             $result .= "<$type";
720             $result .= getAttributeString(@{$content}[0], 0); # no nested translatable elements
721             if ($content) {
722                 my $subresult = getXMLstring($content, $spacepreserve);
723                 if ($subresult) {
724                     $result .= ">".$subresult . "</$type>";
725                 } else {
726                     $result .= "/>";
727                 }
728             } else {
729                 $result .= "/>";
730             }
731         }
732         $index += 2;
733     }
734     return $result;
735 }
736
737 # Translate list of nodes if necessary
738 sub translate_subnodes
739 {
740     my $fh = shift;
741     my $content = shift;
742     my $language = shift || "";
743     my $singlelang = shift || 0;
744     my $spacepreserve = shift || 0;
745
746     my @nodes = @{ $content };
747
748     my $count = scalar(@nodes);
749     my $index = 0;
750     while ($index < $count) {
751         my $type = $nodes[$index];
752         my $rest = $nodes[$index+1];
753         if ($singlelang) {
754             my $oldMO = $MULTIPLE_OUTPUT;
755             $MULTIPLE_OUTPUT = 1;
756             traverse($fh, $type, $rest, $language, $spacepreserve);
757             $MULTIPLE_OUTPUT = $oldMO;
758         } else {
759             traverse($fh, $type, $rest, $language, $spacepreserve);
760         }
761         $index += 2;
762     }
763 }
764
765 sub isWellFormedXmlFragment
766 {
767     my $ret = eval 'require XML::Parser';
768     if(!$ret) {
769         die "You must have XML::Parser installed to run $0\n\n";
770     } 
771
772     my $fragment = shift;
773     return 0 if (!$fragment);
774
775     $fragment = "<root>$fragment</root>";
776     my $xp = new XML::Parser(Style => 'Tree');
777     my $tree = 0;
778     eval { $tree = $xp->parse($fragment); };
779     return $tree;
780 }
781
782 sub traverse
783 {
784     my $fh = shift; 
785     my $nodename = shift;
786     my $content = shift;
787     my $language = shift || "";
788     my $spacepreserve = shift || 0;
789
790     if (!$nodename) {
791         if ($content =~ /^[\s]*$/) {
792             $leading_space .= $content;
793         }
794         print $fh $content;
795     } else {
796         # element
797         my @all = @{ $content };
798         my $attrs = shift @all;
799         my $translate = 0;
800         my $outattr = getAttributeString($attrs, 1, $language, \$translate);
801
802         if ($nodename =~ /^_/) {
803             $translate = 1;
804             $nodename =~ s/^_//;
805         }
806         my $lookup = '';
807
808         $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
809         $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
810
811         print $fh "<$nodename", $outattr;
812         if ($translate) {
813             $lookup = getXMLstring($content, $spacepreserve);
814             if (!$spacepreserve) {
815                 $lookup =~ s/^\s+//s;
816                 $lookup =~ s/\s+$//s;
817             }
818
819             if ($lookup || $translate == 2) {
820                 my $translation = $translations{$language, $lookup} if isWellFormedXmlFragment($translations{$language, $lookup});
821                 if ($MULTIPLE_OUTPUT && ($translation || $translate == 2)) {
822                     $translation = $lookup if (!$translation);
823                     print $fh " xml:lang=\"", $language, "\"" if $language;
824                     print $fh ">";
825                     if ($translate == 2) {
826                         translate_subnodes($fh, \@all, $language, 1, $spacepreserve);
827                     } else {
828                         print $fh $translation;
829                     }
830                     print $fh "</$nodename>";
831
832                     return; # this means there will be no same translation with xml:lang="$language"...
833                             # if we want them both, just remove this "return"
834                 } else {
835                     print $fh ">";
836                     if ($translate == 2) {
837                         translate_subnodes($fh, \@all, $language, 1, $spacepreserve);
838                     } else {
839                         print $fh $lookup;
840                     }
841                     print $fh "</$nodename>";
842                 }
843             } else {
844                 print $fh "/>";
845             }
846
847             for my $lang (sort keys %po_files_by_lang) {
848                     if ($MULTIPLE_OUTPUT && $lang ne "$language") {
849                         next;
850                     }
851                     if ($lang) {
852                         # Handle translation
853                         #
854                         my $translate = 0;
855                         my $localattrs = getAttributeString($attrs, 1, $lang, \$translate);
856                         my $translation = $translations{$lang, $lookup} if isWellFormedXmlFragment($translations{$lang, $lookup});
857                         if ($translate && !$translation) {
858                             $translation = $lookup;
859                         }
860
861                         if ($translation || $translate) {
862                             print $fh "\n";
863                             $leading_space =~ s/.*\n//g;
864                             print $fh $leading_space;
865                             print $fh "<", $nodename, " xml:lang=\"", $lang, "\"", $localattrs, ">";
866                             if ($translate == 2) {
867                                translate_subnodes($fh, \@all, $lang, 1, $spacepreserve);
868                             } else {
869                                 print $fh $translation;
870                             }
871                             print $fh "</$nodename>";
872                         }
873                     }
874             }
875
876         } else {
877             my $count = scalar(@all);
878             if ($count > 0) {
879                 print $fh ">";
880                 my $index = 0;
881                 while ($index < $count) {
882                     my $type = $all[$index];
883                     my $rest = $all[$index+1];
884                     traverse($fh, $type, $rest, $language, $spacepreserve);
885                     $index += 2;
886                 }
887                 print $fh "</$nodename>";
888             } else {
889                 print $fh "/>";
890             }
891         }
892     }
893 }
894
895 sub intltool_tree_comment
896 {
897     my $expat = shift;
898     my $data  = shift;
899     my $clist = $expat->{Curlist};
900     my $pos   = $#$clist;
901
902     push @$clist, 1 => $data;
903 }
904
905 sub intltool_tree_cdatastart
906 {
907     my $expat    = shift;
908     my $clist = $expat->{Curlist};
909     my $pos   = $#$clist;
910
911     push @$clist, 0 => $expat->original_string();
912 }
913
914 sub intltool_tree_cdataend
915 {
916     my $expat    = shift;
917     my $clist = $expat->{Curlist};
918     my $pos   = $#$clist;
919
920     $clist->[$pos] .= $expat->original_string();
921 }
922
923 sub intltool_tree_char
924 {
925     my $expat = shift;
926     my $text  = shift;
927     my $clist = $expat->{Curlist};
928     my $pos   = $#$clist;
929
930     # Use original_string so that we retain escaped entities
931     # in CDATA sections.
932     #
933     if ($pos > 0 and $clist->[$pos - 1] eq '0') {
934         $clist->[$pos] .= $expat->original_string();
935     } else {
936         push @$clist, 0 => $expat->original_string();
937     }
938 }
939
940 sub intltool_tree_start
941 {
942     my $expat    = shift;
943     my $tag      = shift;
944     my @origlist = ();
945
946     # Use original_string so that we retain escaped entities
947     # in attribute values.  We must convert the string to an
948     # @origlist array to conform to the structure of the Tree
949     # Style.
950     #
951     my @original_array = split /\x/, $expat->original_string();
952     my $source         = $expat->original_string();
953
954     # Remove leading tag.
955     #
956     $source =~ s|^\s*<\s*(\S+)||s;
957
958     # Grab attribute key/value pairs and push onto @origlist array.
959     #
960     while ($source)
961     {
962        if ($source =~ /^\s*([\w:-]+)\s*[=]\s*["]/)
963        {
964            $source =~ s|^\s*([\w:-]+)\s*[=]\s*["]([^"]*)["]||s;
965            push @origlist, $1;
966            push @origlist, '"' . $2 . '"';
967        }
968        elsif ($source =~ /^\s*([\w:-]+)\s*[=]\s*[']/)
969        {
970            $source =~ s|^\s*([\w:-]+)\s*[=]\s*[']([^']*)[']||s;
971            push @origlist, $1;
972            push @origlist, "'" . $2 . "'";
973        }
974        else
975        {
976            last;
977        }
978     }
979
980     my $ol = [ { @origlist } ];
981
982     push @{ $expat->{Lists} }, $expat->{Curlist};
983     push @{ $expat->{Curlist} }, $tag => $ol;
984     $expat->{Curlist} = $ol;
985 }
986
987 sub readXml
988 {
989     my $filename = shift || return;
990     if(!-f $filename) {
991         die "ERROR Cannot find filename: $filename\n";
992     }
993
994     my $ret = eval 'require XML::Parser';
995     if(!$ret) {
996         die "You must have XML::Parser installed to run $0\n\n";
997     } 
998     my $xp = new XML::Parser(Style => 'Tree');
999     $xp->setHandlers(Char => \&intltool_tree_char);
1000     $xp->setHandlers(Start => \&intltool_tree_start);
1001     $xp->setHandlers(CdataStart => \&intltool_tree_cdatastart);
1002     $xp->setHandlers(CdataEnd => \&intltool_tree_cdataend);
1003     my $tree = $xp->parsefile($filename);
1004
1005 # <foo><head id="a">Hello <em>there</em></head><bar>Howdy<ref/></bar>do</foo>
1006 # would be:
1007 # [foo, [{}, head, [{id => "a"}, 0, "Hello ",  em, [{}, 0, "there"]], bar, [{},
1008 # 0, "Howdy",  ref, [{}]], 0, "do" ] ]
1009
1010     return $tree;
1011 }
1012
1013 sub print_header
1014 {
1015     my $infile = shift;
1016     my $fh = shift;
1017     my $source;
1018
1019     if(!-f $infile) {
1020         die "ERROR Cannot find filename: $infile\n";
1021     }
1022
1023     print $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
1024     {
1025         local $/;
1026         open DOCINPUT, "<${FILE}" or die;
1027         $source = <DOCINPUT>;
1028         close DOCINPUT;
1029     }
1030     if ($source =~ /(<!DOCTYPE.*\[.*\]\s*>)/s)
1031     {
1032         print $fh "$1\n";
1033     }
1034     elsif ($source =~ /(<!DOCTYPE[^>]*>)/s)
1035     {
1036         print $fh "$1\n";
1037     }
1038 }
1039
1040 sub parseTree
1041 {
1042     my $fh        = shift;
1043     my $ref       = shift;
1044     my $language  = shift || "";
1045
1046     my $name = shift @{ $ref };
1047     my $cont = shift @{ $ref };
1048     
1049     while (!$name || "$name" eq "1") {
1050         $name = shift @{ $ref };
1051         $cont = shift @{ $ref };
1052     }
1053
1054     my $spacepreserve = 0;
1055     my $attrs = @{$cont}[0];
1056     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
1057
1058     traverse($fh, $name, $cont, $language, $spacepreserve);
1059 }
1060
1061 sub xml_merge_output
1062 {
1063     my $source;
1064
1065     if ($MULTIPLE_OUTPUT) {
1066         for my $lang (sort keys %po_files_by_lang) {
1067             if ( ! -d $lang ) {
1068                 mkdir $lang or -d $lang or die "Cannot create subdirectory $lang: $!\n";
1069             }
1070             open OUTPUT, ">$lang/$OUTFILE" or die "Cannot open $lang/$OUTFILE: $!\n";
1071             binmode (OUTPUT) if $^O eq 'MSWin32';
1072             my $tree = readXml($FILE);
1073             print_header($FILE, \*OUTPUT);
1074             parseTree(\*OUTPUT, $tree, $lang);
1075             close OUTPUT;
1076             print "CREATED $lang/$OUTFILE\n" unless $QUIET_ARG;
1077         }
1078     } 
1079     open OUTPUT, ">$OUTFILE" or die "Cannot open $OUTFILE: $!\n";
1080     binmode (OUTPUT) if $^O eq 'MSWin32';
1081     my $tree = readXml($FILE);
1082     print_header($FILE, \*OUTPUT);
1083     parseTree(\*OUTPUT, $tree);
1084     close OUTPUT;
1085     print "CREATED $OUTFILE\n" unless $QUIET_ARG;
1086 }
1087
1088 sub keys_merge_translations
1089 {
1090     open INPUT, "<${FILE}" or die;
1091     open OUTPUT, ">${OUTFILE}" or die;
1092     binmode (OUTPUT) if $^O eq 'MSWin32';
1093
1094     while (<INPUT>) 
1095     {
1096         if (s/^(\s*)_(\w+=(.*))/$1$2/)  
1097         {
1098             my $string = $3;
1099
1100             print OUTPUT;
1101
1102             my $non_translated_line = $_;
1103
1104             for my $lang (sort keys %po_files_by_lang) 
1105             {
1106                 my $translation = $translations{$lang, $string};
1107                 next if !$translation;
1108
1109                 $_ = $non_translated_line;
1110                 s/(\w+)=.*/[$lang]$1=$translation/;
1111                 print OUTPUT;
1112             }
1113         } 
1114         else 
1115         {
1116             print OUTPUT;
1117         }
1118     }
1119
1120     close OUTPUT;
1121     close INPUT;
1122 }
1123
1124 sub desktop_merge_translations
1125 {
1126     open INPUT, "<${FILE}" or die;
1127     open OUTPUT, ">${OUTFILE}" or die;
1128     binmode (OUTPUT) if $^O eq 'MSWin32';
1129
1130     while (<INPUT>) 
1131     {
1132         if (s/^(\s*)_(\w+=(.*))/$1$2/)  
1133         {
1134             my $string = $3;
1135
1136             print OUTPUT;
1137
1138             my $non_translated_line = $_;
1139
1140             for my $lang (sort keys %po_files_by_lang) 
1141             {
1142                 my $translation = $translations{$lang, $string};
1143                 next if !$translation;
1144
1145                 $_ = $non_translated_line;
1146                 s/(\w+)=.*/${1}[$lang]=$translation/;
1147                 print OUTPUT;
1148             }
1149         } 
1150         else 
1151         {
1152             print OUTPUT;
1153         }
1154     }
1155
1156     close OUTPUT;
1157     close INPUT;
1158 }
1159
1160 sub schemas_merge_translations
1161 {
1162     my $source;
1163
1164     {
1165        local $/; # slurp mode
1166        open INPUT, "<$FILE" or die "can't open $FILE: $!";
1167        $source = <INPUT>;
1168        close INPUT;
1169     }
1170
1171     open OUTPUT, ">$OUTFILE" or die;
1172     binmode (OUTPUT) if $^O eq 'MSWin32';
1173
1174     # FIXME: support attribute translations
1175
1176     # Empty nodes never need translation, so unmark all of them.
1177     # For example, <_foo/> is just replaced by <foo/>.
1178     $source =~ s|<\s*_($w+)\s*/>|<$1/>|g;
1179
1180     while ($source =~ s/
1181                         (.*?)
1182                         (\s+)(<locale\ name="C">(\s*)
1183                             (<default>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/default>)?(\s*)
1184                             (<short>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/short>)?(\s*)
1185                             (<long>\s*(?:<!--[^>]*?-->\s*)?(.*?)\s*<\/long>)?(\s*)
1186                         <\/locale>)
1187                        //sx) 
1188     {
1189         print OUTPUT $1;
1190
1191         my $locale_start_spaces = $2 ? $2 : '';
1192         my $default_spaces = $4 ? $4 : '';
1193         my $short_spaces = $7 ? $7 : '';
1194         my $long_spaces = $10 ? $10 : '';
1195         my $locale_end_spaces = $13 ? $13 : '';
1196         my $c_default_block = $3 ? $3 : '';
1197         my $default_string = $6 ? $6 : '';
1198         my $short_string = $9 ? $9 : '';
1199         my $long_string = $12 ? $12 : '';
1200
1201         print OUTPUT "$locale_start_spaces$c_default_block";
1202
1203         $default_string =~ s/\s+/ /g;
1204         $default_string = entity_decode($default_string);
1205         $short_string =~ s/\s+/ /g;
1206         $short_string = entity_decode($short_string);
1207         $long_string =~ s/\s+/ /g;
1208         $long_string = entity_decode($long_string);
1209
1210         for my $lang (sort keys %po_files_by_lang) 
1211         {
1212             my $default_translation = $translations{$lang, $default_string};
1213             my $short_translation = $translations{$lang, $short_string};
1214             my $long_translation  = $translations{$lang, $long_string};
1215
1216             next if (!$default_translation && !$short_translation && 
1217                      !$long_translation);
1218
1219             print OUTPUT "\n$locale_start_spaces<locale name=\"$lang\">";
1220
1221         print OUTPUT "$default_spaces";    
1222
1223         if ($default_translation)
1224         {
1225             $default_translation = entity_encode($default_translation);
1226             print OUTPUT "<default>$default_translation</default>";
1227         }
1228
1229             print OUTPUT "$short_spaces";
1230
1231             if ($short_translation)
1232             {
1233                         $short_translation = entity_encode($short_translation);
1234                         print OUTPUT "<short>$short_translation</short>";
1235             }
1236
1237             print OUTPUT "$long_spaces";
1238
1239             if ($long_translation)
1240             {
1241                         $long_translation = entity_encode($long_translation);
1242                         print OUTPUT "<long>$long_translation</long>";
1243             }       
1244
1245             print OUTPUT "$locale_end_spaces</locale>";
1246         }
1247     }
1248
1249     print OUTPUT $source;
1250
1251     close OUTPUT;
1252 }
1253
1254 sub rfc822deb_merge_translations
1255 {
1256     my %encodings = ();
1257     for my $lang (keys %po_files_by_lang) {
1258         $encodings{$lang} = ($UTF8_ARG ? 'UTF-8' : get_po_encoding($po_files_by_lang{$lang}));
1259     }
1260
1261     my $source;
1262
1263     $Text::Wrap::huge = 'overflow';
1264     $Text::Wrap::break = qr/\n|\s(?=\S)/;
1265
1266     {
1267        local $/; # slurp mode
1268        open INPUT, "<$FILE" or die "can't open $FILE: $!";
1269        $source = <INPUT>;
1270        close INPUT;
1271     }
1272
1273     open OUTPUT, ">${OUTFILE}" or die;
1274     binmode (OUTPUT) if $^O eq 'MSWin32';
1275
1276     while ($source =~ /(^|\n+)(_*)([^:\s]+)(:[ \t]*)(.*?)(?=\n[\S\n]|$)/sg)
1277     {
1278             my $sep = $1;
1279             my $non_translated_line = $3.$4;
1280             my $string = $5;
1281             my $underscore = length($2);
1282             next if $underscore eq 0 && $non_translated_line =~ /^#/;
1283             #  Remove [] dummy strings
1284             my $stripped = $string;
1285             $stripped =~ s/\[\s[^\[\]]*\],/,/g if $underscore eq 2;
1286             $stripped =~ s/\[\s[^\[\]]*\]$//;
1287             $non_translated_line .= $stripped;
1288
1289             print OUTPUT $sep.$non_translated_line;
1290     
1291             if ($underscore) 
1292             {
1293                 my @str_list = rfc822deb_split($underscore, $string);
1294
1295                 for my $lang (sort keys %po_files_by_lang) 
1296                 {
1297                     my $is_translated = 1;
1298                     my $str_translated = '';
1299                     my $first = 1;
1300                 
1301                     for my $str (@str_list) 
1302                     {
1303                         my $translation = $translations{$lang, $str};
1304                     
1305                         if (!$translation) 
1306                         {
1307                             $is_translated = 0;
1308                             last;
1309                         }
1310
1311                         #  $translation may also contain [] dummy
1312                         #  strings, mostly to indicate an empty string
1313                         $translation =~ s/\[\s[^\[\]]*\]$//;
1314                         
1315                         if ($first) 
1316                         {
1317                             if ($underscore eq 2)
1318                             {
1319                                 $str_translated .= $translation;
1320                             }
1321                             else
1322                             {
1323                                 $str_translated .=
1324                                     Text::Tabs::expand($translation) .
1325                                     "\n";
1326                             }
1327                         } 
1328                         else 
1329                         {
1330                             if ($underscore eq 2)
1331                             {
1332                                 $str_translated .= ', ' . $translation;
1333                             }
1334                             else
1335                             {
1336                                 $str_translated .= Text::Tabs::expand(
1337                                     Text::Wrap::wrap(' ', ' ', $translation)) .
1338                                     "\n .\n";
1339                             }
1340                         }
1341                         $first = 0;
1342
1343                         #  To fix some problems with Text::Wrap::wrap
1344                         $str_translated =~ s/(\n )+\n/\n .\n/g;
1345                     }
1346                     next unless $is_translated;
1347
1348                     $str_translated =~ s/\n \.\n$//;
1349                     $str_translated =~ s/\s+$//;
1350
1351                     $_ = $non_translated_line;
1352                     s/^(\w+):\s*.*/$sep${1}-$lang.$encodings{$lang}: $str_translated/s;
1353                     print OUTPUT;
1354                 }
1355             }
1356     }
1357     print OUTPUT "\n";
1358
1359     close OUTPUT;
1360     close INPUT;
1361 }
1362
1363 sub rfc822deb_split 
1364 {
1365     # Debian defines a special way to deal with rfc822-style files:
1366     # when a value contain newlines, it consists of
1367     #   1.  a short form (first line)
1368     #   2.  a long description, all lines begin with a space,
1369     #       and paragraphs are separated by a single dot on a line
1370     # This routine returns an array of all paragraphs, and reformat
1371     # them.
1372     # When first argument is 2, the string is a comma separated list of
1373     # values.
1374     my $type = shift;
1375     my $text = shift;
1376     $text =~ s/^[ \t]//mg;
1377     return (split(/, */, $text, 0)) if $type ne 1;
1378     return ($text) if $text !~ /\n/;
1379
1380     $text =~ s/([^\n]*)\n//;
1381     my @list = ($1);
1382     my $str = '';
1383
1384     for my $line (split (/\n/, $text)) 
1385     {
1386         chomp $line;
1387         if ($line =~ /^\.\s*$/)
1388         {
1389             #  New paragraph
1390             $str =~ s/\s*$//;
1391             push(@list, $str);
1392             $str = '';
1393         } 
1394         elsif ($line =~ /^\s/) 
1395         {
1396             #  Line which must not be reformatted
1397             $str .= "\n" if length ($str) && $str !~ /\n$/;
1398             $line =~ s/\s+$//;
1399             $str .= $line."\n";
1400         } 
1401         else 
1402         {
1403             #  Continuation line, remove newline
1404             $str .= " " if length ($str) && $str !~ /\n$/;
1405             $str .= $line;
1406         }
1407     }
1408
1409     $str =~ s/\s*$//;
1410     push(@list, $str) if length ($str);
1411
1412     return @list;
1413 }
1414
1415 sub quoted_translation
1416 {
1417     my ($lang, $string) = @_;
1418
1419     $string =~ s/\\\"/\"/g;
1420
1421     my $translation = $translations{$lang, $string};
1422     $translation = $string if !$translation;
1423
1424     $translation =~ s/\"/\\\"/g;
1425     return $translation
1426 }
1427
1428 sub quoted_merge_translations
1429 {
1430     if (!$MULTIPLE_OUTPUT) {
1431         print "Quoted only supports Multiple Output.\n";
1432         exit(1);
1433     }
1434
1435     for my $lang (sort keys %po_files_by_lang) {
1436         if ( ! -d $lang ) {
1437             mkdir $lang or -d $lang or die "Cannot create subdirectory $lang: $!\n";
1438         }
1439         open INPUT, "<${FILE}" or die;
1440         open OUTPUT, ">$lang/$OUTFILE" or die "Cannot open $lang/$OUTFILE: $!\n";
1441         binmode (OUTPUT) if $^O eq 'MSWin32';
1442         while (<INPUT>) 
1443         {
1444             s/\"(([^\"]|\\\")*[^\\\"])\"/"\"" . &quoted_translation($lang, $1) . "\""/ge;
1445             print OUTPUT;
1446         }
1447         close OUTPUT;
1448         close INPUT;
1449     }
1450 }