home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 November / CPNL0711.ISO / communic / email / Evolution-2.8.2-2.msi / Data1.cab / intltool_merge < prev    next >
Text File  |  2007-03-07  |  36KB  |  1,404 lines

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