]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
msgfmt: Support XML file merging wip/ueno/its2
authorDaiki Ueno <ueno@gnu.org>
Mon, 5 Oct 2015 08:18:41 +0000 (17:18 +0900)
committerDaiki Ueno <ueno@gnu.org>
Thu, 15 Oct 2015 04:09:43 +0000 (13:09 +0900)
* 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
gettext-tools/doc/msgfmt.texi
gettext-tools/src/Makefile.am
gettext-tools/src/its.c
gettext-tools/src/its.h
gettext-tools/src/msgfmt.c
gettext-tools/src/write-xml.c [new file with mode: 0644]
gettext-tools/src/write-xml.h [new file with mode: 0644]
gettext-tools/tests/Makefile.am
gettext-tools/tests/msgfmt-xml-1 [new file with mode: 0755]
gettext-tools/tests/msgfmt-xml-2 [new file with mode: 0755]

index b1cb24e218b41bb427a21efdcc2e3c70046f52b6..fef15c1ad7588446ada5cf2883feffc3d0b6097f 100644 (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
 
index 402bc1a76ed9f8a9b50426b427ea3d71cd592d7f..9513af75f325af6f4f746f9efc70a7b25e0e6630 100644 (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
index 20a5877cbad7b527bfc536f3d3d2ee3bb8e972aa..f380e431f12fab584db0204a761352183fba1f96 100644 (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
index 585e9845ace5b7aee127a5a13b34235022ec7fb8..6843aac7f8440f538c85a9fbf389dfcea01611b2 100644 (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);
+}
index d26bbcc16290341a5c02381eb4fd9d48b27e506c..8d597f51e26ad6f177c7567f6de3315e277ab83d 100644 (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
index 3dfafdc6fad54256b3e6c21c0af3edcf81b1c7de..e74ab0613a1a7244895e89380eb964fa94af27c6 100644 (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"
 #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;
+}
diff --git a/gettext-tools/src/write-xml.c b/gettext-tools/src/write-xml.c
new file mode 100644 (file)
index 0000000..38e3195
--- /dev/null
@@ -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);
+}
diff --git a/gettext-tools/src/write-xml.h b/gettext-tools/src/write-xml.h
new file mode 100644 (file)
index 0000000..e627789
--- /dev/null
@@ -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 */
index bb24284b18f75126c362ccbb777643ed75a751d9..d3ffb75985b632decbd61c41f33b0854ab977bcd 100644 (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 \
diff --git a/gettext-tools/tests/msgfmt-xml-1 b/gettext-tools/tests/msgfmt-xml-1
new file mode 100755 (executable)
index 0000000..40e956b
--- /dev/null
@@ -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
diff --git a/gettext-tools/tests/msgfmt-xml-2 b/gettext-tools/tests/msgfmt-xml-2
new file mode 100755 (executable)
index 0000000..d10d21c
--- /dev/null
@@ -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