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.
This commit is contained in:
Daiki Ueno 2015-10-05 17:18:41 +09:00
parent 8701164b86
commit 2d1143a6ac
11 changed files with 914 additions and 13 deletions

View File

@ -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

View File

@ -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
@ -202,11 +207,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}
@ -220,6 +222,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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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,12 +456,16 @@ 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"),
@ -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 desktop_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"));
@ -873,6 +1019,22 @@ Desktop Entry mode options:\n"));
-k, --keyword do not to use default keywords\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 (_("\
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 a .desktop 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 (_("\
@ -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;
}

View File

@ -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 <ueno@gnu.org>.
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 <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* Specification. */
#include "write-xml.h"
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#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);
}

View File

@ -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 <ueno@gnu.org>.
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 <http://www.gnu.org/licenses/>. */
#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 */

View File

@ -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 \

119
gettext-tools/tests/msgfmt-xml-1 Executable file
View File

@ -0,0 +1,119 @@
#! /bin/sh
. "${srcdir=.}/init.sh"; path_prepend_ . ../src
# Test iterative mode of msgfmt --xml.
cat <<\EOF > mf.appdata.xml
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>org.gnome.Characters.desktop</id>
<name>GNOME Characters</name>
<summary>Character map application</summary>
<licence>CC0</licence>
<description>
<p>
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.
</p>
<p>
You can also browse characters by categories, such as
Punctuation, Pictures, etc.
</p>
</description>
<url type="homepage">https://wiki.gnome.org/Design/Apps/CharacterMap</url>
<updatecontact>dueno_at_src.gnome.org</updatecontact>
</component>
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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>org.gnome.Characters.desktop</id>
<name>GNOME Characters</name>
<summary>Character map application</summary>
<licence>CC0</licence>
<description>
<p>
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.
</p>
<p xml:lang="fr">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.</p>
<p>
You can also browse characters by categories, such as
Punctuation, Pictures, etc.
</p>
<p xml:lang="fr">Vous pouvez aussi naviguer dans les caractères par catégories, comme par Ponctuation, Images, etc.</p>
</description>
<url type="homepage">https://wiki.gnome.org/Design/Apps/CharacterMap</url>
<updatecontact>dueno_at_src.gnome.org</updatecontact>
</component>
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

203
gettext-tools/tests/msgfmt-xml-2 Executable file
View File

@ -0,0 +1,203 @@
#! /bin/sh
. "${srcdir=.}/init.sh"; path_prepend_ . ../src
# Test 'bulk' mode of msgfmt --xml.
cat <<\EOF > mf.appdata.xml
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>org.gnome.Characters.desktop</id>
<name>GNOME Characters</name>
<summary>Character map application</summary>
<licence>CC0</licence>
<description>
<p>
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.
</p>
<p>
You can also browse characters by categories, such as
Punctuation, Pictures, etc.
</p>
</description>
<url type="homepage">https://wiki.gnome.org/Design/Apps/CharacterMap</url>
<updatecontact>dueno_at_src.gnome.org</updatecontact>
</component>
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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>org.gnome.Characters.desktop</id>
<name>GNOME Characters</name>
<summary>Character map application</summary>
<licence>CC0</licence>
<description>
<p>
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.
</p>
<p xml:lang="fr">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.</p>
<p xml:lang="de">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.</p>
<p>
You can also browse characters by categories, such as
Punctuation, Pictures, etc.
</p>
<p xml:lang="fr">Vous pouvez aussi naviguer dans les caractères par catégories, comme par Ponctuation, Images, etc.</p>
<p xml:lang="de">Sie können ebenfalls nach Kategorie suchen, wie z.B. nach Zeichensetzung oder Bildern.</p>
</description>
<url type="homepage">https://wiki.gnome.org/Design/Apps/CharacterMap</url>
<updatecontact>dueno_at_src.gnome.org</updatecontact>
</component>
EOF
cat <<\EOF > mf.appdata.xml.desired.ok
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>org.gnome.Characters.desktop</id>
<name>GNOME Characters</name>
<summary>Character map application</summary>
<licence>CC0</licence>
<description>
<p>
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.
</p>
<p xml:lang="fr">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.</p>
<p>
You can also browse characters by categories, such as
Punctuation, Pictures, etc.
</p>
<p xml:lang="fr">Vous pouvez aussi naviguer dans les caractères par catégories, comme par Ponctuation, Images, etc.</p>
</description>
<url type="homepage">https://wiki.gnome.org/Design/Apps/CharacterMap</url>
<updatecontact>dueno_at_src.gnome.org</updatecontact>
</component>
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