intltool-merge.in
上传用户:ledjyj
上传日期:2014-08-27
资源大小:2639k
文件大小:34k
源码类别:

驱动编程

开发平台:

Unix_Linux

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