Make treeview refresh properly when adding nodes.
[straw/fork.git] / intltool-extract.in
blob7b8343554e33196f8e990d4599e73be10935a6a9
1 #!@INTLTOOL_PERL@ -w 
2 # -*- Mode: perl; indent-tabs-mode: nil; c-basic-offset: 4  -*-
5 #  The Intltool Message Extractor
7 #  Copyright (C) 2000-2001, 2003 Free Software Foundation.
9 #  Intltool is free software; you can redistribute it and/or
10 #  modify it under the terms of the GNU General Public License as
11 #  published by the Free Software Foundation; either version 2 of the
12 #  License, or (at your option) any later version.
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.
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.
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.
28 #  Authors: Kenneth Christiansen <kenneth@gnu.org>
29 #           Darin Adler <darin@bentspoon.com>
32 ## Release information
33 my $PROGRAM      = "intltool-extract";
34 my $PACKAGE      = "intltool";
35 my $VERSION      = "0.36.2";
37 ## Loaded modules
38 use strict; 
39 use File::Basename;
40 use Getopt::Long;
42 ## Scalars used by the option stuff
43 my $TYPE_ARG    = "0";
44 my $LOCAL_ARG   = "0";
45 my $HELP_ARG    = "0";
46 my $VERSION_ARG = "0";
47 my $UPDATE_ARG  = "0";
48 my $QUIET_ARG   = "0";
49 my $SRCDIR_ARG  = ".";
51 my $FILE;
52 my $OUTFILE;
54 my $gettext_type = "";
55 my $input;
56 my %messages = ();
57 my %loc = ();
58 my %count = ();
59 my %comments = ();
60 my $strcount = 0;
62 my $XMLCOMMENT = "";
64 ## Use this instead of \w for XML files to handle more possible characters.
65 my $w = "[-A-Za-z0-9._:]";
67 ## Always print first
68 $| = 1;
70 ## Handle options
71 GetOptions (
72             "type=s"     => \$TYPE_ARG,
73             "local|l"    => \$LOCAL_ARG,
74             "help|h"     => \$HELP_ARG,
75             "version|v"  => \$VERSION_ARG,
76             "update"     => \$UPDATE_ARG,
77             "quiet|q"    => \$QUIET_ARG,
78             "srcdir=s"   => \$SRCDIR_ARG,
79             ) or &error;
81 &split_on_argument;
84 ## Check for options. 
85 ## This section will check for the different options.
87 sub split_on_argument {
89     if ($VERSION_ARG) {
90         &version;
92     } elsif ($HELP_ARG) {
93         &help;
94         
95     } elsif ($LOCAL_ARG) {
96         &place_local;
97         &extract;
99     } elsif ($UPDATE_ARG) {
100         &place_normal;
101         &extract;
103     } elsif (@ARGV > 0) {
104         &place_normal;
105         &message;
106         &extract;
108     } else {
109         &help;
111     }  
112 }    
114 sub place_normal {
115     $FILE        = $ARGV[0];
116     $OUTFILE     = "$FILE.h";
118     my $dirname = dirname ($OUTFILE);
119     if (! -d "$dirname" && $dirname ne "") {
120         system ("mkdir -p $dirname");
121     }
122 }   
124 sub place_local {
125     $FILE        = $ARGV[0];
126     $OUTFILE     = fileparse($FILE, ());
127     if (!-e "tmp/") { 
128         system("mkdir tmp/"); 
129     }
130     $OUTFILE     = "./tmp/$OUTFILE.h"
133 sub determine_type {
134    if ($TYPE_ARG =~ /^gettext\/(.*)/) {
135         $gettext_type=$1
136    }
139 ## Sub for printing release information
140 sub version{
141     print <<_EOF_;
142 ${PROGRAM} (${PACKAGE}) $VERSION
143 Copyright (C) 2000, 2003 Free Software Foundation, Inc.
144 Written by Kenneth Christiansen, 2000.
146 This is free software; see the source for copying conditions.  There is NO
147 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
148 _EOF_
149     exit;
152 ## Sub for printing usage information
153 sub help {
154     print <<_EOF_;
155 Usage: ${PROGRAM} [OPTION]... [FILENAME]
156 Generates a header file from an XML source file.
158 It grabs all strings between <_translatable_node> and its end tag in
159 XML files. Read manpage (man ${PROGRAM}) for more info.
161       --type=TYPE   Specify the file type of FILENAME. Currently supports:
162                     "gettext/glade", "gettext/ini", "gettext/keys"
163                     "gettext/rfc822deb", "gettext/schemas",
164                     "gettext/scheme", "gettext/xml", "gettext/quoted"
165   -l, --local       Writes output into current working directory
166                     (conflicts with --update)
167       --update      Writes output into the same directory the source file 
168                     reside (conflicts with --local)
169       --srcdir      Root of the source tree
170   -v, --version     Output version information and exit
171   -h, --help        Display this help and exit
172   -q, --quiet       Quiet mode
174 Report bugs to http://bugzilla.gnome.org/ (product name "$PACKAGE")
175 or send email to <xml-i18n-tools\@gnome.org>.
176 _EOF_
177     exit;
180 ## Sub for printing error messages
181 sub error{
182     print STDERR "Try `${PROGRAM} --help' for more information.\n";
183     exit;
186 sub message {
187     print "Generating C format header file for translation.\n" unless $QUIET_ARG;
190 sub extract {
191     &determine_type;
193     &convert;
195     open OUT, ">$OUTFILE";
196     binmode (OUT) if $^O eq 'MSWin32';
197     &msg_write;
198     close OUT;
200     print "Wrote $OUTFILE\n" unless $QUIET_ARG;
203 sub convert {
205     ## Reading the file
206     {
207         local (*IN);
208         local $/; #slurp mode
209         open (IN, "<$SRCDIR_ARG/$FILE") || die "can't open $SRCDIR_ARG/$FILE: $!";
210         $input = <IN>;
211     }
213     &type_ini if $gettext_type eq "ini";
214     &type_keys if $gettext_type eq "keys";
215     &type_xml if $gettext_type eq "xml";
216     &type_glade if $gettext_type eq "glade";
217     &type_scheme if $gettext_type eq "scheme";
218     &type_schemas  if $gettext_type eq "schemas";
219     &type_rfc822deb  if $gettext_type eq "rfc822deb";
220     &type_quoted if $gettext_type eq "quoted";
223 sub entity_decode_minimal
225     local ($_) = @_;
227     s/&apos;/'/g; # '
228     s/&quot;/"/g; # "
229     s/&amp;/&/g;
231     return $_;
234 sub entity_decode
236     local ($_) = @_;
238     s/&apos;/'/g; # '
239     s/&quot;/"/g; # "
240     s/&lt;/</g;
241     s/&gt;/>/g;
242     s/&amp;/&/g;
244     return $_;
247 sub escape_char
249     return '\"' if $_ eq '"';
250     return '\n' if $_ eq "\n";
251     return '\\\\' if $_ eq '\\';
253     return $_;
256 sub escape
258     my ($string) = @_;
259     return join "", map &escape_char, split //, $string;
262 sub type_ini {
263     ### For generic translatable desktop files ###
264     while ($input =~ /^(#(.+)\n)?^_.*=(.*)$/mg) {
265         if (defined($2))  {
266             $comments{$3} = $2;
267         }
268         $messages{$3} = [];
269     }
272 sub type_keys {
273     ### For generic translatable mime/keys files ###
274     while ($input =~ /^\s*_\w+=(.*)$/mg) {
275         $messages{$1} = [];
276     }
279 sub type_xml {
280     ### For generic translatable XML files ###
281     my $tree = readXml($input);
282     parseTree(0, $tree);
285 sub print_var {
286     my $var = shift;
287     my $vartype = ref $var;
288     
289     if ($vartype =~ /ARRAY/) {
290         my @arr = @{$var};
291         print "[ ";
292         foreach my $el (@arr) {
293             print_var($el);
294             print ", ";
295         }
296         print "] ";
297     } elsif ($vartype =~ /HASH/) {
298         my %hash = %{$var};
299         print "{ ";
300         foreach my $key (keys %hash) {
301             print "$key => ";
302             print_var($hash{$key});
303             print ", ";
304         }
305         print "} ";
306     } else {
307         print $var;
308     }
311 # Same syntax as getAttributeString in intltool-merge.in.in, similar logic (look for ## differences comment)
312 sub getAttributeString
314     my $sub = shift;
315     my $do_translate = shift || 1;
316     my $language = shift || "";
317     my $translate = shift;
318     my $result = "";
319     foreach my $e (reverse(sort(keys %{ $sub }))) {
320         my $key    = $e;
321         my $string = $sub->{$e};
322         my $quote = '"';
323         
324         $string =~ s/^[\s]+//;
325         $string =~ s/[\s]+$//;
326         
327         if ($string =~ /^'.*'$/)
328         {
329             $quote = "'";
330         }
331         $string =~ s/^['"]//g;
332         $string =~ s/['"]$//g;
334         ## differences from intltool-merge.in.in
335         if ($key =~ /^_/) {
336             $comments{entity_decode($string)} = $XMLCOMMENT if $XMLCOMMENT;
337             $messages{entity_decode($string)} = [];
338             $$translate = 2;
339         }
340         ## differences end here from intltool-merge.in.in
341         $result .= " $key=$quote$string$quote";
342     }
343     return $result;
346 # Verbatim copy from intltool-merge.in.in
347 sub getXMLstring
349     my $ref = shift;
350     my $spacepreserve = shift || 0;
351     my @list = @{ $ref };
352     my $result = "";
354     my $count = scalar(@list);
355     my $attrs = $list[0];
356     my $index = 1;
358     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
359     $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
361     while ($index < $count) {
362         my $type = $list[$index];
363         my $content = $list[$index+1];
364         if (! $type ) {
365             # We've got CDATA
366             if ($content) {
367                 # lets strip the whitespace here, and *ONLY* here
368                 $content =~ s/\s+/ /gs if (!$spacepreserve);
369                 $result .= $content;
370             }
371         } elsif ( "$type" ne "1" ) {
372             # We've got another element
373             $result .= "<$type";
374             $result .= getAttributeString(@{$content}[0], 0); # no nested translatable elements
375             if ($content) {
376                 my $subresult = getXMLstring($content, $spacepreserve);
377                 if ($subresult) {
378                     $result .= ">".$subresult . "</$type>";
379                 } else {
380                     $result .= "/>";
381                 }
382             } else {
383                 $result .= "/>";
384             }
385         }
386         $index += 2;
387     }
388     return $result;
391 # Verbatim copy from intltool-merge.in.in, except for MULTIPLE_OUTPUT handling removed
392 # Translate list of nodes if necessary
393 sub translate_subnodes
395     my $fh = shift;
396     my $content = shift;
397     my $language = shift || "";
398     my $singlelang = shift || 0;
399     my $spacepreserve = shift || 0;
401     my @nodes = @{ $content };
403     my $count = scalar(@nodes);
404     my $index = 0;
405     while ($index < $count) {
406         my $type = $nodes[$index];
407         my $rest = $nodes[$index+1];
408         traverse($fh, $type, $rest, $language, $spacepreserve);
409         $index += 2;
410     }
413 # Based on traverse() in intltool-merge.in.in
414 sub traverse
416     my $fh = shift; # unused, to allow us to sync code between -merge and -extract
417     my $nodename = shift;
418     my $content = shift;
419     my $language = shift || "";
420     my $spacepreserve = shift || 0;
422     if ($nodename && "$nodename" eq "1") {
423         $XMLCOMMENT = $content;
424     } elsif ($nodename) {
425         # element
426         my @all = @{ $content };
427         my $attrs = shift @all;
428         my $translate = 0;
429         my $outattr = getAttributeString($attrs, 1, $language, \$translate);
431         if ($nodename =~ /^_/) {
432             $translate = 1;
433             $nodename =~ s/^_//;
434         }
435         my $lookup = '';
437         $spacepreserve = 0 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?default["']?$/));
438         $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
440         if ($translate) {
441             $lookup = getXMLstring($content, $spacepreserve);
442             if (!$spacepreserve) {
443                 $lookup =~ s/^\s+//s;
444                 $lookup =~ s/\s+$//s;
445             }
447             if ($lookup && $translate != 2) {
448                 $comments{$lookup} = $XMLCOMMENT if $XMLCOMMENT;
449                 $messages{$lookup} = [];
450             } elsif ($translate == 2) {
451                 translate_subnodes($fh, \@all, $language, 1, $spacepreserve);
452             }
453         } else {
454             $XMLCOMMENT = "";
455             my $count = scalar(@all);
456             if ($count > 0) {
457                 my $index = 0;
458                 while ($index < $count) {
459                     my $type = $all[$index];
460                     my $rest = $all[$index+1];
461                     traverse($fh, $type, $rest, $language, $spacepreserve);
462                     $index += 2;
463                 }
464             }
465         }
466         $XMLCOMMENT = "";
467     }
471 # Verbatim copy from intltool-merge.in.in, $fh for compatibility
472 sub parseTree
474     my $fh        = shift;
475     my $ref       = shift;
476     my $language  = shift || "";
478     my $name = shift @{ $ref };
479     my $cont = shift @{ $ref };
481     while (!$name || "$name" eq "1") {
482         $name = shift @{ $ref };
483         $cont = shift @{ $ref };
484     }
486     my $spacepreserve = 0;
487     my $attrs = @{$cont}[0];
488     $spacepreserve = 1 if ((exists $attrs->{"xml:space"}) && ($attrs->{"xml:space"} =~ /^["']?preserve["']?$/));
490     traverse($fh, $name, $cont, $language, $spacepreserve);
493 # Verbatim copy from intltool-merge.in.in
494 sub intltool_tree_comment
496     my $expat = shift;
497     my $data  = $expat->original_string();
498     my $clist = $expat->{Curlist};
499     my $pos   = $#$clist;
501     $data =~ s/^<!--//s;
502     $data =~ s/-->$//s;
503     push @$clist, 1 => $data;
506 # Verbatim copy from intltool-merge.in.in
507 sub intltool_tree_cdatastart
509     my $expat    = shift;
510     my $clist = $expat->{Curlist};
511     my $pos   = $#$clist;
513     push @$clist, 0 => $expat->original_string();
516 # Verbatim copy from intltool-merge.in.in
517 sub intltool_tree_cdataend
519     my $expat    = shift;
520     my $clist = $expat->{Curlist};
521     my $pos   = $#$clist;
523     $clist->[$pos] .= $expat->original_string();
526 # Verbatim copy from intltool-merge.in.in
527 sub intltool_tree_char
529     my $expat = shift;
530     my $text  = shift;
531     my $clist = $expat->{Curlist};
532     my $pos   = $#$clist;
534     # Use original_string so that we retain escaped entities
535     # in CDATA sections.
536     #
537     if ($pos > 0 and $clist->[$pos - 1] eq '0') {
538         $clist->[$pos] .= $expat->original_string();
539     } else {
540         push @$clist, 0 => $expat->original_string();
541     }
544 # Verbatim copy from intltool-merge.in.in
545 sub intltool_tree_start
547     my $expat    = shift;
548     my $tag      = shift;
549     my @origlist = ();
551     # Use original_string so that we retain escaped entities
552     # in attribute values.  We must convert the string to an
553     # @origlist array to conform to the structure of the Tree
554     # Style.
555     #
556     my @original_array = split /\x/, $expat->original_string();
557     my $source         = $expat->original_string();
559     # Remove leading tag.
560     #
561     $source =~ s|^\s*<\s*(\S+)||s;
563     # Grab attribute key/value pairs and push onto @origlist array.
564     #
565     while ($source)
566     {
567        if ($source =~ /^\s*([\w:-]+)\s*[=]\s*["]/)
568        {
569            $source =~ s|^\s*([\w:-]+)\s*[=]\s*["]([^"]*)["]||s;
570            push @origlist, $1;
571            push @origlist, '"' . $2 . '"';
572        }
573        elsif ($source =~ /^\s*([\w:-]+)\s*[=]\s*[']/)
574        {
575            $source =~ s|^\s*([\w:-]+)\s*[=]\s*[']([^']*)[']||s;
576            push @origlist, $1;
577            push @origlist, "'" . $2 . "'";
578        }
579        else
580        {
581            last;
582        }
583     }
585     my $ol = [ { @origlist } ];
587     push @{ $expat->{Lists} }, $expat->{Curlist};
588     push @{ $expat->{Curlist} }, $tag => $ol;
589     $expat->{Curlist} = $ol;
592 # Copied from intltool-merge.in.in and added comment handler.
593 sub readXml
595     my $xmldoc = shift || return;
596     my $ret = eval 'require XML::Parser';
597     if(!$ret) {
598         die "You must have XML::Parser installed to run $0\n\n";
599     }
600     my $xp = new XML::Parser(Style => 'Tree');
601     $xp->setHandlers(Char => \&intltool_tree_char);
602     $xp->setHandlers(Start => \&intltool_tree_start);
603     $xp->setHandlers(CdataStart => \&intltool_tree_cdatastart);
604     $xp->setHandlers(CdataEnd => \&intltool_tree_cdataend);
606     ## differences from intltool-merge.in.in
607     $xp->setHandlers(Comment => \&intltool_tree_comment);
608     ## differences end here from intltool-merge.in.in
610     my $tree = $xp->parse($xmldoc);
611     #print_var($tree);
613 # <foo><!-- comment --><head id="a">Hello <em>there</em></head><bar>Howdy<ref/></bar>do</foo>
614 # would be:
615 # [foo, [{}, 1, "comment", head, [{id => "a"}, 0, "Hello ",  em, [{}, 0, "there"]], bar, 
616 # [{}, 0, "Howdy",  ref, [{}]], 0, "do" ] ]
618     return $tree;
621 sub type_schemas {
622     ### For schemas XML files ###
623          
624     # FIXME: We should handle escaped < (less than)
625     while ($input =~ /
626                       <locale\ name="C">\s*
627                           (<default>\s*(?:<!--([^>]*?)-->\s*)?(.*?)\s*<\/default>\s*)?
628                           (<short>\s*(?:<!--([^>]*?)-->\s*)?(.*?)\s*<\/short>\s*)?
629                           (<long>\s*(?:<!--([^>]*?)-->\s*)?(.*?)\s*<\/long>\s*)?
630                       <\/locale>
631                      /sgx) {
632         my @totranslate = ($3,$6,$9);
633         my @eachcomment = ($2,$5,$8);
634         foreach (@totranslate) {
635             my $currentcomment = shift @eachcomment;
636             next if !$_;
637             s/\s+/ /g;
638             $messages{entity_decode_minimal($_)} = [];
639             $comments{entity_decode_minimal($_)} = $currentcomment if (defined($currentcomment));
640         }
641     }
644 sub type_rfc822deb {
645     ### For rfc822-style Debian configuration files ###
647     my $lineno = 1;
648     my $type = '';
649     while ($input =~ /\G(.*?)(^|\n)(_+)([^:]+):[ \t]*(.*?)(?=\n\S|$)/sg)
650     {
651         my ($pre, $newline, $underscore, $tag, $text) = ($1, $2, $3, $4, $5);
652         while ($pre =~ m/\n/g)
653         {
654             $lineno ++;
655         }
656         $lineno += length($newline);
657         my @str_list = rfc822deb_split(length($underscore), $text);
658         for my $str (@str_list)
659         {
660             $strcount++;
661             $messages{$str} = [];
662             $loc{$str} = $lineno;
663             $count{$str} = $strcount;
664             my $usercomment = '';
665             while($pre =~ s/(^|\n)#([^\n]*)$//s)
666             {
667                 $usercomment = "\n" . $2 . $usercomment;
668             }
669             $comments{$str} = $tag . $usercomment;
670         }
671         $lineno += ($text =~ s/\n//g);
672     }
675 sub rfc822deb_split {
676     # Debian defines a special way to deal with rfc822-style files:
677     # when a value contain newlines, it consists of
678     #   1.  a short form (first line)
679     #   2.  a long description, all lines begin with a space,
680     #       and paragraphs are separated by a single dot on a line
681     # This routine returns an array of all paragraphs, and reformat
682     # them.
683     # When first argument is 2, the string is a comma separated list of
684     # values.
685     my $type = shift;
686     my $text = shift;
687     $text =~ s/^[ \t]//mg;
688     return (split(/, */, $text, 0)) if $type ne 1;
689     return ($text) if $text !~ /\n/;
691     $text =~ s/([^\n]*)\n//;
692     my @list = ($1);
693     my $str = '';
694     for my $line (split (/\n/, $text))
695     {
696         chomp $line;
697         if ($line =~ /^\.\s*$/)
698         {
699             #  New paragraph
700             $str =~ s/\s*$//;
701             push(@list, $str);
702             $str = '';
703         }
704         elsif ($line =~ /^\s/)
705         {
706             #  Line which must not be reformatted
707             $str .= "\n" if length ($str) && $str !~ /\n$/;
708             $line =~ s/\s+$//;
709             $str .= $line."\n";
710         }
711         else
712         {
713             #  Continuation line, remove newline
714             $str .= " " if length ($str) && $str !~ /\n$/;
715             $str .= $line;
716         }
717     }
718     $str =~ s/\s*$//;
719     push(@list, $str) if length ($str);
720     return @list;
723 sub type_quoted {
724     while ($input =~ /\"(([^\"]|\\\")*[^\\\"])\"/g) {
725         my $message = $1;
726         my $before = $`;
727         $message =~ s/\\\"/\"/g;
728         $before =~ s/[^\n]//g;
729         $messages{$message} = [];
730         $loc{$message} = length ($before) + 2;
731     }
734 sub type_glade {
735     ### For translatable Glade XML files ###
737     my $tags = "label|title|text|format|copyright|comments|preview_text|tooltip|message";
739     while ($input =~ /<($tags)>([^<]+)<\/($tags)>/sg) {
740         # Glade sometimes uses tags that normally mark translatable things for
741         # little bits of non-translatable content. We work around this by not
742         # translating strings that only includes something like label4 or window1.
743         $messages{entity_decode($2)} = [] unless $2 =~ /^(window|label|dialog)[0-9]+$/;
744     }
745     
746     while ($input =~ /<items>(..[^<]*)<\/items>/sg) {
747         for my $item (split (/\n/, $1)) {
748             $messages{entity_decode($item)} = [];
749         }
750     }
752     ## handle new glade files
753     while ($input =~ /<(property|atkproperty)\s+[^>]*translatable\s*=\s*"yes"(?:\s+[^>]*comments\s*=\s*"([^"]*)")?[^>]*>([^<]+)<\/\1>/sg) {
754         $messages{entity_decode($3)} = [] unless $3 =~ /^(window|label)[0-9]+$/;
755         if (defined($2) and !($3 =~ /^(window|label)[0-9]+$/)) {
756            $comments{entity_decode($3)} = entity_decode($2) ;
757         }
758     }
759     while ($input =~ /<atkaction\s+action_name="([^>]*)"\s+description="([^>]+)"\/>/sg) {
760         $messages{entity_decode_minimal($2)} = [];
761     }
764 sub type_scheme {
765     my ($line, $i, $state, $str, $trcomment, $char);
766     for $line (split(/\n/, $input)) {
767         $i = 0;
768         $state = 0; # 0 - nothing, 1 - string, 2 - translatable string
769         while ($i < length($line)) {
770             if (substr($line,$i,1) eq "\"") {
771                 if ($state == 2) {
772                     $comments{$str} = $trcomment if ($trcomment);
773                     $messages{$str} = [];
774                     $str = '';
775                     $state = 0; $trcomment = "";
776                 } elsif ($state == 1) {
777                     $str = '';
778                     $state = 0; $trcomment = "";
779                 } else {
780                     $state = 1;
781                     $str = '';
782                     if ($i>0 && substr($line,$i-1,1) eq '_') {
783                         $state = 2;
784                     }
785                 }
786             } elsif (!$state) {
787                 if (substr($line,$i,1) eq ";") {
788                     $trcomment = substr($line,$i+1);
789                     $trcomment =~ s/^;*\s*//;
790                     $i = length($line);
791                 } elsif ($trcomment && substr($line,$i,1) !~ /\s|\(|\)|_/) {
792                     $trcomment = "";
793                 }
794             } else {
795                 if (substr($line,$i,1) eq "\\") {
796                     $char = substr($line,$i+1,1);
797                     if ($char ne "\"" && $char ne "\\") {
798                        $str = $str . "\\";
799                     }
800                     $i++;
801                 }
802                 $str = $str . substr($line,$i,1);
803             }
804             $i++;
805         }
806     }
809 sub msg_write {
810     my @msgids;
811     if (%count)
812     {
813         @msgids = sort { $count{$a} <=> $count{$b} } keys %count;
814     }
815     else
816     {
817         @msgids = sort keys %messages;
818     }
819     for my $message (@msgids)
820     {
821         my $offsetlines = 1;
822         $offsetlines++ if $message =~ /%/;
823         if (defined ($comments{$message}))
824         {
825                 while ($comments{$message} =~ m/\n/g)
826                 {
827                     $offsetlines++;
828                 }
829         }
830         print OUT "# ".($loc{$message} - $offsetlines).  " \"$FILE\"\n"
831                 if defined $loc{$message};
832         print OUT "/* ".$comments{$message}." */\n"
833                 if defined $comments{$message};
834         print OUT "/* xgettext:no-c-format */\n" if $message =~ /%/;
835         
836         my @lines = split (/\n/, $message, -1);
837         for (my $n = 0; $n < @lines; $n++)
838         {
839             if ($n == 0)
840             {
841                 print OUT "char *s = N_(\""; 
842             }
843             else
844             {  
845                 print OUT "             \""; 
846             }
848             print OUT escape($lines[$n]);
850             if ($n < @lines - 1)
851             {
852                 print OUT "\\n\"\n"; 
853             }
854             else
855             {
856                 print OUT "\");\n";  
857             }
858         }
859     }