blob: 94a2cdf7948587d3868ca76e04a8059e34e82792 [file] [log] [blame]
/* Unicode CLDR plural rule parser and converter
Copyright (C) 2015, 2018-2020 Free Software Foundation, Inc.
This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "basename-lgpl.h"
#include "cldr-plural-exp.h"
#include "closeout.h"
#include "c-ctype.h"
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include "gettext.h"
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <locale.h>
#include "progname.h"
#include "propername.h"
#include "relocatable.h"
#include <stdlib.h>
#include <string.h>
#include "xalloc.h"
#define _(s) gettext(s)
static char *
extract_rules (FILE *fp,
const char *real_filename, const char *logical_filename,
const char *locale)
{
xmlDocPtr doc;
xmlNodePtr node, n;
size_t locale_length;
char *buffer = NULL, *p;
size_t bufmax = 0;
size_t buflen = 0;
doc = xmlReadFd (fileno (fp), logical_filename, NULL,
XML_PARSE_NONET
| XML_PARSE_NOWARNING
| XML_PARSE_NOBLANKS);
if (doc == NULL)
error (EXIT_FAILURE, 0, _("Could not parse file %s as XML"), logical_filename);
node = xmlDocGetRootElement (doc);
if (!node || !xmlStrEqual (node->name, BAD_CAST "supplementalData"))
{
error_at_line (0, 0,
logical_filename,
xmlGetLineNo (node),
_("The root element must be <%s>"),
"supplementalData");
goto out;
}
for (n = node->children; n; n = n->next)
{
if (n->type == XML_ELEMENT_NODE
&& xmlStrEqual (n->name, BAD_CAST "plurals"))
break;
}
if (!n)
{
error (0, 0, _("The element <%s> does not contain a <%s> element"),
"supplementalData", "plurals");
goto out;
}
locale_length = strlen (locale);
for (n = n->children; n; n = n->next)
{
xmlChar *locales;
xmlChar *cp;
xmlNodePtr n2;
bool found = false;
if (n->type != XML_ELEMENT_NODE
|| !xmlStrEqual (n->name, BAD_CAST "pluralRules"))
continue;
if (!xmlHasProp (n, BAD_CAST "locales"))
{
error_at_line (0, 0,
logical_filename,
xmlGetLineNo (n),
_("The element <%s> does not have attribute <%s>"),
"pluralRules", "locales");
continue;
}
cp = locales = xmlGetProp (n, BAD_CAST "locales");
while (*cp != '\0')
{
while (c_isspace (*cp))
cp++;
if (xmlStrncmp (cp, BAD_CAST locale, locale_length) == 0
&& (*(cp + locale_length) == '\0'
|| c_isspace (*(cp + locale_length))))
{
found = true;
break;
}
while (*cp && !c_isspace (*cp))
cp++;
}
xmlFree (locales);
if (!found)
continue;
for (n2 = n->children; n2; n2 = n2->next)
{
xmlChar *count;
xmlChar *content;
size_t length;
if (n2->type != XML_ELEMENT_NODE
|| !xmlStrEqual (n2->name, BAD_CAST "pluralRule"))
continue;
if (!xmlHasProp (n2, BAD_CAST "count"))
{
error_at_line (0, 0,
logical_filename,
xmlGetLineNo (n2),
_("The element <%s> does not have attribute <%s>"),
"pluralRule", "count");
break;
}
count = xmlGetProp (n2, BAD_CAST "count");
content = xmlNodeGetContent (n2);
length = xmlStrlen (count) + strlen (": ")
+ xmlStrlen (content) + strlen ("; ");
if (buflen + length + 1 > bufmax)
{
bufmax *= 2;
if (bufmax < buflen + length + 1)
bufmax = buflen + length + 1;
buffer = (char *) xrealloc (buffer, bufmax);
}
sprintf (buffer + buflen, "%s: %s; ", count, content);
xmlFree (count);
xmlFree (content);
buflen += length;
}
}
if (buffer)
{
/* Scrub the last semicolon, if any. */
p = strrchr (buffer, ';');
if (p)
*p = '\0';
}
out:
xmlFreeDoc (doc);
return buffer;
}
/* Display usage information and exit. */
static void
usage (int status)
{
if (status != EXIT_SUCCESS)
fprintf (stderr, _("Try '%s --help' for more information.\n"),
program_name);
else
{
printf (_("\
Usage: %s [OPTION...] [LOCALE RULES]...\n\
"), program_name);
printf ("\n");
/* xgettext: no-wrap */
printf (_("\
Extract or convert Unicode CLDR plural rules.\n\
\n\
If both LOCALE and RULES are specified, it reads CLDR plural rules for\n\
LOCALE from RULES and print them in a form suitable for gettext use.\n\
If no argument is given, it reads CLDR plural rules from the standard input.\n\
"));
printf ("\n");
/* xgettext: no-wrap */
printf (_("\
Mandatory arguments to long options are mandatory for short options too.\n\
Similarly for optional arguments.\n\
"));
printf ("\n");
printf (_("\
-c, --cldr print plural rules in the CLDR format\n"));
printf (_("\
-h, --help display this help and exit\n"));
printf (_("\
-V, --version output version information and exit\n"));
printf ("\n");
/* TRANSLATORS: The first placeholder is the web address of the Savannah
project of this package. The second placeholder is the bug-reporting
email address for this package. Please add _another line_ saying
"Report translation bugs to <...>\n" with the address for translation
bugs (typically your translation team's web or email address). */
printf(_("\
Report bugs in the bug tracker at <%s>\n\
or by email to <%s>.\n"),
"https://savannah.gnu.org/projects/gettext",
"bug-gettext@gnu.org");
}
exit (status);
}
/* Long options. */
static const struct option long_options[] =
{
{ "cldr", no_argument, NULL, 'c' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
int
main (int argc, char **argv)
{
bool opt_cldr_format = false;
bool do_help = false;
bool do_version = false;
int optchar;
/* Set program name for messages. */
set_program_name (argv[0]);
/* Set locale via LC_ALL. */
setlocale (LC_ALL, "");
/* Set the text message domain. */
bindtextdomain (PACKAGE, relocate (LOCALEDIR));
bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
textdomain (PACKAGE);
/* Ensure that write errors on stdout are detected. */
atexit (close_stdout);
while ((optchar = getopt_long (argc, argv, "chV", long_options, NULL)) != EOF)
switch (optchar)
{
case '\0': /* Long option. */
break;
case 'c':
opt_cldr_format = true;
break;
case 'h':
do_help = true;
break;
case 'V':
do_version = true;
break;
default:
usage (EXIT_FAILURE);
/* NOTREACHED */
}
/* Version information requested. */
if (do_version)
{
printf ("%s (GNU %s) %s\n", last_component (program_name),
PACKAGE, VERSION);
/* xgettext: no-wrap */
printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
License GPLv3+: GNU GPL version 3 or later <%s>\n\
This is free software: you are free to change and redistribute it.\n\
There is NO WARRANTY, to the extent permitted by law.\n\
"),
"2015-2020", "https://gnu.org/licenses/gpl.html");
printf (_("Written by %s.\n"), proper_name ("Daiki Ueno"));
exit (EXIT_SUCCESS);
}
/* Help is requested. */
if (do_help)
usage (EXIT_SUCCESS);
if (argc == optind + 2)
{
/* Two arguments: Read CLDR rules from a file. */
const char *locale = argv[optind];
const char *logical_filename = argv[optind + 1];
char *extracted_rules;
FILE *fp;
LIBXML_TEST_VERSION
fp = fopen (logical_filename, "r");
if (fp == NULL)
error (1, 0, _("%s cannot be read"), logical_filename);
extracted_rules = extract_rules (fp, logical_filename, logical_filename,
locale);
fclose (fp);
if (extracted_rules == NULL)
error (1, 0, _("cannot extract rules for %s"), locale);
if (opt_cldr_format)
printf ("%s\n", extracted_rules);
else
{
struct cldr_plural_rule_list_ty *result;
result = cldr_plural_parse (extracted_rules);
if (result == NULL)
error (1, 0, _("cannot parse CLDR rule"));
cldr_plural_rule_list_print (result, stdout);
cldr_plural_rule_list_free (result);
}
free (extracted_rules);
}
else if (argc == optind)
{
/* No argument: Read CLDR rules from standard input. */
char *line = NULL;
size_t line_size = 0;
for (;;)
{
int line_len;
struct cldr_plural_rule_list_ty *result;
line_len = getline (&line, &line_size, stdin);
if (line_len < 0)
break;
if (line_len > 0 && line[line_len - 1] == '\n')
line[--line_len] = '\0';
result = cldr_plural_parse (line);
if (result)
{
cldr_plural_rule_list_print (result, stdout);
cldr_plural_rule_list_free (result);
}
}
free (line);
}
else
{
error (1, 0, _("extra operand %s"), argv[optind]);
}
return 0;
}