1 /* msgunfmt - converts binary .mo files to Uniforum style .po files
2    Copyright (C) 1995-1998, 2000-2007, 2009-2010, 2012, 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 <getopt.h>
24 #include <limits.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <locale.h>
29 
30 #include <textstyle.h>
31 
32 #include "noreturn.h"
33 #include "closeout.h"
34 #include "error.h"
35 #include "error-progname.h"
36 #include "progname.h"
37 #include "relocatable.h"
38 #include "basename-lgpl.h"
39 #include "message.h"
40 #include "msgunfmt.h"
41 #include "read-mo.h"
42 #include "read-java.h"
43 #include "read-csharp.h"
44 #include "read-resources.h"
45 #include "read-tcl.h"
46 #include "write-catalog.h"
47 #include "write-po.h"
48 #include "write-properties.h"
49 #include "write-stringtable.h"
50 #include "propername.h"
51 #include "gettext.h"
52 
53 #define _(str) gettext (str)
54 
55 
56 /* Be more verbose.  */
57 bool verbose;
58 
59 /* Java mode input file specification.  */
60 static bool java_mode;
61 static const char *java_resource_name;
62 static const char *java_locale_name;
63 
64 /* C# mode input file specification.  */
65 static bool csharp_mode;
66 static const char *csharp_resource_name;
67 static const char *csharp_locale_name;
68 static const char *csharp_base_directory;
69 
70 /* C# resources mode input file specification.  */
71 static bool csharp_resources_mode;
72 
73 /* Tcl mode input file specification.  */
74 static bool tcl_mode;
75 static const char *tcl_locale_name;
76 static const char *tcl_base_directory;
77 
78 /* Force output of PO file even if empty.  */
79 static int force_po;
80 
81 /* Long options.  */
82 static const struct option long_options[] =
83 {
84   { "color", optional_argument, NULL, CHAR_MAX + 6 },
85   { "csharp", no_argument, NULL, CHAR_MAX + 4 },
86   { "csharp-resources", no_argument, NULL, CHAR_MAX + 5 },
87   { "escape", no_argument, NULL, 'E' },
88   { "force-po", no_argument, &force_po, 1 },
89   { "help", no_argument, NULL, 'h' },
90   { "indent", no_argument, NULL, 'i' },
91   { "java", no_argument, NULL, 'j' },
92   { "locale", required_argument, NULL, 'l' },
93   { "no-escape", no_argument, NULL, 'e' },
94   { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
95   { "output-file", required_argument, NULL, 'o' },
96   { "properties-output", no_argument, NULL, 'p' },
97   { "resource", required_argument, NULL, 'r' },
98   { "sort-output", no_argument, NULL, 's' },
99   { "strict", no_argument, NULL, 'S' },
100   { "stringtable-output", no_argument, NULL, CHAR_MAX + 3 },
101   { "style", required_argument, NULL, CHAR_MAX + 7 },
102   { "tcl", no_argument, NULL, CHAR_MAX + 1 },
103   { "verbose", no_argument, NULL, 'v' },
104   { "version", no_argument, NULL, 'V' },
105   { "width", required_argument, NULL, 'w' },
106   { NULL, 0, NULL, 0 }
107 };
108 
109 
110 /* Forward declaration of local functions.  */
111 _GL_NORETURN_FUNC static void usage (int status);
112 static void read_one_file (message_list_ty *mlp, const char *filename);
113 
114 
115 int
main(int argc,char ** argv)116 main (int argc, char **argv)
117 {
118   int optchar;
119   bool do_help = false;
120   bool do_version = false;
121   const char *output_file = "-";
122   msgdomain_list_ty *result;
123   catalog_output_format_ty output_syntax = &output_format_po;
124   bool sort_by_msgid = false;
125 
126   /* Set program name for messages.  */
127   set_program_name (argv[0]);
128   error_print_progname = maybe_print_progname;
129 
130   /* Set locale via LC_ALL.  */
131   setlocale (LC_ALL, "");
132 
133   /* Set the text message domain.  */
134   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
135   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
136   textdomain (PACKAGE);
137 
138   /* Ensure that write errors on stdout are detected.  */
139   atexit (close_stdout);
140 
141   while ((optchar = getopt_long (argc, argv, "d:eEhijl:o:pr:svVw:",
142                                  long_options, NULL))
143          != EOF)
144     switch (optchar)
145       {
146       case '\0':
147         /* long option */
148         break;
149 
150       case 'd':
151         csharp_base_directory = optarg;
152         tcl_base_directory = optarg;
153         break;
154 
155       case 'e':
156         message_print_style_escape (false);
157         break;
158 
159       case 'E':
160         message_print_style_escape (true);
161         break;
162 
163       case 'h':
164         do_help = true;
165         break;
166 
167       case 'i':
168         message_print_style_indent ();
169         break;
170 
171       case 'j':
172         java_mode = true;
173         break;
174 
175       case 'l':
176         java_locale_name = optarg;
177         csharp_locale_name = optarg;
178         tcl_locale_name = optarg;
179         break;
180 
181       case 'o':
182         output_file = optarg;
183         break;
184 
185       case 'p':
186         output_syntax = &output_format_properties;
187         break;
188 
189       case 'r':
190         java_resource_name = optarg;
191         csharp_resource_name = optarg;
192         break;
193 
194       case 's':
195         sort_by_msgid = true;
196         break;
197 
198       case 'S':
199         message_print_style_uniforum ();
200         break;
201 
202       case 'v':
203         verbose = true;
204         break;
205 
206       case 'V':
207         do_version = true;
208         break;
209 
210       case 'w':
211         {
212           int value;
213           char *endp;
214           value = strtol (optarg, &endp, 10);
215           if (endp != optarg)
216             message_page_width_set (value);
217         }
218         break;
219 
220       case CHAR_MAX + 1: /* --tcl */
221         tcl_mode = true;
222         break;
223 
224       case CHAR_MAX + 2: /* --no-wrap */
225         message_page_width_ignore ();
226         break;
227 
228       case CHAR_MAX + 3: /* --stringtable-output */
229         output_syntax = &output_format_stringtable;
230         break;
231 
232       case CHAR_MAX + 4: /* --csharp */
233         csharp_mode = true;
234         break;
235 
236       case CHAR_MAX + 5: /* --csharp-resources */
237         csharp_resources_mode = true;
238         break;
239 
240       case CHAR_MAX + 6: /* --color */
241         if (handle_color_option (optarg) || color_test_mode)
242           usage (EXIT_FAILURE);
243         break;
244 
245       case CHAR_MAX + 7: /* --style */
246         handle_style_option (optarg);
247         break;
248 
249       default:
250         usage (EXIT_FAILURE);
251         break;
252       }
253 
254   /* Version information is requested.  */
255   if (do_version)
256     {
257       printf ("%s (GNU %s) %s\n", last_component (program_name),
258               PACKAGE, VERSION);
259       /* xgettext: no-wrap */
260       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
261 License GPLv3+: GNU GPL version 3 or later <%s>\n\
262 This is free software: you are free to change and redistribute it.\n\
263 There is NO WARRANTY, to the extent permitted by law.\n\
264 "),
265               "1995-2020", "https://gnu.org/licenses/gpl.html");
266       printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper"));
267       exit (EXIT_SUCCESS);
268     }
269 
270   /* Help is requested.  */
271   if (do_help)
272     usage (EXIT_SUCCESS);
273 
274   /* Check for contradicting options.  */
275   {
276     unsigned int modes =
277       (java_mode ? 1 : 0)
278       | (csharp_mode ? 2 : 0)
279       | (csharp_resources_mode ? 4 : 0)
280       | (tcl_mode ? 8 : 0);
281     static const char *mode_options[] =
282       { "--java", "--csharp", "--csharp-resources", "--tcl" };
283     /* More than one bit set?  */
284     if (modes & (modes - 1))
285       {
286         const char *first_option;
287         const char *second_option;
288         unsigned int i;
289         for (i = 0; ; i++)
290           if (modes & (1 << i))
291             break;
292         first_option = mode_options[i];
293         for (i = i + 1; ; i++)
294           if (modes & (1 << i))
295             break;
296         second_option = mode_options[i];
297         error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
298                first_option, second_option);
299       }
300   }
301   if (java_mode)
302     {
303       if (optind < argc)
304         {
305           error (EXIT_FAILURE, 0,
306                  _("%s and explicit file names are mutually exclusive"),
307                  "--java");
308         }
309     }
310   else if (csharp_mode)
311     {
312       if (optind < argc)
313         {
314           error (EXIT_FAILURE, 0,
315                  _("%s and explicit file names are mutually exclusive"),
316                  "--csharp");
317         }
318       if (csharp_locale_name == NULL)
319         {
320           error (EXIT_SUCCESS, 0,
321                  _("%s requires a \"-l locale\" specification"),
322                  "--csharp");
323           usage (EXIT_FAILURE);
324         }
325       if (csharp_base_directory == NULL)
326         {
327           error (EXIT_SUCCESS, 0,
328                  _("%s requires a \"-d directory\" specification"),
329                  "--csharp");
330           usage (EXIT_FAILURE);
331         }
332     }
333   else if (tcl_mode)
334     {
335       if (optind < argc)
336         {
337           error (EXIT_FAILURE, 0,
338                  _("%s and explicit file names are mutually exclusive"),
339                  "--tcl");
340         }
341       if (tcl_locale_name == NULL)
342         {
343           error (EXIT_SUCCESS, 0,
344                  _("%s requires a \"-l locale\" specification"),
345                  "--tcl");
346           usage (EXIT_FAILURE);
347         }
348       if (tcl_base_directory == NULL)
349         {
350           error (EXIT_SUCCESS, 0,
351                  _("%s requires a \"-d directory\" specification"),
352                  "--tcl");
353           usage (EXIT_FAILURE);
354         }
355     }
356   else
357     {
358       if (java_resource_name != NULL)
359         {
360           error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"),
361                  "--resource", "--java", "--csharp");
362           usage (EXIT_FAILURE);
363         }
364       if (java_locale_name != NULL)
365         {
366           error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"),
367                  "--locale", "--java", "--csharp");
368           usage (EXIT_FAILURE);
369         }
370     }
371 
372   /* Read the given .mo file. */
373   if (java_mode)
374     {
375       result = msgdomain_read_java (java_resource_name, java_locale_name);
376     }
377   else if (csharp_mode)
378     {
379       result = msgdomain_read_csharp (csharp_resource_name, csharp_locale_name,
380                                       csharp_base_directory);
381     }
382   else if (tcl_mode)
383     {
384       result = msgdomain_read_tcl (tcl_locale_name, tcl_base_directory);
385     }
386   else
387     {
388       message_list_ty *mlp;
389 
390       mlp = message_list_alloc (false);
391       if (optind < argc)
392         {
393           do
394             read_one_file (mlp, argv[optind]);
395           while (++optind < argc);
396         }
397       else
398         read_one_file (mlp, "-");
399 
400       result = msgdomain_list_alloc (false);
401       result->item[0]->messages = mlp;
402     }
403 
404   /* Sorting the list of messages.  */
405   if (sort_by_msgid)
406     msgdomain_list_sort_by_msgid (result);
407 
408   /* Write the resulting message list to the given .po file.  */
409   msgdomain_list_print (result, output_file, output_syntax, force_po, false);
410 
411   /* No problems.  */
412   exit (EXIT_SUCCESS);
413 }
414 
415 
416 /* Display usage information and exit.  */
417 static void
usage(int status)418 usage (int status)
419 {
420   if (status != EXIT_SUCCESS)
421     fprintf (stderr, _("Try '%s --help' for more information.\n"),
422              program_name);
423   else
424     {
425       printf (_("\
426 Usage: %s [OPTION] [FILE]...\n\
427 "), program_name);
428       printf ("\n");
429       printf (_("\
430 Convert binary message catalog to Uniforum style .po file.\n\
431 "));
432       printf ("\n");
433       printf (_("\
434 Mandatory arguments to long options are mandatory for short options too.\n"));
435       printf ("\n");
436       printf (_("\
437 Operation mode:\n"));
438       printf (_("\
439   -j, --java                  Java mode: input is a Java ResourceBundle class\n"));
440       printf (_("\
441       --csharp                C# mode: input is a .NET .dll file\n"));
442       printf (_("\
443       --csharp-resources      C# resources mode: input is a .NET .resources file\n"));
444       printf (_("\
445       --tcl                   Tcl mode: input is a tcl/msgcat .msg file\n"));
446       printf ("\n");
447       printf (_("\
448 Input file location:\n"));
449       printf (_("\
450   FILE ...                    input .mo files\n"));
451       printf (_("\
452 If no input file is given or if it is -, standard input is read.\n"));
453       printf ("\n");
454       printf (_("\
455 Input file location in Java mode:\n"));
456       printf (_("\
457   -r, --resource=RESOURCE     resource name\n"));
458       printf (_("\
459   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
460       printf (_("\
461 The class name is determined by appending the locale name to the resource name,\n\
462 separated with an underscore.  The class is located using the CLASSPATH.\n\
463 "));
464       printf ("\n");
465       printf (_("\
466 Input file location in C# mode:\n"));
467       printf (_("\
468   -r, --resource=RESOURCE     resource name\n"));
469       printf (_("\
470   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
471       printf (_("\
472   -d DIRECTORY                base directory for locale dependent .dll files\n"));
473       printf (_("\
474 The -l and -d options are mandatory.  The .dll file is located in a\n\
475 subdirectory of the specified directory whose name depends on the locale.\n"));
476       printf ("\n");
477       printf (_("\
478 Input file location in Tcl mode:\n"));
479       printf (_("\
480   -l, --locale=LOCALE         locale name, either language or language_COUNTRY\n"));
481       printf (_("\
482   -d DIRECTORY                base directory of .msg message catalogs\n"));
483       printf (_("\
484 The -l and -d options are mandatory.  The .msg file is located in the\n\
485 specified directory.\n"));
486       printf ("\n");
487       printf (_("\
488 Output file location:\n"));
489       printf (_("\
490   -o, --output-file=FILE      write output to specified file\n"));
491       printf (_("\
492 The results are written to standard output if no output file is specified\n\
493 or if it is -.\n"));
494       printf ("\n");
495       printf (_("\
496 Output details:\n"));
497       printf (_("\
498       --color                 use colors and other text attributes always\n\
499       --color=WHEN            use colors and other text attributes if WHEN.\n\
500                               WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
501       printf (_("\
502       --style=STYLEFILE       specify CSS style rule file for --color\n"));
503       printf (_("\
504   -e, --no-escape             do not use C escapes in output (default)\n"));
505       printf (_("\
506   -E, --escape                use C escapes in output, no extended chars\n"));
507       printf (_("\
508       --force-po              write PO file even if empty\n"));
509       printf (_("\
510   -i, --indent                write indented output style\n"));
511       printf (_("\
512       --strict                write strict uniforum style\n"));
513       printf (_("\
514   -p, --properties-output     write out a Java .properties file\n"));
515       printf (_("\
516       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
517       printf (_("\
518   -w, --width=NUMBER          set output page width\n"));
519       printf (_("\
520       --no-wrap               do not break long message lines, longer than\n\
521                               the output page width, into several lines\n"));
522       printf (_("\
523   -s, --sort-output           generate sorted output\n"));
524       printf ("\n");
525       printf (_("\
526 Informative output:\n"));
527       printf (_("\
528   -h, --help                  display this help and exit\n"));
529       printf (_("\
530   -V, --version               output version information and exit\n"));
531       printf (_("\
532   -v, --verbose               increase verbosity level\n"));
533       printf ("\n");
534       /* TRANSLATORS: The first placeholder is the web address of the Savannah
535          project of this package.  The second placeholder is the bug-reporting
536          email address for this package.  Please add _another line_ saying
537          "Report translation bugs to <...>\n" with the address for translation
538          bugs (typically your translation team's web or email address).  */
539       printf(_("\
540 Report bugs in the bug tracker at <%s>\n\
541 or by email to <%s>.\n"),
542              "https://savannah.gnu.org/projects/gettext",
543              "bug-gettext@gnu.org");
544     }
545 
546   exit (status);
547 }
548 
549 
550 static void
read_one_file(message_list_ty * mlp,const char * filename)551 read_one_file (message_list_ty *mlp, const char *filename)
552 {
553   if (csharp_resources_mode)
554     read_resources_file (mlp, filename);
555   else
556     read_mo_file (mlp, filename);
557 }
558