From b3c2a5a242c36fbbaa0c5b17f975d6c638598a23 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Mon, 5 Oct 2015 17:18:41 +0900 Subject: msgfmt: Support XML file merging * gettext-tools/src/Makefile.am (noinst_HEADERS): Add write-xml.h. (msgfmt_SOURCES): Add write-xml.c. * gettext-tools/src/its.c (its_merge_context_merge_node): New function. (its_merge_context_merge): New function. (its_merge_context_alloc): New function. (its_merge_context_write): New function. (its_merge_context_free): New function. * gettext-tools/src/its.h (its_merge_context_ty): New type. * gettext-tools/src/msgfmt.c: Include "its.h", "locating-rule.h", and "write-xml.h". (SIZEOF): New macro. (xml_mode, xml_locale_name, xml_template_name, xml_base_directory, xml_language, xml_its_rules): New variable. (long_options): Add --language and --xml. (main): Handle new options. (usage): Document new options. (msgfmt_xml_bulk): New function. * gettext-tools/src/write-xml.c: New file. * gettext-tools/src/write-xml.h: New file. * gettext-tools/doc/gettext.texi: Mention XML file merging use-case. * gettext-tools/doc/msgfmt.texi: Mention --xml option. * gettext-tools/tests/msgfmt-xml-1: New file. * gettext-tools/tests/msgfmt-xml-2: New file. * gettext-tools/tests/Makefile.am (TESTS): Add new tests. --- gettext-tools/doc/gettext.texi | 29 +++++- gettext-tools/doc/msgfmt.texi | 61 ++++++++++- gettext-tools/src/Makefile.am | 4 +- gettext-tools/src/its.c | 131 ++++++++++++++++++++++++ gettext-tools/src/its.h | 12 +++ gettext-tools/src/msgfmt.c | 212 +++++++++++++++++++++++++++++++++++++-- gettext-tools/src/write-xml.c | 107 ++++++++++++++++++++ gettext-tools/src/write-xml.h | 52 ++++++++++ gettext-tools/tests/Makefile.am | 1 + gettext-tools/tests/msgfmt-xml-1 | 119 ++++++++++++++++++++++ gettext-tools/tests/msgfmt-xml-2 | 203 +++++++++++++++++++++++++++++++++++++ 11 files changed, 916 insertions(+), 15 deletions(-) create mode 100644 gettext-tools/src/write-xml.c create mode 100644 gettext-tools/src/write-xml.h create mode 100755 gettext-tools/tests/msgfmt-xml-1 create mode 100755 gettext-tools/tests/msgfmt-xml-2 diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index 7e81a9c..8391073 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -12286,7 +12286,18 @@ A required @code{escape} attribute with the value @code{yes} or @code{no}. This data category extends the standard @samp{Preserve Space} data category with the additional value @samp{trim}. The value means to remove the leading and trailing whitespaces of the content, but not to -normalize whitespaces in the middle. +normalize whitespaces in the middle. In the global rule, the +@code{preserveSpaceRule} element contains the following: + +@itemize +@item +A required @code{selector} attribute. It contains an absolute selector +that selects the nodes to which this rule applies. + +@item +A required @code{space} attribute with the value @code{default}, +@code{preserve}, or @code{trim}. +@end itemize @end table @@ -12363,6 +12374,22 @@ rule files and locating rule files must be installed in the properly installed, @code{xgettext} can extract translatable strings from the matching XML files. +@subsubsection Two Use-cases of Translated Strings in XML + +For XML, there are two use-cases of translated strings. One is the case +where the translated strings are directly consumed by programs, and the +other is the case where the translated strings are merged back to the +original XML document. In the former case, special characters in the +extracted strings shouldn't be escaped, while they should in the latter +case. To control wheter to escape special characters, the @samp{Escape +Special Characters} data category can be used. + +To merge the translations, the @samp{msgfmt} program can be used with +the option @code{--xml}. @xref{msgfmt Invocation}, for more details +about how one calls the @samp{msgfmt} program. @samp{msgfmt}'s +@code{--xml} option doesn't perform character escaping, so translated +strings can have arbitrary XML constructs, such as elements for markup. + @c This is the template for new data formats. @ignore diff --git a/gettext-tools/doc/msgfmt.texi b/gettext-tools/doc/msgfmt.texi index a808b7c..a6fefcd 100644 --- a/gettext-tools/doc/msgfmt.texi +++ b/gettext-tools/doc/msgfmt.texi @@ -65,6 +65,11 @@ Qt mode: generate a Qt @file{.qm} file. @cindex Desktop Entry mode, and @code{msgfmt} program Desktop Entry mode: generate a @file{.desktop} file. +@item --xml +@opindex --xml@r{, @code{msgfmt} option} +@cindex XML mode, and @code{msgfmt} program +XML mode: generate an XML file. + @end table @subsection Output file location @@ -203,11 +208,8 @@ msgfmt --desktop --template=@var{template} --locale=@var{locale} \ -o @var{file} @var{filename}.po @dots{} @end example -On the other hand, when using msgfmt from a Makefile, it is cumbersome -to loop over all locales under a particular directory. msgfmt -provides a special operation mode for this use-case. To generate a -@samp{.desktop} file from multiple @samp{.po} files under a directory, -specify the directory with the @samp{-d} option. +msgfmt provides a special "bulk" operation mode to process multiple +@file{.po} files at a time. @example msgfmt --desktop --template=@var{template} -d @var{directory} -o @var{file} @@ -221,6 +223,55 @@ variable. For either operation modes, the @samp{-o} and @samp{--template} options are mandatory. +@subsection XML mode operations + +@table @samp +@item --template=@var{template} +@opindex --template@r{, @code{msgfmt} option} +Specify an XML file used as a template. + +@item -L @var{name} +@itemx --language=@var{name} +@opindex -L@r{, @code{msgfmt} option} +@opindex --language@r{, @code{msgfmt} option} +@cindex supported languages, @code{msgfmt} +Specifies the language of the input files. + +@item -l @var{locale} +@itemx --locale=@var{locale} +@opindex -l@r{, @code{msgfmt} option} +@opindex --locale@r{, @code{msgfmt} option} +Specify the locale name, either a language specification of the form @var{ll} +or a combined language and country specification of the form @var{ll_CC}. + +@item -d @var{directory} +@opindex -d@r{, @code{msgfmt} option} +Specify the base directory of @file{.po} message catalogs. + +@end table + +To generate an XML file for a single locale, you can use it as follows. + +@example +msgfmt --xml --template=@var{template} --locale=@var{locale} \ + -o @var{file} @var{filename}.po @dots{} +@end example + +msgfmt provides a special "bulk" operation mode to process multiple +@file{.po} files at a time. + +@example +msgfmt --xml --template=@var{template} -d @var{directory} -o @var{file} +@end example + +msgfmt first reads the @samp{LINGUAS} file under @var{directory}, and +then processes all @samp{.po} files listed there. You can also limit +the locales to a subset, through the @samp{LINGUAS} environment +variable. + +For either operation modes, the @samp{-o} and @samp{--template} +options are mandatory. + @subsection Input file syntax @table @samp diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am index 27cc7af..f3dab4c 100644 --- a/gettext-tools/src/Makefile.am +++ b/gettext-tools/src/Makefile.am @@ -51,6 +51,7 @@ read-resources.h write-resources.h \ read-tcl.h write-tcl.h \ write-qt.h \ read-desktop.h write-desktop.h \ +write-xml.h \ po-time.h plural-table.h lang-table.h format.h filters.h \ xgettext.h x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \ x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \ @@ -164,7 +165,8 @@ msgcmp_SOURCES += msgl-fsearch.c msgfmt_SOURCES = msgfmt.c msgfmt_SOURCES += \ write-mo.c write-java.c write-csharp.c write-resources.c write-tcl.c \ - write-qt.c write-desktop.c ../../gettext-runtime/intl/hash-string.c + write-qt.c write-desktop.c write-xml.c \ + ../../gettext-runtime/intl/hash-string.c if !WOE32DLL msgmerge_SOURCES = msgmerge.c else diff --git a/gettext-tools/src/its.c b/gettext-tools/src/its.c index 585e984..6843aac 100644 --- a/gettext-tools/src/its.c +++ b/gettext-tools/src/its.c @@ -1810,3 +1810,134 @@ its_rule_list_extract (its_rule_list_ty *rules, free (nodes.items); xmlFreeDoc (doc); } + +struct its_merge_context_ty +{ + its_rule_list_ty *rules; + xmlDoc *doc; + struct its_node_list_ty nodes; +}; + +static void +its_merge_context_merge_node (struct its_merge_context_ty *context, + xmlNode *node, + const char *language, + message_list_ty *mlp) +{ + if (node->type == XML_ELEMENT_NODE) + { + struct its_value_list_ty *values; + const char *value; + char *msgid = NULL, *msgctxt = NULL; + enum its_whitespace_type_ty whitespace; + bool no_escape; + + values = its_rule_list_eval (context->rules, node); + + value = its_value_list_get_value (values, "space"); + if (value && strcmp (value, "preserve") == 0) + whitespace = ITS_WHITESPACE_PRESERVE; + else if (value && strcmp (value, "trim") == 0) + whitespace = ITS_WHITESPACE_TRIM; + else + whitespace = ITS_WHITESPACE_NORMALIZE; + + value = its_value_list_get_value (values, "escape"); + no_escape = value != NULL && strcmp (value, "no") == 0; + + value = its_value_list_get_value (values, "contextPointer"); + if (value) + msgctxt = _its_get_content (context->rules, node, value, + ITS_WHITESPACE_PRESERVE, no_escape); + + value = its_value_list_get_value (values, "textPointer"); + if (value) + msgid = _its_get_content (context->rules, node, value, + ITS_WHITESPACE_PRESERVE, no_escape); + its_value_list_destroy (values); + free (values); + + if (msgid == NULL) + msgid = _its_collect_text_content (node, whitespace, no_escape); + if (*msgid != '\0') + { + message_ty *mp; + + mp = message_list_search (mlp, msgctxt, msgid); + if (mp && *mp->msgstr != '\0') + { + xmlNode *translated; + + translated = xmlNewNode (node->ns, node->name); + xmlSetProp (translated, BAD_CAST "xml:lang", BAD_CAST language); + + xmlNodeAddContent (translated, BAD_CAST mp->msgstr); + xmlAddNextSibling (node, translated); + } + } + free (msgctxt); + free (msgid); + } +} + +void +its_merge_context_merge (its_merge_context_ty *context, + const char *language, + message_list_ty *mlp) +{ + size_t i; + + for (i = 0; i < context->nodes.nitems; i++) + its_merge_context_merge_node (context, context->nodes.items[i], + language, + mlp); +} + +struct its_merge_context_ty * +its_merge_context_alloc (its_rule_list_ty *rules, + const char *filename) +{ + xmlDoc *doc; + struct its_merge_context_ty *result; + + doc = xmlReadFile (filename, NULL, + XML_PARSE_NONET + | XML_PARSE_NOWARNING + | XML_PARSE_NOBLANKS + | XML_PARSE_NOERROR); + if (doc == NULL) + { + xmlError *err = xmlGetLastError (); + error (0, 0, _("cannot read %s: %s"), filename, err->message); + return NULL; + } + + its_rule_list_apply (rules, doc); + + result = XMALLOC (struct its_merge_context_ty); + result->rules = rules; + result->doc = doc; + + /* Collect translatable nodes. */ + memset (&result->nodes, 0, sizeof (struct its_node_list_ty)); + its_rule_list_extract_nodes (result->rules, + &result->nodes, + xmlDocGetRootElement (result->doc)); + + return result; +} + +void +its_merge_context_write (struct its_merge_context_ty *context, + FILE *fp) +{ + xmlDocFormatDump (fp, context->doc, 1); +} + +void +its_merge_context_free (struct its_merge_context_ty *context) +{ + xmlFreeDoc (context->doc); + free (context->nodes.items); + free (context); +} diff --git a/gettext-tools/src/its.h b/gettext-tools/src/its.h index d26bbcc..8d597f5 100644 --- a/gettext-tools/src/its.h +++ b/gettext-tools/src/its.h @@ -66,6 +66,18 @@ extern void its_rule_list_extract (its_rule_list_ty *rules, msgdomain_list_ty *mdlp, its_extract_callback_ty callback); +typedef struct its_merge_context_ty its_merge_context_ty; + +extern its_merge_context_ty * + its_merge_context_alloc (its_rule_list_ty *rules, const char *filename); +extern void its_merge_context_free (its_merge_context_ty *context); +extern void its_merge_context_merge (its_merge_context_ty *context, + const char *language, + message_list_ty *mlp); + +extern void its_merge_context_write (its_merge_context_ty *context, + FILE *fp); + #ifdef __cplusplus } #endif diff --git a/gettext-tools/src/msgfmt.c b/gettext-tools/src/msgfmt.c index 3dfafdc..0517c99 100644 --- a/gettext-tools/src/msgfmt.c +++ b/gettext-tools/src/msgfmt.c @@ -50,6 +50,7 @@ #include "write-tcl.h" #include "write-qt.h" #include "write-desktop.h" +#include "write-xml.h" #include "propername.h" #include "message.h" #include "open-catalog.h" @@ -62,10 +63,14 @@ #include "msgl-check.h" #include "msgl-iconv.h" #include "concat-filename.h" +#include "its.h" +#include "locating-rule.h" #include "gettext.h" #define _(str) gettext (str) +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + /* Contains exit status for case in which no premature exit occurs. */ static int exit_status; @@ -111,6 +116,14 @@ static const char *desktop_base_directory; static hash_table desktop_keywords; static bool desktop_default_keywords = true; +/* XML mode output file specification. */ +static bool xml_mode; +static const char *xml_locale_name; +static const char *xml_template_name; +static const char *xml_base_directory; +static const char *xml_language; +static its_rule_list_ty *xml_its_rules; + /* We may have more than one input file. Domains with same names in different files have to merged. So we need a list of tables for each output file. */ @@ -181,6 +194,7 @@ static const struct option long_options[] = { "java", no_argument, NULL, 'j' }, { "java2", no_argument, NULL, CHAR_MAX + 5 }, { "keyword", required_argument, NULL, 'k' }, + { "language", required_argument, NULL, 'L' }, { "locale", required_argument, NULL, 'l' }, { "no-hash", no_argument, NULL, CHAR_MAX + 6 }, { "output-file", required_argument, NULL, 'o' }, @@ -197,6 +211,7 @@ static const struct option long_options[] = { "use-untranslated", no_argument, NULL, CHAR_MAX + 12 }, { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, + { "xml", no_argument, NULL, 'x' }, { NULL, 0, NULL, 0 } }; @@ -216,6 +231,10 @@ static int msgfmt_desktop_bulk (const char *directory, const char *template_file_name, hash_table *keywords, const char *file_name); +static int msgfmt_xml_bulk (const char *directory, + const char *template_file_name, + its_rule_list_ty *its_rules, + const char *file_name); int @@ -252,8 +271,8 @@ main (int argc, char *argv[]) /* Ensure that write errors on stdout are detected. */ atexit (close_stdout); - while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:o:Pr:vV", long_options, - NULL)) + while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:L:o:Pr:vVx", + long_options, NULL)) != EOF) switch (opt) { @@ -281,6 +300,7 @@ main (int argc, char *argv[]) csharp_base_directory = optarg; tcl_base_directory = optarg; desktop_base_directory = optarg; + xml_base_directory = optarg; break; case 'D': dir_list_append (optarg); @@ -313,6 +333,10 @@ main (int argc, char *argv[]) csharp_locale_name = optarg; tcl_locale_name = optarg; desktop_locale_name = optarg; + xml_locale_name = optarg; + break; + case 'L': + xml_language = optarg; break; case 'o': output_file_name = optarg; @@ -333,6 +357,9 @@ main (int argc, char *argv[]) case 'V': do_version = true; break; + case 'x': + xml_mode = true; + break; case CHAR_MAX + 1: /* --check-accelerators */ check_accelerators = true; if (optarg != NULL) @@ -402,6 +429,7 @@ main (int argc, char *argv[]) break; case CHAR_MAX + 16: /* --template=TEMPLATE */ desktop_template_name = optarg; + xml_template_name = optarg; break; default: usage (EXIT_FAILURE); @@ -428,16 +456,20 @@ There is NO WARRANTY, to the extent permitted by law.\n\ usage (EXIT_SUCCESS); /* Test whether we have a .po file name as argument. */ - if (optind >= argc && !(desktop_mode && desktop_base_directory)) + if (optind >= argc + && !(desktop_mode && desktop_base_directory) + && !(xml_mode && xml_base_directory)) { error (EXIT_SUCCESS, 0, _("no input file given")); usage (EXIT_FAILURE); } - if (optind < argc && desktop_mode && desktop_base_directory) + if (optind < argc + && ((desktop_mode && desktop_base_directory) + || (xml_mode && xml_base_directory))) { error (EXIT_SUCCESS, 0, _("no input file should be given if %s and %s are specified"), - "--desktop", "-d"); + desktop_mode ? "--desktop" : "--xml", "-d"); usage (EXIT_FAILURE); } @@ -449,10 +481,11 @@ There is NO WARRANTY, to the extent permitted by law.\n\ | (csharp_resources_mode ? 4 : 0) | (tcl_mode ? 8 : 0) | (qt_mode ? 16 : 0) - | (desktop_mode ? 32 : 0); + | (desktop_mode ? 32 : 0) + | (xml_mode ? 64 : 0); static const char *mode_options[] = { "--java", "--csharp", "--csharp-resources", "--tcl", "--qt", - "--desktop" }; + "--desktop", "--xml" }; /* More than one bit set? */ if (modes & (modes - 1)) { @@ -558,6 +591,34 @@ There is NO WARRANTY, to the extent permitted by law.\n\ usage (EXIT_FAILURE); } } + else if (xml_mode) + { + if (xml_template_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"--template template\" specification"), + "--xml"); + usage (EXIT_FAILURE); + } + if (output_file_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-o file\" specification"), + "--xml"); + usage (EXIT_FAILURE); + } + if (xml_base_directory != NULL && xml_locale_name != NULL) + error (EXIT_FAILURE, 0, + _("%s and %s are mutually exclusive in %s"), + "-d", "-l", "--xml"); + if (xml_base_directory == NULL && xml_locale_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-l locale\" specification"), + "--xml"); + usage (EXIT_FAILURE); + } + } else { if (java_resource_name != NULL) @@ -600,6 +661,80 @@ There is NO WARRANTY, to the extent permitted by law.\n\ exit (exit_status); } + if (xml_mode) + { + const char *gettextdatadir; + char *versioned_gettextdatadir; + char *its_dirs[2] = { NULL, NULL }; + locating_rule_list_ty *its_locating_rules; + const char *its_basename; + size_t i; + + /* Make it possible to override the locator file location. This + is necessary for running the testsuite before "make + install". */ + gettextdatadir = getenv ("GETTEXTDATADIR"); + if (gettextdatadir == NULL || gettextdatadir[0] == '\0') + gettextdatadir = relocate (GETTEXTDATADIR); + + its_dirs[0] = xconcatenated_filename (gettextdatadir, "its", NULL); + + versioned_gettextdatadir = + xasprintf ("%s%s", relocate (GETTEXTDATADIR), PACKAGE_SUFFIX); + its_dirs[1] = xconcatenated_filename (versioned_gettextdatadir, "its", + NULL); + free (versioned_gettextdatadir); + + its_locating_rules = locating_rule_list_alloc (); + for (i = 0; i < SIZEOF (its_dirs); i++) + locating_rule_list_add_from_directory (its_locating_rules, its_dirs[i]); + + its_basename = locating_rule_list_locate (its_locating_rules, + xml_template_name, + xml_language); + + if (its_basename != NULL) + { + size_t j; + + xml_its_rules = its_rule_list_alloc (); + for (j = 0; j < SIZEOF (its_dirs); j++) + { + char *its_filename = + xconcatenated_filename (its_dirs[j], its_basename, NULL); + struct stat statbuf; + bool ok = false; + + if (stat (its_filename, &statbuf) == 0) + ok = its_rule_list_add_from_file (xml_its_rules, its_filename); + free (its_filename); + if (ok) + break; + } + if (j == SIZEOF (its_dirs)) + { + its_rule_list_free (xml_its_rules); + xml_its_rules = NULL; + } + } + locating_rule_list_free (its_locating_rules); + + if (xml_its_rules == NULL) + error (EXIT_FAILURE, 0, _("cannot locate ITS rules for %s"), + xml_template_name); + } + + /* Bulk processing mode for XML files. + Process all .po files in xml_base_directory. */ + if (xml_mode && xml_base_directory) + { + exit_status = msgfmt_xml_bulk (xml_base_directory, + xml_template_name, + xml_its_rules, + output_file_name); + exit (exit_status); + } + /* The -o option determines the name of the domain and therefore the output file. */ if (output_file_name != NULL) @@ -705,6 +840,15 @@ There is NO WARRANTY, to the extent permitted by law.\n\ if (desktop_keywords.table != NULL) hash_destroy (&desktop_keywords); } + else if (xml_mode) + { + if (msgdomain_write_xml (domain->mlp, canon_encoding, + xml_locale_name, + xml_template_name, + xml_its_rules, + domain->file_name)) + exit_status = EXIT_FAILURE; + } else { if (msgdomain_write_mo (domain->mlp, domain->domain_name, @@ -810,6 +954,8 @@ Operation mode:\n")); --qt Qt mode: generate a Qt .qm file\n")); printf (_("\ --desktop Desktop Entry mode: generate a .desktop file\n")); + printf (_("\ + --xml XML mode: generate XML file\n")); printf ("\n"); printf (_("\ Output file location:\n")); @@ -876,6 +1022,22 @@ The -l, -o, and --template options are mandatory. If -D is specified, input\n\ files are read from the directory instead of the command line arguments.\n")); printf ("\n"); printf (_("\ +XML mode options:\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ + -L, --language=NAME recognise the specified XML language\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ + --template=TEMPLATE an XML file used as a template\n")); + printf (_("\ + -d DIRECTORY base directory of .po files\n")); + printf (_("\ +The -l, -o, and --template options are mandatory. If -D is specified, input\n\ +files are read from the directory instead of the command line arguments.\n")); + printf ("\n"); + printf (_("\ Input file syntax:\n")); printf (_("\ -P, --properties-input input files are in Java .properties syntax\n")); @@ -1063,7 +1225,7 @@ msgfmt_set_domain (default_catalog_reader_ty *this, char *name) /* If no output file was given, we change it with each 'domain' directive. */ if (!java_mode && !csharp_mode && !csharp_resources_mode && !tcl_mode - && !qt_mode && !desktop_mode && output_file_name == NULL) + && !qt_mode && !desktop_mode && !xml_mode && output_file_name == NULL) { size_t correct; @@ -1519,3 +1681,37 @@ msgfmt_desktop_bulk (const char *directory, return status; } + +/* Helper function to support 'bulk' operation mode of --xml. + This reads all .po files in DIRECTORY and merges them into an + XML file FILE_NAME. Currently it does not support some + options available in 'iterative' mode, such as --statistics. */ +static int +msgfmt_xml_bulk (const char *directory, + const char *template_file_name, + its_rule_list_ty *its_rules, + const char *file_name) +{ + msgfmt_operand_list_ty operands; + int nerrors, status; + + msgfmt_operand_list_init (&operands); + + /* Read all .po files. */ + nerrors = msgfmt_operand_list_add_from_directory (&operands, directory); + if (nerrors > 0) + { + msgfmt_operand_list_destroy (&operands); + return 1; + } + + /* Write the messages into .xml file. */ + status = msgdomain_write_xml_bulk (&operands, + template_file_name, + its_rules, + file_name); + + msgfmt_operand_list_destroy (&operands); + + return status; +} diff --git a/gettext-tools/src/write-xml.c b/gettext-tools/src/write-xml.c new file mode 100644 index 0000000..38e3195 --- /dev/null +++ b/gettext-tools/src/write-xml.c @@ -0,0 +1,107 @@ +/* Writing XML files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014-2015 + Free Software Foundation, Inc. + This file was written by Daiki Ueno . + + 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "write-xml.h" + +#include +#include +#include +#include +#include "error.h" +#include "msgl-iconv.h" +#include "po-charset.h" +#include "read-catalog.h" +#include "read-po.h" +#include "fwriteerror.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(str) gettext (str) + +int +msgdomain_write_xml_bulk (msgfmt_operand_list_ty *operands, + const char *template_file_name, + its_rule_list_ty *its_rules, + const char *file_name) +{ + its_merge_context_ty *context; + size_t i; + FILE *fp; + + if (strcmp (file_name, "-") == 0) + fp = stdout; + else + { + fp = fopen (file_name, "wb"); + if (fp == NULL) + { + error (0, errno, _("cannot create output file \"%s\""), + file_name); + return 1; + } + } + + context = its_merge_context_alloc (its_rules, template_file_name); + for (i = 0; i < operands->nitems; i++) + its_merge_context_merge (context, + operands->items[i].language, + operands->items[i].mlp); + its_merge_context_write (context, fp); + its_merge_context_free (context); + + /* Make sure nothing went wrong. */ + if (fwriteerror (fp)) + { + error (0, errno, _("error while writing \"%s\" file"), + file_name); + return 1; + } + + return 0; +} + +int +msgdomain_write_xml (message_list_ty *mlp, + const char *canon_encoding, + const char *locale_name, + const char *template_file_name, + its_rule_list_ty *its_rules, + const char *file_name) +{ + msgfmt_operand_ty operand; + msgfmt_operand_list_ty operands; + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + /* Create a single-element operands and run the bulk operation on it. */ + operand.language = (char *) locale_name; + operand.mlp = mlp; + operands.nitems = 1; + operands.items = &operand; + + return msgdomain_write_xml_bulk (&operands, + template_file_name, + its_rules, + file_name); +} diff --git a/gettext-tools/src/write-xml.h b/gettext-tools/src/write-xml.h new file mode 100644 index 0000000..e627789 --- /dev/null +++ b/gettext-tools/src/write-xml.h @@ -0,0 +1,52 @@ +/* Reading XML files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014-2015 + Free Software Foundation, Inc. + This file was written by Daiki Ueno . + + 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 . */ + +#ifndef _WRITE_XML_H +#define _WRITE_XML_H + +#include "its.h" +#include "msgfmt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Write an XML file. mlp is a list containing the messages + to be output. locale_name is the locale name. template_file_name + is the template file. file_name is the output file. Return 0 if + ok, nonzero on error. */ +extern int + msgdomain_write_xml (message_list_ty *mlp, + const char *canon_encoding, + const char *locale_name, + const char *template_file_name, + its_rule_list_ty *its_rules, + const char *file_name); + +extern int + msgdomain_write_xml_bulk (msgfmt_operand_list_ty *operands, + const char *template_file_name, + its_rule_list_ty *its_rules, + const char *file_name); + +#ifdef __cplusplus +} +#endif + + +#endif /* _WRITE_XML_H */ diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index bb24284..d3ffb75 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -51,6 +51,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \ msgfmt-properties-1 \ msgfmt-qt-1 msgfmt-qt-2 \ msgfmt-desktop-1 msgfmt-desktop-2 \ + msgfmt-xml-1 msgfmt-xml-2 \ msggrep-1 msggrep-2 msggrep-3 msggrep-4 msggrep-5 msggrep-6 msggrep-7 \ msggrep-8 msggrep-9 msggrep-10 msggrep-11 \ msginit-1 msginit-2 msginit-3 msginit-4 \ diff --git a/gettext-tools/tests/msgfmt-xml-1 b/gettext-tools/tests/msgfmt-xml-1 new file mode 100755 index 0000000..40e956b --- /dev/null +++ b/gettext-tools/tests/msgfmt-xml-1 @@ -0,0 +1,119 @@ +#! /bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test iterative mode of msgfmt --xml. + +cat <<\EOF > mf.appdata.xml + + + org.gnome.Characters.desktop + GNOME Characters + Character map application + CC0 + +

+ Characters is a simple utility application to find and insert + unusual characters. It allows you to quickly find the character + you are looking for by searching for keywords. +

+

+ You can also browse characters by categories, such as + Punctuation, Pictures, etc. +

+
+ https://wiki.gnome.org/Design/Apps/CharacterMap + dueno_at_src.gnome.org +
+EOF + +cat <<\EOF > fr.po +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-03-17 07:36+0900\n" +"PO-Revision-Date: 2014-03-17 08:40+0900\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "" +"Characters is a simple utility application to find and insert unusual " +"characters. It allows you to quickly find the character you are looking for " +"by searching for keywords." +msgstr "" +"Caractères est un utilitaire pour chercher et insérer des caractères " +"inhabituels. Il vous permet de trouver rapidement le caractère que vous " +"cherchez par le biais de mots-clés." + +msgid "" +"You can also browse characters by categories, such as Punctuation, Pictures, " +"etc." +msgstr "" +"Vous pouvez aussi naviguer dans les caractères par catégories, comme par " +"Ponctuation, Images, etc." +EOF + +cat <<\EOF > mf.appdata.xml.ok + + + org.gnome.Characters.desktop + GNOME Characters + Character map application + CC0 + +

+ Characters is a simple utility application to find and insert + unusual characters. It allows you to quickly find the character + you are looking for by searching for keywords. +

+

Caractères est un utilitaire pour chercher et insérer des caractères inhabituels. Il vous permet de trouver rapidement le caractère que vous cherchez par le biais de mots-clés.

+

+ You can also browse characters by categories, such as + Punctuation, Pictures, etc. +

+

Vous pouvez aussi naviguer dans les caractères par catégories, comme par Ponctuation, Images, etc.

+
+ https://wiki.gnome.org/Design/Apps/CharacterMap + dueno_at_src.gnome.org +
+EOF + +# Sanity checks for contradicting options. + +${MSGFMT} --xml --template=mf.appdata.xml -l fr fr.po \ + >/dev/null 2>/dev/null \ + && exit 1 + +${MSGFMG} --xml --template=mf.appdata.xml fr.po -o mf.appdata.xml.out \ + >/dev/null 2>/dev/null \ + && exit 1 + +# Proceed to the XML file generation. + +${MSGFMT} --xml --template=mf.appdata.xml -l fr fr.po -o mf.appdata.xml.out \ + || exit 1 + +: ${DIFF=diff} +${DIFF} mf.appdata.xml.ok mf.appdata.xml.out +result=$? +test $result = 0 || exit $result + +# Test -L option. +cp mf.appdata.xml mf.xml +${MSGFMT} --xml --template=mf.xml -L AppData -l fr fr.po -o mf.appdata.xml.out \ + || exit 1 +${DIFF} mf.appdata.xml.ok mf.appdata.xml.out +result=$? +test $result = 0 || exit $result + +exit $result diff --git a/gettext-tools/tests/msgfmt-xml-2 b/gettext-tools/tests/msgfmt-xml-2 new file mode 100755 index 0000000..d10d21c --- /dev/null +++ b/gettext-tools/tests/msgfmt-xml-2 @@ -0,0 +1,203 @@ +#! /bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test 'bulk' mode of msgfmt --xml. + +cat <<\EOF > mf.appdata.xml + + + org.gnome.Characters.desktop + GNOME Characters + Character map application + CC0 + +

+ Characters is a simple utility application to find and insert + unusual characters. It allows you to quickly find the character + you are looking for by searching for keywords. +

+

+ You can also browse characters by categories, such as + Punctuation, Pictures, etc. +

+
+ https://wiki.gnome.org/Design/Apps/CharacterMap + dueno_at_src.gnome.org +
+EOF + +test -d po || mkdir po + +cat <<\EOF > po/fr.po +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-03-17 07:36+0900\n" +"PO-Revision-Date: 2014-03-17 08:40+0900\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "" +"Characters is a simple utility application to find and insert unusual " +"characters. It allows you to quickly find the character you are looking for " +"by searching for keywords." +msgstr "" +"Caractères est un utilitaire pour chercher et insérer des caractères " +"inhabituels. Il vous permet de trouver rapidement le caractère que vous " +"cherchez par le biais de mots-clés." + +msgid "" +"You can also browse characters by categories, such as Punctuation, Pictures, " +"etc." +msgstr "" +"Vous pouvez aussi naviguer dans les caractères par catégories, comme par " +"Ponctuation, Images, etc." +EOF + +cat <<\EOF > po/de.po +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-03-17 07:36+0900\n" +"PO-Revision-Date: 2014-03-17 08:40+0900\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "" +"Characters is a simple utility application to find and insert unusual " +"characters. It allows you to quickly find the character you are looking for by " +"searching for keywords." +msgstr "" +"Zeichen ist ein einfaches Hilfsprogramm zum Auffinden und Einsetzen von selten " +"verwendeten Zeichen. Sie können schnell das gesuchte Zeichen finden, indem Sie " +"nach Schlüsselwörtern suchen." + +msgid "" +"You can also browse characters by categories, such as Punctuation, Pictures, " +"etc." +msgstr "" +"Sie können ebenfalls nach Kategorie suchen, wie z.B. nach Zeichensetzung oder " +"Bildern." +EOF + +cat <<\EOF > mf.appdata.xml.ok + + + org.gnome.Characters.desktop + GNOME Characters + Character map application + CC0 + +

+ Characters is a simple utility application to find and insert + unusual characters. It allows you to quickly find the character + you are looking for by searching for keywords. +

+

Caractères est un utilitaire pour chercher et insérer des caractères inhabituels. Il vous permet de trouver rapidement le caractère que vous cherchez par le biais de mots-clés.

+

Zeichen ist ein einfaches Hilfsprogramm zum Auffinden und Einsetzen von selten verwendeten Zeichen. Sie können schnell das gesuchte Zeichen finden, indem Sie nach Schlüsselwörtern suchen.

+

+ You can also browse characters by categories, such as + Punctuation, Pictures, etc. +

+

Vous pouvez aussi naviguer dans les caractères par catégories, comme par Ponctuation, Images, etc.

+

Sie können ebenfalls nach Kategorie suchen, wie z.B. nach Zeichensetzung oder Bildern.

+
+ https://wiki.gnome.org/Design/Apps/CharacterMap + dueno_at_src.gnome.org +
+EOF + +cat <<\EOF > mf.appdata.xml.desired.ok + + + org.gnome.Characters.desktop + GNOME Characters + Character map application + CC0 + +

+ Characters is a simple utility application to find and insert + unusual characters. It allows you to quickly find the character + you are looking for by searching for keywords. +

+

Caractères est un utilitaire pour chercher et insérer des caractères inhabituels. Il vous permet de trouver rapidement le caractère que vous cherchez par le biais de mots-clés.

+

+ You can also browse characters by categories, such as + Punctuation, Pictures, etc. +

+

Vous pouvez aussi naviguer dans les caractères par catégories, comme par Ponctuation, Images, etc.

+
+ https://wiki.gnome.org/Design/Apps/CharacterMap + dueno_at_src.gnome.org +
+EOF + +unset LINGUAS + +# Sanity checks for contradicting options. + +${MSGFMT} --xml --template=mf.appdata.xml -d po -o mf.appdata.xml.out \ + >/dev/null 2>/dev/null \ + exit 1 + +test -d po/LINGUAS || mkdir po/LINGUAS + +${MSGFMT} --xml --template=mf.appdata.xml -d po -o mf.appdata.xml.out \ + >/dev/null 2>/dev/null \ + exit 1 + +rm -fr po/LINGUAS + +cat <<\EOF > po/LINGUAS +de +fr +EOF + +${MSGFMT} --xml --template=mf.appdata.xml -d po \ + >/dev/null 2>/dev/null \ + && exit 1 + +${MSGFMG} --xml --template=mf.appdata.xml -d po -o mf.appdata.xml.out -l fr \ + >/dev/null 2>/dev/null \ + && exit 1 + +${MSGFMG} --xml --template=mf.appdata.xml -d po -o mf.appdata.xml.out po/fr.po \ + >/dev/null 2>/dev/null \ + && exit 1 + +# Proceed to the .desktop file generation. + +${MSGFMT} --xml --template=mf.appdata.xml -d po -o mf.appdata.xml.out || exit 1 +: ${DIFF=diff} +${DIFF} mf.appdata.xml.ok mf.appdata.xml.out +test $? = 0 || exit 1 + +# Restrict the desired languages with the LINGUAS envvar. + +LINGUAS="fr ja" ${MSGFMT} --xml --template=mf.appdata.xml -d po -o mf.appdata.xml.desired.out || exit 1 + +: ${DIFF=diff} +${DIFF} mf.appdata.xml.desired.ok mf.appdata.xml.desired.out +test $? = 0 || exit 1 -- cgit v1.1