home *** CD-ROM | disk | FTP | other *** search
/ Chip 2005 June / ccd0605.iso / LINUX / gopchop-1.1.7.tar.tar / gopchop-1.1.7.tar / gopchop-1.1.7 / intltool-merge.in < prev    next >
Text File  |  2005-04-30  |  34KB  |  1,316 lines

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