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