1 /* Converts Uniforum style .po files to binary .mo files
2    Copyright (C) 1995-1998, 2000-2007, 2009-2010, 2012, 2014-2016, 2018-2020 Free Software
3    Foundation, Inc.
4    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 
23 #include <ctype.h>
24 #include <getopt.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <locale.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <assert.h>
33 
34 #include "noreturn.h"
35 #include "closeout.h"
36 #include "str-list.h"
37 #include "dir-list.h"
38 #include "error.h"
39 #include "error-progname.h"
40 #include "progname.h"
41 #include "relocatable.h"
42 #include "basename-lgpl.h"
43 #include "xerror.h"
44 #include "xvasprintf.h"
45 #include "xalloc.h"
46 #include "msgfmt.h"
47 #include "write-mo.h"
48 #include "write-java.h"
49 #include "write-csharp.h"
50 #include "write-resources.h"
51 #include "write-tcl.h"
52 #include "write-qt.h"
53 #include "write-desktop.h"
54 #include "write-xml.h"
55 #include "propername.h"
56 #include "message.h"
57 #include "open-catalog.h"
58 #include "read-catalog.h"
59 #include "read-po.h"
60 #include "read-properties.h"
61 #include "read-stringtable.h"
62 #include "read-desktop.h"
63 #include "po-charset.h"
64 #include "msgl-check.h"
65 #include "msgl-iconv.h"
66 #include "concat-filename.h"
67 #include "its.h"
68 #include "locating-rule.h"
69 #include "search-path.h"
70 #include "gettext.h"
71 
72 #define _(str) gettext (str)
73 
74 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
75 
76 /* Contains exit status for case in which no premature exit occurs.  */
77 static int exit_status;
78 
79 /* If true include even fuzzy translations in output file.  */
80 static bool include_fuzzies = false;
81 
82 /* If true include even untranslated messages in output file.  */
83 static bool include_untranslated = false;
84 
85 /* Specifies name of the output file.  */
86 static const char *output_file_name;
87 
88 /* Java mode output file specification.  */
89 static bool java_mode;
90 static bool assume_java2;
91 static const char *java_resource_name;
92 static const char *java_locale_name;
93 static const char *java_class_directory;
94 static bool java_output_source;
95 
96 /* C# mode output file specification.  */
97 static bool csharp_mode;
98 static const char *csharp_resource_name;
99 static const char *csharp_locale_name;
100 static const char *csharp_base_directory;
101 
102 /* C# resources mode output file specification.  */
103 static bool csharp_resources_mode;
104 
105 /* Tcl mode output file specification.  */
106 static bool tcl_mode;
107 static const char *tcl_locale_name;
108 static const char *tcl_base_directory;
109 
110 /* Qt mode output file specification.  */
111 static bool qt_mode;
112 
113 /* Desktop Entry mode output file specification.  */
114 static bool desktop_mode;
115 static const char *desktop_locale_name;
116 static const char *desktop_template_name;
117 static const char *desktop_base_directory;
118 static hash_table desktop_keywords;
119 static bool desktop_default_keywords = true;
120 
121 /* XML mode output file specification.  */
122 static bool xml_mode;
123 static const char *xml_locale_name;
124 static const char *xml_template_name;
125 static const char *xml_base_directory;
126 static const char *xml_language;
127 static its_rule_list_ty *xml_its_rules;
128 
129 /* We may have more than one input file.  Domains with same names in
130    different files have to merged.  So we need a list of tables for
131    each output file.  */
132 struct msg_domain
133 {
134   /* List for mapping message IDs to message strings.  */
135   message_list_ty *mlp;
136   /* Name of domain these ID/String pairs are part of.  */
137   const char *domain_name;
138   /* Output file name.  */
139   const char *file_name;
140   /* Link to the next domain.  */
141   struct msg_domain *next;
142 };
143 static struct msg_domain *domain_list;
144 static struct msg_domain *current_domain;
145 
146 /* Be more verbose.  Use only 'fprintf' and 'multiline_warning' but not
147    'error' or 'multiline_error' to emit verbosity messages, because 'error'
148    and 'multiline_error' during PO file parsing cause the program to exit
149    with EXIT_FAILURE.  See function lex_end().  */
150 int verbose = 0;
151 
152 /* If true check strings according to format string rules for the
153    language.  */
154 static bool check_format_strings = false;
155 
156 /* If true check the header entry is present and complete.  */
157 static bool check_header = false;
158 
159 /* Check that domain directives can be satisfied.  */
160 static bool check_domain = false;
161 
162 /* Check that msgfmt's behaviour is semantically compatible with
163    X/Open msgfmt or XView msgfmt.  */
164 static bool check_compatibility = false;
165 
166 /* If true, consider that strings containing an '&' are menu items and
167    the '&' designates a keyboard accelerator, and verify that the translations
168    also have a keyboard accelerator.  */
169 static bool check_accelerators = false;
170 static char accelerator_char = '&';
171 
172 /* Counters for statistics on translations for the processed files.  */
173 static int msgs_translated;
174 static int msgs_untranslated;
175 static int msgs_fuzzy;
176 
177 /* If not zero print statistics about translation at the end.  */
178 static int do_statistics;
179 
180 /* Long options.  */
181 static const struct option long_options[] =
182 {
183   { "alignment", required_argument, NULL, 'a' },
184   { "check", no_argument, NULL, 'c' },
185   { "check-accelerators", optional_argument, NULL, CHAR_MAX + 1 },
186   { "check-compatibility", no_argument, NULL, 'C' },
187   { "check-domain", no_argument, NULL, CHAR_MAX + 2 },
188   { "check-format", no_argument, NULL, CHAR_MAX + 3 },
189   { "check-header", no_argument, NULL, CHAR_MAX + 4 },
190   { "csharp", no_argument, NULL, CHAR_MAX + 10 },
191   { "csharp-resources", no_argument, NULL, CHAR_MAX + 11 },
192   { "desktop", no_argument, NULL, CHAR_MAX + 15 },
193   { "directory", required_argument, NULL, 'D' },
194   { "endianness", required_argument, NULL, CHAR_MAX + 13 },
195   { "help", no_argument, NULL, 'h' },
196   { "java", no_argument, NULL, 'j' },
197   { "java2", no_argument, NULL, CHAR_MAX + 5 },
198   { "keyword", required_argument, NULL, 'k' },
199   { "language", required_argument, NULL, 'L' },
200   { "locale", required_argument, NULL, 'l' },
201   { "no-hash", no_argument, NULL, CHAR_MAX + 6 },
202   { "output-file", required_argument, NULL, 'o' },
203   { "properties-input", no_argument, NULL, 'P' },
204   { "qt", no_argument, NULL, CHAR_MAX + 9 },
205   { "resource", required_argument, NULL, 'r' },
206   { "source", no_argument, NULL, CHAR_MAX + 14 },
207   { "statistics", no_argument, &do_statistics, 1 },
208   { "strict", no_argument, NULL, 'S' },
209   { "stringtable-input", no_argument, NULL, CHAR_MAX + 8 },
210   { "tcl", no_argument, NULL, CHAR_MAX + 7 },
211   { "template", required_argument, NULL, CHAR_MAX + 16 },
212   { "use-fuzzy", no_argument, NULL, 'f' },
213   { "use-untranslated", no_argument, NULL, CHAR_MAX + 12 },
214   { "verbose", no_argument, NULL, 'v' },
215   { "version", no_argument, NULL, 'V' },
216   { "xml", no_argument, NULL, 'x' },
217   { NULL, 0, NULL, 0 }
218 };
219 
220 
221 /* Forward declaration of local functions.  */
222 _GL_NORETURN_FUNC static void usage (int status);
223 static const char *add_mo_suffix (const char *);
224 static struct msg_domain *new_domain (const char *name, const char *file_name);
225 static bool is_nonobsolete (const message_ty *mp);
226 static void read_catalog_file_msgfmt (char *filename,
227                                       catalog_input_format_ty input_syntax);
228 static int msgfmt_desktop_bulk (const char *directory,
229                                 const char *template_file_name,
230                                 hash_table *keywords,
231                                 const char *file_name);
232 static int msgfmt_xml_bulk (const char *directory,
233                             const char *template_file_name,
234                             its_rule_list_ty *its_rules,
235                             const char *file_name);
236 
237 
238 int
main(int argc,char * argv[])239 main (int argc, char *argv[])
240 {
241   int opt;
242   bool do_help = false;
243   bool do_version = false;
244   bool strict_uniforum = false;
245   catalog_input_format_ty input_syntax = &input_format_po;
246   int arg_i;
247   const char *canon_encoding;
248   struct msg_domain *domain;
249 
250   /* Set default value for global variables.  */
251   alignment = DEFAULT_OUTPUT_ALIGNMENT;
252   byteswap = 0 ^ ENDIANNESS;
253 
254   /* Set program name for messages.  */
255   set_program_name (argv[0]);
256   error_print_progname = maybe_print_progname;
257   error_one_per_line = 1;
258   exit_status = EXIT_SUCCESS;
259 
260   /* Set locale via LC_ALL.  */
261   setlocale (LC_ALL, "");
262 
263   /* Set the text message domain.  */
264   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
265   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
266   textdomain (PACKAGE);
267 
268   /* Ensure that write errors on stdout are detected.  */
269   atexit (close_stdout);
270 
271   while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:L:o:Pr:vVx",
272                              long_options, NULL))
273          != EOF)
274     switch (opt)
275       {
276       case '\0':                /* Long option.  */
277         break;
278       case 'a':
279         {
280           char *endp;
281           size_t new_align = strtoul (optarg, &endp, 0);
282 
283           if (endp != optarg)
284             alignment = new_align;
285         }
286         break;
287       case 'c':
288         check_domain = true;
289         check_format_strings = true;
290         check_header = true;
291         break;
292       case 'C':
293         check_compatibility = true;
294         break;
295       case 'd':
296         java_class_directory = optarg;
297         csharp_base_directory = optarg;
298         tcl_base_directory = optarg;
299         desktop_base_directory = optarg;
300         xml_base_directory = optarg;
301         break;
302       case 'D':
303         dir_list_append (optarg);
304         break;
305       case 'f':
306         include_fuzzies = true;
307         break;
308       case 'h':
309         do_help = true;
310         break;
311       case 'j':
312         java_mode = true;
313         break;
314       case 'k':
315         if (optarg == NULL)
316           desktop_default_keywords = false;
317         else
318           {
319             if (desktop_keywords.table == NULL)
320               {
321                 hash_init (&desktop_keywords, 100);
322                 desktop_default_keywords = false;
323               }
324 
325             desktop_add_keyword (&desktop_keywords, optarg, false);
326           }
327         break;
328       case 'l':
329         java_locale_name = optarg;
330         csharp_locale_name = optarg;
331         tcl_locale_name = optarg;
332         desktop_locale_name = optarg;
333         xml_locale_name = optarg;
334         break;
335       case 'L':
336         xml_language = optarg;
337         break;
338       case 'o':
339         output_file_name = optarg;
340         break;
341       case 'P':
342         input_syntax = &input_format_properties;
343         break;
344       case 'r':
345         java_resource_name = optarg;
346         csharp_resource_name = optarg;
347         break;
348       case 'S':
349         strict_uniforum = true;
350         break;
351       case 'v':
352         verbose++;
353         break;
354       case 'V':
355         do_version = true;
356         break;
357       case 'x':
358         xml_mode = true;
359         break;
360       case CHAR_MAX + 1: /* --check-accelerators */
361         check_accelerators = true;
362         if (optarg != NULL)
363           {
364             if (optarg[0] != '\0' && ispunct ((unsigned char) optarg[0])
365                 && optarg[1] == '\0')
366               accelerator_char = optarg[0];
367             else
368               error (EXIT_FAILURE, 0,
369                      _("the argument to %s should be a single punctuation character"),
370                      "--check-accelerators");
371           }
372         break;
373       case CHAR_MAX + 2: /* --check-domain */
374         check_domain = true;
375         break;
376       case CHAR_MAX + 3: /* --check-format */
377         check_format_strings = true;
378         break;
379       case CHAR_MAX + 4: /* --check-header */
380         check_header = true;
381         break;
382       case CHAR_MAX + 5: /* --java2 */
383         java_mode = true;
384         assume_java2 = true;
385         break;
386       case CHAR_MAX + 6: /* --no-hash */
387         no_hash_table = true;
388         break;
389       case CHAR_MAX + 7: /* --tcl */
390         tcl_mode = true;
391         break;
392       case CHAR_MAX + 8: /* --stringtable-input */
393         input_syntax = &input_format_stringtable;
394         break;
395       case CHAR_MAX + 9: /* --qt */
396         qt_mode = true;
397         break;
398       case CHAR_MAX + 10: /* --csharp */
399         csharp_mode = true;
400         break;
401       case CHAR_MAX + 11: /* --csharp-resources */
402         csharp_resources_mode = true;
403         break;
404       case CHAR_MAX + 12: /* --use-untranslated (undocumented) */
405         include_untranslated = true;
406         break;
407       case CHAR_MAX + 13: /* --endianness={big|little} */
408         {
409           int endianness;
410 
411           if (strcmp (optarg, "big") == 0)
412             endianness = 1;
413           else if (strcmp (optarg, "little") == 0)
414             endianness = 0;
415           else
416             error (EXIT_FAILURE, 0, _("invalid endianness: %s"), optarg);
417 
418           byteswap = endianness ^ ENDIANNESS;
419         }
420         break;
421       case CHAR_MAX + 14: /* --source */
422         java_output_source = true;
423         break;
424       case CHAR_MAX + 15: /* --desktop */
425         desktop_mode = true;
426         break;
427       case CHAR_MAX + 16: /* --template=TEMPLATE */
428         desktop_template_name = optarg;
429         xml_template_name = optarg;
430         break;
431       default:
432         usage (EXIT_FAILURE);
433         break;
434       }
435 
436   /* Version information is requested.  */
437   if (do_version)
438     {
439       printf ("%s (GNU %s) %s\n", last_component (program_name),
440               PACKAGE, VERSION);
441       /* xgettext: no-wrap */
442       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
443 License GPLv3+: GNU GPL version 3 or later <%s>\n\
444 This is free software: you are free to change and redistribute it.\n\
445 There is NO WARRANTY, to the extent permitted by law.\n\
446 "),
447               "1995-2020", "https://gnu.org/licenses/gpl.html");
448       printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper"));
449       exit (EXIT_SUCCESS);
450     }
451 
452   /* Help is requested.  */
453   if (do_help)
454     usage (EXIT_SUCCESS);
455 
456   /* Test whether we have a .po file name as argument.  */
457   if (optind >= argc
458       && !(desktop_mode && desktop_base_directory)
459       && !(xml_mode && xml_base_directory))
460     {
461       error (EXIT_SUCCESS, 0, _("no input file given"));
462       usage (EXIT_FAILURE);
463     }
464   if (optind < argc
465       && ((desktop_mode && desktop_base_directory)
466           || (xml_mode && xml_base_directory)))
467     {
468       error (EXIT_SUCCESS, 0,
469              _("no input file should be given if %s and %s are specified"),
470              desktop_mode ? "--desktop" : "--xml", "-d");
471       usage (EXIT_FAILURE);
472     }
473 
474   /* Check for contradicting options.  */
475   {
476     unsigned int modes =
477       (java_mode ? 1 : 0)
478       | (csharp_mode ? 2 : 0)
479       | (csharp_resources_mode ? 4 : 0)
480       | (tcl_mode ? 8 : 0)
481       | (qt_mode ? 16 : 0)
482       | (desktop_mode ? 32 : 0)
483       | (xml_mode ? 64 : 0);
484     static const char *mode_options[] =
485       { "--java", "--csharp", "--csharp-resources", "--tcl", "--qt",
486         "--desktop", "--xml" };
487     /* More than one bit set?  */
488     if (modes & (modes - 1))
489       {
490         const char *first_option;
491         const char *second_option;
492         unsigned int i;
493         for (i = 0; ; i++)
494           if (modes & (1 << i))
495             break;
496         first_option = mode_options[i];
497         for (i = i + 1; ; i++)
498           if (modes & (1 << i))
499             break;
500         second_option = mode_options[i];
501         error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
502                first_option, second_option);
503       }
504   }
505   if (java_mode)
506     {
507       if (output_file_name != NULL)
508         {
509           error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
510                  "--java", "--output-file");
511         }
512       if (java_class_directory == NULL)
513         {
514           error (EXIT_SUCCESS, 0,
515                  _("%s requires a \"-d directory\" specification"),
516                  "--java");
517           usage (EXIT_FAILURE);
518         }
519     }
520   else if (csharp_mode)
521     {
522       if (output_file_name != NULL)
523         {
524           error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
525                  "--csharp", "--output-file");
526         }
527       if (csharp_locale_name == NULL)
528         {
529           error (EXIT_SUCCESS, 0,
530                  _("%s requires a \"-l locale\" specification"),
531                  "--csharp");
532           usage (EXIT_FAILURE);
533         }
534       if (csharp_base_directory == NULL)
535         {
536           error (EXIT_SUCCESS, 0,
537                  _("%s requires a \"-d directory\" specification"),
538                  "--csharp");
539           usage (EXIT_FAILURE);
540         }
541     }
542   else if (tcl_mode)
543     {
544       if (output_file_name != NULL)
545         {
546           error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
547                  "--tcl", "--output-file");
548         }
549       if (tcl_locale_name == NULL)
550         {
551           error (EXIT_SUCCESS, 0,
552                  _("%s requires a \"-l locale\" specification"),
553                  "--tcl");
554           usage (EXIT_FAILURE);
555         }
556       if (tcl_base_directory == NULL)
557         {
558           error (EXIT_SUCCESS, 0,
559                  _("%s requires a \"-d directory\" specification"),
560                  "--tcl");
561           usage (EXIT_FAILURE);
562         }
563     }
564   else if (desktop_mode)
565     {
566       if (desktop_template_name == NULL)
567         {
568           error (EXIT_SUCCESS, 0,
569                  _("%s requires a \"--template template\" specification"),
570                  "--desktop");
571           usage (EXIT_FAILURE);
572         }
573       if (output_file_name == NULL)
574         {
575           error (EXIT_SUCCESS, 0,
576                  _("%s requires a \"-o file\" specification"),
577                  "--desktop");
578           usage (EXIT_FAILURE);
579         }
580       if (desktop_base_directory != NULL && desktop_locale_name != NULL)
581         error (EXIT_FAILURE, 0,
582                _("%s and %s are mutually exclusive in %s"),
583                "-d", "-l", "--desktop");
584       if (desktop_base_directory == NULL && desktop_locale_name == NULL)
585         {
586           error (EXIT_SUCCESS, 0,
587                  _("%s requires a \"-l locale\" specification"),
588                  "--desktop");
589           usage (EXIT_FAILURE);
590         }
591     }
592   else if (xml_mode)
593     {
594       if (xml_template_name == NULL)
595         {
596           error (EXIT_SUCCESS, 0,
597                  _("%s requires a \"--template template\" specification"),
598                  "--xml");
599           usage (EXIT_FAILURE);
600         }
601       if (output_file_name == NULL)
602         {
603           error (EXIT_SUCCESS, 0,
604                  _("%s requires a \"-o file\" specification"),
605                  "--xml");
606           usage (EXIT_FAILURE);
607         }
608       if (xml_base_directory != NULL && xml_locale_name != NULL)
609         error (EXIT_FAILURE, 0,
610                _("%s and %s are mutually exclusive in %s"),
611                "-d", "-l", "--xml");
612       if (xml_base_directory == NULL && xml_locale_name == NULL)
613         {
614           error (EXIT_SUCCESS, 0,
615                  _("%s requires a \"-l locale\" specification"),
616                  "--xml");
617           usage (EXIT_FAILURE);
618         }
619     }
620   else
621     {
622       if (java_resource_name != NULL)
623         {
624           error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"),
625                  "--resource", "--java", "--csharp");
626           usage (EXIT_FAILURE);
627         }
628       if (java_locale_name != NULL)
629         {
630           error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"),
631                  "--locale", "--java", "--csharp", "--tcl");
632           usage (EXIT_FAILURE);
633         }
634       if (java_class_directory != NULL)
635         {
636           error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"),
637                  "-d", "--java", "--csharp", "--tcl");
638           usage (EXIT_FAILURE);
639         }
640     }
641 
642   if (desktop_mode && desktop_default_keywords)
643     {
644       if (desktop_keywords.table == NULL)
645         hash_init (&desktop_keywords, 100);
646       desktop_add_default_keywords (&desktop_keywords);
647     }
648 
649   /* Bulk processing mode for .desktop files.
650      Process all .po files in desktop_base_directory.  */
651   if (desktop_mode && desktop_base_directory)
652     {
653       exit_status = msgfmt_desktop_bulk (desktop_base_directory,
654                                          desktop_template_name,
655                                          &desktop_keywords,
656                                          output_file_name);
657       if (desktop_keywords.table != NULL)
658         hash_destroy (&desktop_keywords);
659       exit (exit_status);
660     }
661 
662   if (xml_mode)
663     {
664       char **its_dirs;
665       char **dirs;
666       locating_rule_list_ty *its_locating_rules;
667       const char *its_basename;
668 
669       its_dirs = get_search_path ("its");
670       its_locating_rules = locating_rule_list_alloc ();
671       for (dirs = its_dirs; *dirs != NULL; dirs++)
672         locating_rule_list_add_from_directory (its_locating_rules, *dirs);
673 
674       its_basename = locating_rule_list_locate (its_locating_rules,
675                                                 xml_template_name,
676                                                 xml_language);
677 
678       if (its_basename != NULL)
679         {
680           size_t j;
681 
682           xml_its_rules = its_rule_list_alloc ();
683           for (j = 0; its_dirs[j] != NULL; j++)
684             {
685               char *its_filename =
686                 xconcatenated_filename (its_dirs[j], its_basename, NULL);
687               struct stat statbuf;
688               bool ok = false;
689 
690               if (stat (its_filename, &statbuf) == 0)
691                 ok = its_rule_list_add_from_file (xml_its_rules, its_filename);
692               free (its_filename);
693               if (ok)
694                 break;
695             }
696           if (its_dirs[j] == NULL)
697             {
698               its_rule_list_free (xml_its_rules);
699               xml_its_rules = NULL;
700             }
701         }
702       locating_rule_list_free (its_locating_rules);
703 
704       for (dirs = its_dirs; *dirs != NULL; dirs++)
705         free (*dirs);
706       free (its_dirs);
707 
708       if (xml_its_rules == NULL)
709         error (EXIT_FAILURE, 0, _("cannot locate ITS rules for %s"),
710                xml_template_name);
711     }
712 
713   /* Bulk processing mode for XML files.
714      Process all .po files in xml_base_directory.  */
715   if (xml_mode && xml_base_directory)
716     {
717       exit_status = msgfmt_xml_bulk (xml_base_directory,
718                                      xml_template_name,
719                                      xml_its_rules,
720                                      output_file_name);
721       exit (exit_status);
722     }
723 
724   /* The -o option determines the name of the domain and therefore
725      the output file.  */
726   if (output_file_name != NULL)
727     current_domain =
728       new_domain (output_file_name,
729                   strict_uniforum && !csharp_resources_mode && !qt_mode
730                   ? add_mo_suffix (output_file_name)
731                   : output_file_name);
732 
733   /* Process all given .po files.  */
734   for (arg_i = optind; arg_i < argc; arg_i++)
735     {
736       /* Remember that we currently have not specified any domain.  This
737          is of course not true when we saw the -o option.  */
738       if (output_file_name == NULL)
739         current_domain = NULL;
740 
741       /* And process the input file.  */
742       read_catalog_file_msgfmt (argv[arg_i], input_syntax);
743     }
744 
745   /* We know a priori that some input_syntax->parse() functions convert
746      strings to UTF-8.  */
747   canon_encoding = (input_syntax->produces_utf8 ? po_charset_utf8 : NULL);
748 
749   /* Remove obsolete messages.  They were only needed for duplicate
750      checking.  */
751   for (domain = domain_list; domain != NULL; domain = domain->next)
752     message_list_remove_if_not (domain->mlp, is_nonobsolete);
753 
754   /* Perform all kinds of checks: plural expressions, format strings, ...  */
755   {
756     int nerrors = 0;
757 
758     for (domain = domain_list; domain != NULL; domain = domain->next)
759       nerrors +=
760         check_message_list (domain->mlp,
761                             /* Untranslated and fuzzy messages have already
762                                been dealt with during parsing, see below in
763                                msgfmt_frob_new_message.  */
764                             0, 0,
765                             1, check_format_strings, check_header,
766                             check_compatibility,
767                             check_accelerators, accelerator_char);
768 
769     /* Exit with status 1 on any error.  */
770     if (nerrors > 0)
771       {
772         error (0, 0,
773                ngettext ("found %d fatal error", "found %d fatal errors",
774                          nerrors),
775                nerrors);
776         exit_status = EXIT_FAILURE;
777       }
778   }
779 
780   /* Now write out all domains.  */
781   for (domain = domain_list; domain != NULL; domain = domain->next)
782     {
783       if (java_mode)
784         {
785           if (msgdomain_write_java (domain->mlp, canon_encoding,
786                                     java_resource_name, java_locale_name,
787                                     java_class_directory, assume_java2,
788                                     java_output_source))
789             exit_status = EXIT_FAILURE;
790         }
791       else if (csharp_mode)
792         {
793           if (msgdomain_write_csharp (domain->mlp, canon_encoding,
794                                       csharp_resource_name, csharp_locale_name,
795                                       csharp_base_directory))
796             exit_status = EXIT_FAILURE;
797         }
798       else if (csharp_resources_mode)
799         {
800           if (msgdomain_write_csharp_resources (domain->mlp, canon_encoding,
801                                                 domain->domain_name,
802                                                 domain->file_name))
803             exit_status = EXIT_FAILURE;
804         }
805       else if (tcl_mode)
806         {
807           if (msgdomain_write_tcl (domain->mlp, canon_encoding,
808                                    tcl_locale_name, tcl_base_directory))
809             exit_status = EXIT_FAILURE;
810         }
811       else if (qt_mode)
812         {
813           if (msgdomain_write_qt (domain->mlp, canon_encoding,
814                                   domain->domain_name, domain->file_name))
815             exit_status = EXIT_FAILURE;
816         }
817       else if (desktop_mode)
818         {
819           if (msgdomain_write_desktop (domain->mlp, canon_encoding,
820                                        desktop_locale_name,
821                                        desktop_template_name,
822                                        &desktop_keywords,
823                                        domain->file_name))
824             exit_status = EXIT_FAILURE;
825 
826           if (desktop_keywords.table != NULL)
827             hash_destroy (&desktop_keywords);
828         }
829       else if (xml_mode)
830         {
831           if (msgdomain_write_xml (domain->mlp, canon_encoding,
832                                    xml_locale_name,
833                                    xml_template_name,
834                                    xml_its_rules,
835                                    domain->file_name))
836             exit_status = EXIT_FAILURE;
837         }
838       else
839         {
840           if (msgdomain_write_mo (domain->mlp, domain->domain_name,
841                                   domain->file_name))
842             exit_status = EXIT_FAILURE;
843         }
844 
845       /* List is not used anymore.  */
846       message_list_free (domain->mlp, 0);
847     }
848 
849   /* Print statistics if requested.  */
850   if (verbose || do_statistics)
851     {
852       if (do_statistics + verbose >= 2 && optind < argc)
853         {
854           /* Print the input file name(s) in front of the statistics line.  */
855           char *all_input_file_names;
856 
857           {
858             string_list_ty input_file_names;
859 
860             string_list_init (&input_file_names);;
861             for (arg_i = optind; arg_i < argc; arg_i++)
862               string_list_append (&input_file_names, argv[arg_i]);
863             all_input_file_names =
864               string_list_join (&input_file_names, ", ", '\0', false);
865             string_list_destroy (&input_file_names);
866           }
867 
868           /* TRANSLATORS: The prefix before a statistics message.  The argument
869              is a file name or a comma separated list of file names.  */
870           fprintf (stderr, _("%s: "), all_input_file_names);
871           free (all_input_file_names);
872         }
873       fprintf (stderr,
874                ngettext ("%d translated message", "%d translated messages",
875                          msgs_translated),
876                msgs_translated);
877       if (msgs_fuzzy > 0)
878         fprintf (stderr,
879                  ngettext (", %d fuzzy translation", ", %d fuzzy translations",
880                            msgs_fuzzy),
881                  msgs_fuzzy);
882       if (msgs_untranslated > 0)
883         fprintf (stderr,
884                  ngettext (", %d untranslated message",
885                            ", %d untranslated messages",
886                            msgs_untranslated),
887                  msgs_untranslated);
888       fputs (".\n", stderr);
889     }
890 
891   exit (exit_status);
892 }
893 
894 
895 /* Display usage information and exit.  */
896 static void
usage(int status)897 usage (int status)
898 {
899   if (status != EXIT_SUCCESS)
900     fprintf (stderr, _("Try '%s --help' for more information.\n"),
901              program_name);
902   else
903     {
904       printf (_("\
905 Usage: %s [OPTION] filename.po ...\n\
906 "), program_name);
907       printf ("\n");
908       printf (_("\
909 Generate binary message catalog from textual translation description.\n\
910 "));
911       printf ("\n");
912       /* xgettext: no-wrap */
913       printf (_("\
914 Mandatory arguments to long options are mandatory for short options too.\n\
915 Similarly for optional arguments.\n\
916 "));
917       printf ("\n");
918       printf (_("\
919 Input file location:\n"));
920       printf (_("\
921   filename.po ...             input files\n"));
922       printf (_("\
923   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
924       printf (_("\
925 If input file is -, standard input is read.\n"));
926       printf ("\n");
927       printf (_("\
928 Operation mode:\n"));
929       printf (_("\
930   -j, --java                  Java mode: generate a Java ResourceBundle class\n"));
931       printf (_("\
932       --java2                 like --java, and assume Java2 (JDK 1.2 or higher)\n"));
933       printf (_("\
934       --csharp                C# mode: generate a .NET .dll file\n"));
935       printf (_("\
936       --csharp-resources      C# resources mode: generate a .NET .resources file\n"));
937       printf (_("\
938       --tcl                   Tcl mode: generate a tcl/msgcat .msg file\n"));
939       printf (_("\
940       --qt                    Qt mode: generate a Qt .qm file\n"));
941       printf (_("\
942       --desktop               Desktop Entry mode: generate a .desktop file\n"));
943       printf (_("\
944       --xml                   XML mode: generate XML file\n"));
945       printf ("\n");
946       printf (_("\
947 Output file location:\n"));
948       printf (_("\
949   -o, --output-file=FILE      write output to specified file\n"));
950       printf (_("\
951       --strict                enable strict Uniforum mode\n"));
952       printf (_("\
953 If output file is -, output is written to standard output.\n"));
954       printf ("\n");
955       printf (_("\
956 Output file location in Java mode:\n"));
957       printf (_("\
958   -r, --resource=RESOURCE     resource name\n"));
959       printf (_("\
960   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
961       printf (_("\
962       --source                produce a .java file, instead of a .class file\n"));
963       printf (_("\
964   -d DIRECTORY                base directory of classes directory hierarchy\n"));
965       printf (_("\
966 The class name is determined by appending the locale name to the resource name,\n\
967 separated with an underscore.  The -d option is mandatory.  The class is\n\
968 written under the specified directory.\n\
969 "));
970       printf ("\n");
971       printf (_("\
972 Output file location in C# mode:\n"));
973       printf (_("\
974   -r, --resource=RESOURCE     resource name\n"));
975       printf (_("\
976   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
977       printf (_("\
978   -d DIRECTORY                base directory for locale dependent .dll files\n"));
979       printf (_("\
980 The -l and -d options are mandatory.  The .dll file is written in a\n\
981 subdirectory of the specified directory whose name depends on the locale.\n"));
982       printf ("\n");
983       printf (_("\
984 Output file location in Tcl mode:\n"));
985       printf (_("\
986   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
987       printf (_("\
988   -d DIRECTORY                base directory of .msg message catalogs\n"));
989       printf (_("\
990 The -l and -d options are mandatory.  The .msg file is written in the\n\
991 specified directory.\n"));
992       printf ("\n");
993       printf (_("\
994 Desktop Entry mode options:\n"));
995       printf (_("\
996   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
997       printf (_("\
998   -o, --output-file=FILE      write output to specified file\n"));
999       printf (_("\
1000   --template=TEMPLATE         a .desktop file used as a template\n"));
1001       printf (_("\
1002   -d DIRECTORY                base directory of .po files\n"));
1003       printf (_("\
1004   -kWORD, --keyword=WORD      look for WORD as an additional keyword\n\
1005   -k, --keyword               do not to use default keywords\n"));
1006       printf (_("\
1007 The -l, -o, and --template options are mandatory.  If -D is specified, input\n\
1008 files are read from the directory instead of the command line arguments.\n"));
1009       printf ("\n");
1010       printf (_("\
1011 XML mode options:\n"));
1012       printf (_("\
1013   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
1014       printf (_("\
1015   -L, --language=NAME         recognise the specified XML language\n"));
1016       printf (_("\
1017   -o, --output-file=FILE      write output to specified file\n"));
1018       printf (_("\
1019   --template=TEMPLATE         an XML file used as a template\n"));
1020       printf (_("\
1021   -d DIRECTORY                base directory of .po files\n"));
1022       printf (_("\
1023 The -l, -o, and --template options are mandatory.  If -D is specified, input\n\
1024 files are read from the directory instead of the command line arguments.\n"));
1025       printf ("\n");
1026       printf (_("\
1027 Input file syntax:\n"));
1028       printf (_("\
1029   -P, --properties-input      input files are in Java .properties syntax\n"));
1030       printf (_("\
1031       --stringtable-input     input files are in NeXTstep/GNUstep .strings\n\
1032                               syntax\n"));
1033       printf ("\n");
1034       printf (_("\
1035 Input file interpretation:\n"));
1036       printf (_("\
1037   -c, --check                 perform all the checks implied by\n\
1038                                 --check-format, --check-header, --check-domain\n"));
1039       printf (_("\
1040       --check-format          check language dependent format strings\n"));
1041       printf (_("\
1042       --check-header          verify presence and contents of the header entry\n"));
1043       printf (_("\
1044       --check-domain          check for conflicts between domain directives\n\
1045                                 and the --output-file option\n"));
1046       printf (_("\
1047   -C, --check-compatibility   check that GNU msgfmt behaves like X/Open msgfmt\n"));
1048       printf (_("\
1049       --check-accelerators[=CHAR]  check presence of keyboard accelerators for\n\
1050                                 menu items\n"));
1051       printf (_("\
1052   -f, --use-fuzzy             use fuzzy entries in output\n"));
1053       printf ("\n");
1054       printf (_("\
1055 Output details:\n"));
1056       printf (_("\
1057   -a, --alignment=NUMBER      align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT);
1058       printf (_("\
1059       --endianness=BYTEORDER  write out 32-bit numbers in the given byte order\n\
1060                                 (big or little, default depends on platform)\n"));
1061       printf (_("\
1062       --no-hash               binary file will not include the hash table\n"));
1063       printf ("\n");
1064       printf (_("\
1065 Informative output:\n"));
1066       printf (_("\
1067   -h, --help                  display this help and exit\n"));
1068       printf (_("\
1069   -V, --version               output version information and exit\n"));
1070       printf (_("\
1071       --statistics            print statistics about translations\n"));
1072       printf (_("\
1073   -v, --verbose               increase verbosity level\n"));
1074       printf ("\n");
1075       /* TRANSLATORS: The first placeholder is the web address of the Savannah
1076          project of this package.  The second placeholder is the bug-reporting
1077          email address for this package.  Please add _another line_ saying
1078          "Report translation bugs to <...>\n" with the address for translation
1079          bugs (typically your translation team's web or email address).  */
1080       printf(_("\
1081 Report bugs in the bug tracker at <%s>\n\
1082 or by email to <%s>.\n"),
1083              "https://savannah.gnu.org/projects/gettext",
1084              "bug-gettext@gnu.org");
1085     }
1086 
1087   exit (status);
1088 }
1089 
1090 
1091 static const char *
add_mo_suffix(const char * fname)1092 add_mo_suffix (const char *fname)
1093 {
1094   size_t len;
1095   char *result;
1096 
1097   len = strlen (fname);
1098   if (len > 3 && memcmp (fname + len - 3, ".mo", 3) == 0)
1099     return fname;
1100   if (len > 4 && memcmp (fname + len - 4, ".gmo", 4) == 0)
1101     return fname;
1102   result = XNMALLOC (len + 4, char);
1103   stpcpy (stpcpy (result, fname), ".mo");
1104   return result;
1105 }
1106 
1107 
1108 static struct msg_domain *
new_domain(const char * name,const char * file_name)1109 new_domain (const char *name, const char *file_name)
1110 {
1111   struct msg_domain **p_dom = &domain_list;
1112 
1113   while (*p_dom != NULL && strcmp (name, (*p_dom)->domain_name) != 0)
1114     p_dom = &(*p_dom)->next;
1115 
1116   if (*p_dom == NULL)
1117     {
1118       struct msg_domain *domain;
1119 
1120       domain = XMALLOC (struct msg_domain);
1121       domain->mlp = message_list_alloc (true);
1122       domain->domain_name = name;
1123       domain->file_name = file_name;
1124       domain->next = NULL;
1125       *p_dom = domain;
1126     }
1127 
1128   return *p_dom;
1129 }
1130 
1131 
1132 static bool
is_nonobsolete(const message_ty * mp)1133 is_nonobsolete (const message_ty *mp)
1134 {
1135   return !mp->obsolete;
1136 }
1137 
1138 
1139 /* The rest of the file defines a subclass msgfmt_catalog_reader_ty of
1140    default_catalog_reader_ty.  Its particularities are:
1141    - The header entry check is performed on-the-fly.
1142    - Comments are not stored, they are discarded right away.
1143      (This is achieved by setting handle_comments = false.)
1144    - The multi-domain handling is adapted to our domain_list.
1145  */
1146 
1147 
1148 /* This structure defines a derived class of the default_catalog_reader_ty
1149    class.  (See read-catalog-abstract.h for an explanation.)  */
1150 typedef struct msgfmt_catalog_reader_ty msgfmt_catalog_reader_ty;
1151 struct msgfmt_catalog_reader_ty
1152 {
1153   /* inherited instance variables, etc */
1154   DEFAULT_CATALOG_READER_TY
1155 
1156   bool has_header_entry;
1157 };
1158 
1159 
1160 /* Prepare for first message.  */
1161 static void
msgfmt_constructor(abstract_catalog_reader_ty * that)1162 msgfmt_constructor (abstract_catalog_reader_ty *that)
1163 {
1164   msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
1165 
1166   /* Invoke superclass constructor.  */
1167   default_constructor (that);
1168 
1169   this->has_header_entry = false;
1170 }
1171 
1172 
1173 /* Some checks after whole file is read.  */
1174 static void
msgfmt_parse_debrief(abstract_catalog_reader_ty * that)1175 msgfmt_parse_debrief (abstract_catalog_reader_ty *that)
1176 {
1177   msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
1178 
1179   /* Invoke superclass method.  */
1180   default_parse_debrief (that);
1181 
1182   /* Test whether header entry was found.  */
1183   if (check_header)
1184     {
1185       if (!this->has_header_entry)
1186         {
1187           multiline_error (xasprintf ("%s: ", this->file_name),
1188                            xasprintf (_("warning: PO file header missing or invalid\n")));
1189           multiline_error (NULL,
1190                            xasprintf (_("warning: charset conversion will not work\n")));
1191         }
1192     }
1193 }
1194 
1195 
1196 /* Set 'domain' directive when seen in .po file.  */
1197 static void
msgfmt_set_domain(default_catalog_reader_ty * this,char * name)1198 msgfmt_set_domain (default_catalog_reader_ty *this, char *name)
1199 {
1200   /* If no output file was given, we change it with each 'domain'
1201      directive.  */
1202   if (!java_mode && !csharp_mode && !csharp_resources_mode && !tcl_mode
1203       && !qt_mode && !desktop_mode && !xml_mode && output_file_name == NULL)
1204     {
1205       size_t correct;
1206 
1207       correct = strcspn (name, INVALID_PATH_CHAR);
1208       if (name[correct] != '\0')
1209         {
1210           exit_status = EXIT_FAILURE;
1211           if (correct == 0)
1212             {
1213               error (0, 0,
1214                      _("domain name \"%s\" not suitable as file name"), name);
1215               return;
1216             }
1217           else
1218             error (0, 0,
1219                    _("domain name \"%s\" not suitable as file name: will use prefix"),
1220                    name);
1221           name[correct] = '\0';
1222         }
1223 
1224       /* Set new domain.  */
1225       current_domain = new_domain (name, add_mo_suffix (name));
1226       this->domain = current_domain->domain_name;
1227       this->mlp = current_domain->mlp;
1228     }
1229   else
1230     {
1231       if (check_domain)
1232         po_gram_error_at_line (&gram_pos,
1233                                _("'domain %s' directive ignored"), name);
1234 
1235       /* NAME was allocated in po-gram-gen.y but is not used anywhere.  */
1236       free (name);
1237     }
1238 }
1239 
1240 
1241 static void
msgfmt_add_message(default_catalog_reader_ty * this,char * msgctxt,char * msgid,lex_pos_ty * msgid_pos,char * msgid_plural,char * msgstr,size_t msgstr_len,lex_pos_ty * msgstr_pos,char * prev_msgctxt,char * prev_msgid,char * prev_msgid_plural,bool force_fuzzy,bool obsolete)1242 msgfmt_add_message (default_catalog_reader_ty *this,
1243                     char *msgctxt,
1244                     char *msgid,
1245                     lex_pos_ty *msgid_pos,
1246                     char *msgid_plural,
1247                     char *msgstr, size_t msgstr_len,
1248                     lex_pos_ty *msgstr_pos,
1249                     char *prev_msgctxt,
1250                     char *prev_msgid,
1251                     char *prev_msgid_plural,
1252                     bool force_fuzzy, bool obsolete)
1253 {
1254   /* Check whether already a domain is specified.  If not, use default
1255      domain.  */
1256   if (current_domain == NULL)
1257     {
1258       current_domain = new_domain (MESSAGE_DOMAIN_DEFAULT,
1259                                    add_mo_suffix (MESSAGE_DOMAIN_DEFAULT));
1260       /* Keep current_domain and this->domain synchronized.  */
1261       this->domain = current_domain->domain_name;
1262       this->mlp = current_domain->mlp;
1263     }
1264 
1265   /* Invoke superclass method.  */
1266   default_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
1267                        msgstr, msgstr_len, msgstr_pos,
1268                        prev_msgctxt, prev_msgid, prev_msgid_plural,
1269                        force_fuzzy, obsolete);
1270 }
1271 
1272 
1273 static void
msgfmt_frob_new_message(default_catalog_reader_ty * that,message_ty * mp,const lex_pos_ty * msgid_pos,const lex_pos_ty * msgstr_pos)1274 msgfmt_frob_new_message (default_catalog_reader_ty *that, message_ty *mp,
1275                          const lex_pos_ty *msgid_pos,
1276                          const lex_pos_ty *msgstr_pos)
1277 {
1278   msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
1279 
1280   if (!mp->obsolete)
1281     {
1282       /* Don't emit untranslated entries.
1283          Also don't emit fuzzy entries, unless --use-fuzzy was specified.
1284          But ignore fuzziness of the header entry.  */
1285       if ((!include_untranslated && mp->msgstr[0] == '\0')
1286           || (!include_fuzzies && mp->is_fuzzy && !is_header (mp)))
1287         {
1288           if (check_compatibility)
1289             {
1290               error_with_progname = false;
1291               error_at_line (0, 0, mp->pos.file_name, mp->pos.line_number,
1292                              (mp->msgstr[0] == '\0'
1293                               ? _("empty 'msgstr' entry ignored")
1294                               : _("fuzzy 'msgstr' entry ignored")));
1295               error_with_progname = true;
1296             }
1297 
1298           /* Increment counter for fuzzy/untranslated messages.  */
1299           if (mp->msgstr[0] == '\0')
1300             ++msgs_untranslated;
1301           else
1302             ++msgs_fuzzy;
1303 
1304           mp->obsolete = true;
1305         }
1306       else
1307         {
1308           /* Test for header entry.  */
1309           if (is_header (mp))
1310             {
1311               this->has_header_entry = true;
1312             }
1313           else
1314             /* We don't count the header entry in the statistic so place
1315                the counter incrementation here.  */
1316             if (mp->is_fuzzy)
1317               ++msgs_fuzzy;
1318             else
1319               ++msgs_translated;
1320         }
1321     }
1322 }
1323 
1324 
1325 /* Test for '#, fuzzy' comments and warn.  */
1326 static void
msgfmt_comment_special(abstract_catalog_reader_ty * that,const char * s)1327 msgfmt_comment_special (abstract_catalog_reader_ty *that, const char *s)
1328 {
1329   msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
1330 
1331   /* Invoke superclass method.  */
1332   default_comment_special (that, s);
1333 
1334   if (this->is_fuzzy)
1335     {
1336       static bool warned = false;
1337 
1338       if (!include_fuzzies && check_compatibility && !warned)
1339         {
1340           warned = true;
1341           error (0, 0,
1342                  _("%s: warning: source file contains fuzzy translation"),
1343                  gram_pos.file_name);
1344         }
1345     }
1346 }
1347 
1348 
1349 /* So that the one parser can be used for multiple programs, and also
1350    use good data hiding and encapsulation practices, an object
1351    oriented approach has been taken.  An object instance is allocated,
1352    and all actions resulting from the parse will be through
1353    invocations of method functions of that object.  */
1354 
1355 static default_catalog_reader_class_ty msgfmt_methods =
1356 {
1357   {
1358     sizeof (msgfmt_catalog_reader_ty),
1359     msgfmt_constructor,
1360     default_destructor,
1361     default_parse_brief,
1362     msgfmt_parse_debrief,
1363     default_directive_domain,
1364     default_directive_message,
1365     default_comment,
1366     default_comment_dot,
1367     default_comment_filepos,
1368     msgfmt_comment_special
1369   },
1370   msgfmt_set_domain, /* set_domain */
1371   msgfmt_add_message, /* add_message */
1372   msgfmt_frob_new_message /* frob_new_message */
1373 };
1374 
1375 
1376 /* Read .po file FILENAME and store translation pairs.  */
1377 static void
read_catalog_file_msgfmt(char * filename,catalog_input_format_ty input_syntax)1378 read_catalog_file_msgfmt (char *filename, catalog_input_format_ty input_syntax)
1379 {
1380   char *real_filename;
1381   FILE *fp = open_catalog_file (filename, &real_filename, true);
1382   default_catalog_reader_ty *pop;
1383 
1384   pop = default_catalog_reader_alloc (&msgfmt_methods);
1385   pop->handle_comments = false;
1386   pop->allow_domain_directives = true;
1387   pop->allow_duplicates = false;
1388   pop->allow_duplicates_if_same_msgstr = false;
1389   pop->file_name = real_filename;
1390   pop->mdlp = NULL;
1391   pop->mlp = NULL;
1392   if (current_domain != NULL)
1393     {
1394       /* Keep current_domain and this->domain synchronized.  */
1395       pop->domain = current_domain->domain_name;
1396       pop->mlp = current_domain->mlp;
1397     }
1398   po_lex_pass_obsolete_entries (true);
1399   catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename,
1400                         filename, input_syntax);
1401   catalog_reader_free ((abstract_catalog_reader_ty *) pop);
1402 
1403   if (fp != stdin)
1404     fclose (fp);
1405 }
1406 
1407 static void
add_languages(string_list_ty * languages,string_list_ty * desired_languages,const char * line,size_t length)1408 add_languages (string_list_ty *languages, string_list_ty *desired_languages,
1409                const char *line, size_t length)
1410 {
1411   char *start;
1412 
1413   /* Split the line by whitespace and build the languages list.  */
1414   for (start = (char *) line; start - line < length; )
1415     {
1416       char *p;
1417 
1418       /* Skip whitespace before the string.  */
1419       while (*start == ' ' || *start == '\t')
1420         start++;
1421 
1422       p = start;
1423       while (*p != '\0' && *p != ' ' && *p != '\t')
1424         p++;
1425 
1426       *p = '\0';
1427       if (desired_languages == NULL
1428           || string_list_member (desired_languages, start))
1429         string_list_append_unique (languages, start);
1430       start = p + 1;
1431     }
1432 }
1433 
1434 /* Compute the languages list by reading the "LINGUAS" envvar or the
1435    LINGUAS file under DIRECTORY.  */
1436 static void
get_languages(string_list_ty * languages,const char * directory)1437 get_languages (string_list_ty *languages, const char *directory)
1438 {
1439   char *envval;
1440   string_list_ty real_desired_languages, *desired_languages = NULL;
1441   char *linguas_file_name = NULL;
1442   struct stat statbuf;
1443   FILE *fp;
1444   size_t line_len = 0;
1445   char *line_buf = NULL;
1446 
1447   envval = getenv ("LINGUAS");
1448   if (envval)
1449     {
1450       string_list_init (&real_desired_languages);
1451       add_languages (&real_desired_languages, NULL, envval, strlen (envval));
1452       desired_languages = &real_desired_languages;
1453     }
1454 
1455   linguas_file_name = xconcatenated_filename (directory, "LINGUAS", NULL);
1456   if (stat (linguas_file_name, &statbuf) < 0)
1457     {
1458       error (EXIT_SUCCESS, 0, _("%s does not exist"), linguas_file_name);
1459       goto out;
1460     }
1461 
1462   fp = fopen (linguas_file_name, "r");
1463   if (fp == NULL)
1464     {
1465       error (EXIT_SUCCESS, 0, _("%s exists but cannot read"),
1466              linguas_file_name);
1467       goto out;
1468     }
1469 
1470   while (!feof (fp))
1471     {
1472       /* Read next line from file.  */
1473       int len = getline (&line_buf, &line_len, fp);
1474 
1475       /* In case of an error leave loop.  */
1476       if (len < 0)
1477         break;
1478 
1479       /* Remove trailing '\n' and trailing whitespace.  */
1480       if (len > 0 && line_buf[len - 1] == '\n')
1481         line_buf[--len] = '\0';
1482       while (len > 0
1483              && (line_buf[len - 1] == ' '
1484                  || line_buf[len - 1] == '\t'
1485                  || line_buf[len - 1] == '\r'))
1486         line_buf[--len] = '\0';
1487 
1488       /* Test if we have to ignore the line.  */
1489       if (!(*line_buf == '\0' || *line_buf == '#'))
1490         /* Include the line among the languages.  */
1491         add_languages (languages, desired_languages, line_buf, len);
1492     }
1493 
1494   free (line_buf);
1495   fclose (fp);
1496 
1497  out:
1498   if (desired_languages != NULL)
1499     string_list_destroy (desired_languages);
1500   free (linguas_file_name);
1501 }
1502 
1503 static void
msgfmt_operand_list_init(msgfmt_operand_list_ty * operands)1504 msgfmt_operand_list_init (msgfmt_operand_list_ty *operands)
1505 {
1506   operands->items = NULL;
1507   operands->nitems = 0;
1508   operands->nitems_max = 0;
1509 }
1510 
1511 static void
msgfmt_operand_list_destroy(msgfmt_operand_list_ty * operands)1512 msgfmt_operand_list_destroy (msgfmt_operand_list_ty *operands)
1513 {
1514   size_t i;
1515 
1516   for (i = 0; i < operands->nitems; i++)
1517     {
1518       free (operands->items[i].language);
1519       message_list_free (operands->items[i].mlp, 0);
1520     }
1521   free (operands->items);
1522 }
1523 
1524 static void
msgfmt_operand_list_append(msgfmt_operand_list_ty * operands,const char * language,message_list_ty * messages)1525 msgfmt_operand_list_append (msgfmt_operand_list_ty *operands,
1526                             const char *language,
1527                             message_list_ty *messages)
1528 {
1529   msgfmt_operand_ty *operand;
1530 
1531   if (operands->nitems == operands->nitems_max)
1532     {
1533       operands->nitems_max = operands->nitems_max * 2 + 1;
1534       operands->items = xrealloc (operands->items,
1535                                   sizeof (msgfmt_operand_ty)
1536                                   * operands->nitems_max);
1537     }
1538 
1539   operand = &operands->items[operands->nitems++];
1540   operand->language = xstrdup (language);
1541   operand->mlp = messages;
1542 }
1543 
1544 static int
msgfmt_operand_list_add_from_directory(msgfmt_operand_list_ty * operands,const char * directory)1545 msgfmt_operand_list_add_from_directory (msgfmt_operand_list_ty *operands,
1546                                         const char *directory)
1547 {
1548   string_list_ty languages;
1549   void *saved_dir_list;
1550   int retval = 0;
1551   size_t i;
1552 
1553   string_list_init (&languages);
1554   get_languages (&languages, directory);
1555 
1556   if (languages.nitems == 0)
1557     return 0;
1558 
1559   /* Reset the directory search list so only .po files under DIRECTORY
1560      will be read.  */
1561   saved_dir_list = dir_list_save_reset ();
1562   dir_list_append (directory);
1563 
1564   /* Read all .po files.  */
1565   for (i = 0; i < languages.nitems; i++)
1566     {
1567       const char *language = languages.item[i];
1568       message_list_ty *mlp;
1569       char *input_file_name;
1570       int nerrors;
1571 
1572       current_domain = new_domain (MESSAGE_DOMAIN_DEFAULT,
1573                                    add_mo_suffix (MESSAGE_DOMAIN_DEFAULT));
1574 
1575       input_file_name = xconcatenated_filename ("", language, ".po");
1576       read_catalog_file_msgfmt (input_file_name, &input_format_po);
1577       free (input_file_name);
1578 
1579       /* The domain directive is not supported in the bulk execution mode.
1580          Thus, domain_list should always contain a single domain.  */
1581       assert (current_domain == domain_list && domain_list->next == NULL);
1582       mlp = current_domain->mlp;
1583       free (current_domain);
1584       current_domain = domain_list = NULL;
1585 
1586       /* Remove obsolete messages.  They were only needed for duplicate
1587          checking.  */
1588       message_list_remove_if_not (mlp, is_nonobsolete);
1589 
1590       /* Perform all kinds of checks: plural expressions, format
1591          strings, ...  */
1592       nerrors =
1593         check_message_list (mlp,
1594                             /* Untranslated and fuzzy messages have already
1595                                been dealt with during parsing, see below in
1596                                msgfmt_frob_new_message.  */
1597                             0, 0,
1598                             1, check_format_strings, check_header,
1599                             check_compatibility,
1600                             check_accelerators, accelerator_char);
1601 
1602       retval += nerrors;
1603       if (nerrors > 0)
1604         {
1605           error (0, 0,
1606                  ngettext ("found %d fatal error", "found %d fatal errors",
1607                            nerrors),
1608                  nerrors);
1609           continue;
1610         }
1611 
1612       /* Convert the messages to Unicode.  */
1613       iconv_message_list (mlp, NULL, po_charset_utf8, NULL);
1614 
1615       msgfmt_operand_list_append (operands, language, mlp);
1616     }
1617 
1618   string_list_destroy (&languages);
1619   dir_list_restore (saved_dir_list);
1620 
1621   return retval;
1622 }
1623 
1624 /* Helper function to support 'bulk' operation mode of --desktop.
1625    This reads all .po files in DIRECTORY and merges them into a
1626    .desktop file FILE_NAME.  Currently it does not support some
1627    options available in 'iterative' mode, such as --statistics.  */
1628 static int
msgfmt_desktop_bulk(const char * directory,const char * template_file_name,hash_table * keywords,const char * file_name)1629 msgfmt_desktop_bulk (const char *directory,
1630                      const char *template_file_name,
1631                      hash_table *keywords,
1632                      const char *file_name)
1633 {
1634   msgfmt_operand_list_ty operands;
1635   int nerrors, status;
1636 
1637   msgfmt_operand_list_init (&operands);
1638 
1639   /* Read all .po files.  */
1640   nerrors = msgfmt_operand_list_add_from_directory (&operands, directory);
1641   if (nerrors > 0)
1642     {
1643       msgfmt_operand_list_destroy (&operands);
1644       return 1;
1645     }
1646 
1647   /* Write the messages into .desktop file.  */
1648   status = msgdomain_write_desktop_bulk (&operands,
1649                                          template_file_name,
1650                                          keywords,
1651                                          file_name);
1652 
1653   msgfmt_operand_list_destroy (&operands);
1654 
1655   return status;
1656 }
1657 
1658 /* Helper function to support 'bulk' operation mode of --xml.
1659    This reads all .po files in DIRECTORY and merges them into an
1660    XML file FILE_NAME.  Currently it does not support some
1661    options available in 'iterative' mode, such as --statistics.  */
1662 static int
msgfmt_xml_bulk(const char * directory,const char * template_file_name,its_rule_list_ty * its_rules,const char * file_name)1663 msgfmt_xml_bulk (const char *directory,
1664                  const char *template_file_name,
1665                  its_rule_list_ty *its_rules,
1666                  const char *file_name)
1667 {
1668   msgfmt_operand_list_ty operands;
1669   int nerrors, status;
1670 
1671   msgfmt_operand_list_init (&operands);
1672 
1673   /* Read all .po files.  */
1674   nerrors = msgfmt_operand_list_add_from_directory (&operands, directory);
1675   if (nerrors > 0)
1676     {
1677       msgfmt_operand_list_destroy (&operands);
1678       return 1;
1679     }
1680 
1681   /* Write the messages into .xml file.  */
1682   status = msgdomain_write_xml_bulk (&operands,
1683                                      template_file_name,
1684                                      its_rules,
1685                                      file_name);
1686 
1687   msgfmt_operand_list_destroy (&operands);
1688 
1689   return status;
1690 }
1691