Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / cldr-plurals.c
1 /* Unicode CLDR plural rule parser and converter
2    Copyright (C) 2015 Free Software Foundation, Inc.
3
4    This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
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 <http://www.gnu.org/licenses/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include "basename.h"
24 #include "cldr-plural-exp.h"
25 #include "c-ctype.h"
26 #include <errno.h>
27 #include <error.h>
28 #include <getopt.h>
29 #include "gettext.h"
30 #include <libxml/tree.h>
31 #include <libxml/parser.h>
32 #include <locale.h>
33 #include "progname.h"
34 #include "propername.h"
35 #include "relocatable.h"
36 #include <stdlib.h>
37 #include <string.h>
38 #include "xalloc.h"
39
40 #define _(s) gettext(s)
41
42
43 static char *
44 extract_rules (FILE *fp,
45                const char *real_filename, const char *logical_filename,
46                const char *locale)
47 {
48   xmlDocPtr doc;
49   xmlNodePtr node, n;
50   size_t locale_length;
51   char *buffer = NULL, *p;
52   size_t bufmax = 0;
53   size_t buflen = 0;
54
55   doc = xmlReadFd (fileno (fp), logical_filename, NULL,
56                    XML_PARSE_NONET
57                    | XML_PARSE_NOWARNING
58                    | XML_PARSE_NOERROR
59                    | XML_PARSE_NOBLANKS);
60   if (doc == NULL)
61     error (EXIT_FAILURE, 0, _("memory exhausted"));
62
63   node = xmlDocGetRootElement (doc);
64   if (!node || !xmlStrEqual (node->name, BAD_CAST "supplementalData"))
65     {
66       error_at_line (0, 0,
67                      logical_filename,
68                      xmlGetLineNo (node),
69                      _("\
70 The root element must be <%s>"),
71                      "supplementalData");
72       goto out;
73     }
74
75   for (n = node->children; n; n = n->next)
76     {
77       if (n->type == XML_ELEMENT_NODE
78           && xmlStrEqual (n->name, BAD_CAST "plurals"))
79         break;
80     }
81   if (!n)
82     {
83       error (0, 0, _("The element <%s> does not contain a <%s> element"),
84              "supplementalData", "plurals");
85       goto out;
86     }
87
88   locale_length = strlen (locale);
89   for (n = n->children; n; n = n->next)
90     {
91       xmlChar *locales;
92       xmlChar *cp;
93       xmlNodePtr n2;
94       bool found = false;
95
96       if (n->type != XML_ELEMENT_NODE
97           || !xmlStrEqual (n->name, BAD_CAST "pluralRules"))
98         continue;
99
100       if (!xmlHasProp (n, BAD_CAST "locales"))
101         {
102           error_at_line (0, 0,
103                          logical_filename,
104                          xmlGetLineNo (n),
105                          _("\
106 The element <%s> does not have attribute <%s>"),
107                          "pluralRules", "locales");
108           continue;
109         }
110
111       cp = locales = xmlGetProp (n, BAD_CAST "locales");
112       while (*cp != '\0')
113         {
114           while (c_isspace (*cp))
115             cp++;
116           if (xmlStrncmp (cp, BAD_CAST locale, locale_length) == 0
117               && (*(cp + locale_length) == '\0'
118                   || c_isspace (*(cp + locale_length))))
119             {
120               found = true;
121               break;
122             }
123           while (*cp && !c_isspace (*cp))
124             cp++;
125         }
126       xmlFree (locales);
127
128       if (!found)
129         continue;
130
131       for (n2 = n->children; n2; n2 = n2->next)
132         {
133           xmlChar *count;
134           xmlChar *content;
135           size_t length;
136
137           if (n2->type != XML_ELEMENT_NODE
138               || !xmlStrEqual (n2->name, BAD_CAST "pluralRule"))
139             continue;
140
141           if (!xmlHasProp (n2, BAD_CAST "count"))
142             {
143               error_at_line (0, 0,
144                              logical_filename,
145                              xmlGetLineNo (n2),
146                              _("\
147 The element <%s> does not have attribute <%s>"),
148                              "pluralRule", "count");
149               break;
150             }
151
152           count = xmlGetProp (n2, BAD_CAST "count");
153           content = xmlNodeGetContent (n2);
154           length = xmlStrlen (count) + strlen (": ")
155             + xmlStrlen (content) + strlen ("; ");
156
157           if (buflen + length + 1 > bufmax)
158             {
159               bufmax *= 2;
160               if (bufmax < buflen + length + 1)
161                 bufmax = buflen + length + 1;
162               buffer = (char *) xrealloc (buffer, bufmax);
163             }
164
165           sprintf (buffer + buflen, "%s: %s; ", count, content);
166           xmlFree (count);
167           xmlFree (content);
168
169           buflen += length;
170         }
171     }
172
173   if (buffer)
174     {
175       /* Scrub the last semicolon, if any.  */
176       p = strrchr (buffer, ';');
177       if (p)
178         *p = '\0';
179     }
180
181  out:
182   xmlFreeDoc (doc);
183   return buffer;
184 }
185
186 /* Display usage information and exit.  */
187 static void
188 usage (int status)
189 {
190   if (status != EXIT_SUCCESS)
191     fprintf (stderr, _("Try '%s --help' for more information.\n"),
192              program_name);
193   else
194     {
195       printf (_("\
196 Usage: %s [OPTION...] [LOCALE RULES]...\n\
197 "), program_name);
198       printf ("\n");
199       /* xgettext: no-wrap */
200       printf (_("\
201 Extract or convert Unicode CLDR plural rules.\n\
202 \n\
203 If both LOCALE and RULES are specified, it reads CLDR plural rules for\n\
204 LOCALE from RULES and print them in a form suitable for gettext use.\n\
205 If no argument is given, it reads CLDR plural rules from the standard input.\n\
206 "));
207       printf ("\n");
208       /* xgettext: no-wrap */
209       printf (_("\
210 Mandatory arguments to long options are mandatory for short options too.\n\
211 Similarly for optional arguments.\n\
212 "));
213       printf ("\n");
214       printf (_("\
215   -c, --cldr                  print plural rules in the CLDR format\n"));
216       printf (_("\
217   -h, --help                  display this help and exit\n"));
218       printf (_("\
219   -V, --version               output version information and exit\n"));
220       printf ("\n");
221       /* TRANSLATORS: The placeholder indicates the bug-reporting address
222          for this package.  Please add _another line_ saying
223          "Report translation bugs to <...>\n" with the address for translation
224          bugs (typically your translation team's web or email address).  */
225       fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
226              stdout);
227     }
228   exit (status);
229 }
230
231 /* Long options.  */
232 static const struct option long_options[] =
233 {
234   { "cldr", no_argument, NULL, 'c' },
235   { "help", no_argument, NULL, 'h' },
236   { "version", no_argument, NULL, 'V' },
237   { NULL, 0, NULL, 0 }
238 };
239
240 int
241 main (int argc, char **argv)
242 {
243   bool opt_cldr_format = false;
244   bool do_help = false;
245   bool do_version = false;
246   int optchar;
247
248   /* Set program name for messages.  */
249   set_program_name (argv[0]);
250
251 #ifdef HAVE_SETLOCALE
252   /* Set locale via LC_ALL.  */
253   setlocale (LC_ALL, "");
254 #endif
255
256   /* Set the text message domain.  */
257   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
258   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
259   textdomain (PACKAGE);
260
261   while ((optchar = getopt_long (argc, argv, "chV", long_options, NULL)) != EOF)
262     switch (optchar)
263       {
264       case '\0':                /* Long option.  */
265         break;
266
267       case 'c':
268         opt_cldr_format = true;
269         break;
270
271       case 'h':
272         do_help = true;
273         break;
274
275       case 'V':
276         do_version = true;
277         break;
278
279       default:
280         usage (EXIT_FAILURE);
281         /* NOTREACHED */
282       }
283
284   /* Version information requested.  */
285   if (do_version)
286     {
287       printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
288       /* xgettext: no-wrap */
289       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
290 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
291 This is free software: you are free to change and redistribute it.\n\
292 There is NO WARRANTY, to the extent permitted by law.\n\
293 "),
294               "2015");
295       printf (_("Written by %s.\n"), proper_name ("Daiki Ueno"));
296       exit (EXIT_SUCCESS);
297     }
298
299   /* Help is requested.  */
300   if (do_help)
301     usage (EXIT_SUCCESS);
302
303   if (argc == optind + 2)
304     {
305       /* Two arguments: Read CLDR rules from a file.  */
306       const char *locale = argv[optind];
307       const char *logical_filename = argv[optind + 1];
308       char *extracted_rules;
309       FILE *fp;
310
311       LIBXML_TEST_VERSION
312
313       fp = fopen (logical_filename, "r");
314       if (fp == NULL)
315         error (1, 0, _("%s cannot be read"), logical_filename);
316
317       extracted_rules = extract_rules (fp, logical_filename, logical_filename,
318                                        locale);
319       fclose (fp);
320       if (extracted_rules == NULL)
321         error (1, 0, _("cannot extract rules for %s"), locale);
322
323       if (opt_cldr_format)
324         printf ("%s\n", extracted_rules);
325       else
326         {
327           struct cldr_plural_rule_list_ty *result;
328
329           result = cldr_plural_parse (extracted_rules);
330           if (result == NULL)
331             error (1, 0, _("cannot parse CLDR rule"));
332
333           cldr_plural_rule_list_print (result, stdout);
334           cldr_plural_rule_list_free (result);
335         }
336       free (extracted_rules);
337     }
338   else if (argc == optind)
339     {
340       /* No argument: Read CLDR rules from standard input.  */
341       char *line = NULL;
342       size_t line_size = 0;
343       for (;;)
344         {
345           int line_len;
346           struct cldr_plural_rule_list_ty *result;
347
348           line_len = getline (&line, &line_size, stdin);
349           if (line_len < 0)
350             break;
351           if (line_len > 0 && line[line_len - 1] == '\n')
352             line[--line_len] = '\0';
353
354           result = cldr_plural_parse (line);
355           if (result)
356             {
357               cldr_plural_rule_list_print (result, stdout);
358               cldr_plural_rule_list_free (result);
359             }
360         }
361
362       free (line);
363     }
364   else
365     {
366       error (1, 0, _("extra operand %s"), argv[optind]);
367     }
368
369   return 0;
370 }