xgettext: Add support for generic XML files

* autogen.sh (GNULIB_MODULES_TOOLS_FOR_SRC): Add trim module.
* gettext-tools/src/locating-rule.h: New file.
* gettext-tools/src/locating-rule.c: New file.
* gettext-tools/src/its.h: New file.
* gettext-tools/src/its.c: New file.
* gettext-tools/src/xgettext.c: Include "locating-rule.h" and "its.h".
(its_locators): New variable.
(long_options): Add --itstool option.
(extract_from_xml_file): New function.
(main): Handle --itstool option.
(usage): Document --itstool option.
* gettext-tools/src/Makefile.am (noinst_HEADERS): Add locating-rule.h
and its.h.
(libgettextsrc_la_CPPFLAGS): Add $(INCXML).
(libgettextsrc_la_SOURCES): Add locating-rule.c and its.c.
* gettext-tools/Makefile.am (SUBDIRS): Add its directory.
* gettext-tools/configure.ac: Output its/Makefile.
* gettext-tools/doc/gettext.texi (Preparing ITS Rules): New section.
* gettext-tools/doc/xgettext.texi: Mention --itstool option.
* gettext-tools/tests/Makefile.am (TESTS): Add new tests.
* gettext-tools/tests/xgettext-its-1: New file
* gettext-tools/tests/init-env.in: Set GETTEXTDATADIR for ITS tests.
This commit is contained in:
Daiki Ueno
2015-09-30 15:40:26 +09:00
parent 4d0dcc0c64
commit 6fab71fdbe
15 changed files with 3012 additions and 36 deletions
+1
View File
@@ -245,6 +245,7 @@ if ! $skip_gnulib; then
sys_stat
sys_time
term-styled-ostream
trim
unictype/ctype-space
unilbrk/ulc-width-linebreaks
uniname/uniname
+1 -1
View File
@@ -19,7 +19,7 @@
AUTOMAKE_OPTIONS = 1.5 gnu no-dependencies
ACLOCAL_AMFLAGS = -I m4 -I ../gettext-runtime/m4 -I ../m4 -I gnulib-m4 -I libgrep/gnulib-m4 -I libgettextpo/gnulib-m4
SUBDIRS = doc intl gnulib-lib libgrep src libgettextpo po projects styles misc man m4 tests gnulib-tests examples
SUBDIRS = doc intl gnulib-lib libgrep src libgettextpo po projects styles misc man m4 tests gnulib-tests examples its
EXTRA_DIST = misc/DISCLAIM
MOSTLYCLEANFILES = core *.stackdump
+7
View File
@@ -461,6 +461,11 @@ AC_SUBST([ARCHIVE_FORMAT])
ARCHIVE_VERSION=0.19.6
AC_SUBST([ARCHIVE_VERSION])
PACKAGE_SUFFIX="-$ARCHIVE_VERSION"
AC_SUBST([PACKAGE_SUFFIX])
AC_DEFINE_UNQUOTED(PACKAGE_SUFFIX, "$PACKAGE_SUFFIX",
[Define to the suffix of this package])
dnl Check for tools needed for formatting the documentation.
ac_aux_dir_abs=`cd $ac_aux_dir && pwd`
AC_PATH_PROG([DVIPS], [dvips], [$ac_aux_dir_abs/missing dvips])
@@ -519,6 +524,8 @@ AC_CONFIG_FILES([intl/Makefile:../gettext-runtime/intl/Makefile.in], [
mv intl/Makefile.tmp intl/Makefile
])
AC_CONFIG_FILES([its/Makefile])
AC_CONFIG_FILES([gnulib-lib/Makefile])
AC_CONFIG_FILES([libgrep/Makefile])
+136 -1
View File
@@ -470,6 +470,7 @@ Internationalizable Data
* Glade:: Glade - GNOME user interface description
* GSettings:: GSettings - GNOME user configuration schema
* AppData:: AppData - freedesktop.org application description
* Preparing ITS Rules:: Preparing Rules for XML Internationalization
Concluding Remarks
@@ -12155,6 +12156,7 @@ using GNU gettext.
* Glade:: Glade - GNOME user interface description
* GSettings:: GSettings - GNOME user configuration schema
* AppData:: AppData - freedesktop.org application description
* Preparing ITS Rules:: Preparing Rules for XML Internationalization
@end menu
@node POT, RST, List of Data Formats, List of Data Formats
@@ -12214,7 +12216,7 @@ glib2
@code{xgettext}, @code{intltool-extract}
@end table
@node AppData, , GSettings, List of Data Formats
@node AppData, Preparing ITS Rules, GSettings, List of Data Formats
@subsection AppData - freedesktop.org application description
@table @asis
@@ -12228,6 +12230,139 @@ appdata-tools, appstream, libappstream-glib, libappstream-glib-builder
@code{xgettext}, @code{intltool-extract}, @code{itstool}
@end table
@menu
@end menu
@node Preparing ITS Rules, , AppData, List of Data Formats
@subsection Preparing Rules for XML Internationalization
@cindex preparing rules for XML translation
Marking translatable strings in an XML file is done through a separate
"rule" file, making use of the Internationalization Tag Set standard
(ITS, @uref{http://www.w3.org/TR/its20/}). The currently supported ITS
data categories are: @samp{Translate}, @samp{Localization Note},
@samp{Elements Within Text}, and @samp{Preserve Space}. In addition to
them, @code{xgettext} also recognizes the following extended data
categories:
@table @samp
@item Context
This data category associates @code{msgctxt} to the extracted text. In
the global rule, the @code{contextRule} 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{contextPointer} attribute that contains a relative
selector pointing to a node that holds the @code{msgctxt} value.
@item
An optional @code{textPointer} attribute that contains a relative
selector pointing to a node that holds the @code{msgid} value.
@end itemize
@item Escape Special Characters
This data category indicates whether the special XML characters
(@code{<}, @code{>}, @code{&}, @code{"}) are escaped with entity
reference. In the global rule, the @code{escapeRule} 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{escape} attribute with the value @code{yes} or @code{no}.
@end itemize
@item Extended Preserve Space
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.
@end table
All those extended data categories can only be expressed with global
rules, and the rule elements have to have the
@code{https://www.gnu.org/s/gettext/ns/its/extensions/1.0} namespace.
Given the following XML document in a file @file{messages.xml}:
@example
<?xml version="1.0"?>
<messages>
<message>
<p>A translatable string</p>
</message>
<message>
<p translatable="no">A non-translatable string</p>
</message>
</messages>
@end example
To extract the first text content ("A translatable string"), but not the
second ("A non-translatable string"), the following ITS rules can be used:
@example
<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its" version="1.0">
<its:translateRule selector="/messages" translate="no"/>
<its:translateRule selector="//message/p" translate="yes"/>
<!-- If 'p' has an attribute 'translatable' with the value 'no', then
the content is not translatable. -->
<its:translateRule selector="//message/p[@@translatable = 'no']"
translate="no"/>
</its:rules>
@end example
@samp{xgettext} needs another file called "locating rule" to associate
an ITS rule with an XML file. If the above ITS file is saved as
@file{messages.its}, the locating rule would look like:
@example
<?xml version="1.0"?>
<locatingRules>
<locatingRule name="Messages" pattern="*.xml">
<documentRule localName="messages" target="messages.its"/>
</locatingRule>
<locatingRule name="Messages" pattern="*.msg" target="messages.its"/>
</locatingRules>
@end example
The @code{locatingRule} element must have a @code{pattern} attribute,
which denotes either a literal file name or a wildcard pattern of the
XML file. The @code{locatingRule} element can have child
@code{documentRule} element, which adds checks on the content of the XML
file.
The first rule matches any file with the @file{.xml} file extension, but
it only applies to XML files whose root element is @samp{<messages>}.
The second rule indicates that the same ITS rule file are also
applicable to any file with the @file{.msg} file extension. The
optional @code{name} attribute of @code{locatingRule} allows to choose
rules by name, typically with @code{xgettext}'s @code{-L} option.
The associated ITS rule file is indicated by the @code{target} attribute
of @code{locatingRule} or @code{documentRule}. If it is specified in a
@code{documentRule} element, the parent @code{locatingRule} shouldn't
have the @code{target} attribute.
Locating rule files must have the @file{.loc} file extension. Both ITS
rule files and locating rule files must be installed in the
@file{$prefix/share/gettext/its} directory. Once those files are
properly installed, @code{xgettext} can extract translatable strings
from the matching XML files.
@c This is the template for new data formats.
@ignore
+5
View File
@@ -500,6 +500,11 @@ obsolete messages.
Write out a NeXTstep/GNUstep localized resource file in @code{.strings} syntax.
Note that this file format doesn't support plural forms.
@item --itstool
@opindex --itstool@r{, @code{xgettext} option}
Write out comments recognized by itstool (@uref{http://itstool.org}).
Note that this is only effective with XML files.
@item -w @var{number}
@itemx --width=@var{number}
@opindex -w@r{, @code{xgettext} option}
+23
View File
@@ -0,0 +1,23 @@
## Makefile for the gettext-tools/its subdirectory of GNU gettext
## Copyright (C) 2015 Free Software Foundation, Inc.
##
## 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/>.
## Process this file with automake to produce Makefile.in.
AUTOMAKE_OPTIONS = 1.2 gnits
pkgdatadir = $(datadir)/gettext
itsdir = $(pkgdatadir)$(PACKAGE_SUFFIX)/its
dist_its_DATA =
+3 -3
View File
@@ -40,7 +40,7 @@ read-po.h read-properties.h read-stringtable.h \
str-list.h \
color.h write-catalog.h write-po.h write-properties.h write-stringtable.h \
dir-list.h file-list.h po-gram-gen.h po-gram-gen2.h cldr-plural.h \
cldr-plural-exp.h \
cldr-plural-exp.h locating-rule.h its.h \
msgl-charset.h msgl-equal.h msgl-iconv.h msgl-ascii.h msgl-cat.h msgl-header.h \
msgl-english.h msgl-check.h msgl-fsearch.h msgfmt.h msgunfmt.h \
plural-count.h plural-eval.h plural-distrib.h \
@@ -153,7 +153,7 @@ msgl-ascii.c msgl-iconv.c msgl-equal.c msgl-cat.c msgl-header.c msgl-english.c \
msgl-check.c file-list.c msgl-charset.c po-time.c plural-exp.c plural-eval.c \
plural-table.c quote.h sentence.h sentence.c libexpat-compat.c \
$(FORMAT_SOURCE) \
read-desktop.c
read-desktop.c locating-rule.c its.c
# msggrep needs pattern matching.
LIBGREP = ../libgrep/libgrep.a
@@ -254,7 +254,7 @@ libgettextsrc_la_LDFLAGS = \
-release @VERSION@ \
../gnulib-lib/libgettextlib.la $(LTLIBUNISTRING) @LTLIBINTL@ @LTLIBICONV@ @LTLIBEXPAT@ -lc -no-undefined
libgettextsrc_la_CPPFLAGS = $(AM_CPPFLAGS)
libgettextsrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(INCXML)
# Tell the mingw or Cygwin linker which symbols to export.
if WOE32DLL
File diff suppressed because it is too large Load Diff
+73
View File
@@ -0,0 +1,73 @@
/* Internationalization Tag Set (ITS) handling
Copyright (C) 2015 Free Software Foundation, Inc.
This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#ifndef _ITS_H_
#define _ITS_H_
#include "message.h"
#include "xgettext.h"
#ifdef __cplusplus
extern "C" {
#endif
enum its_whitespace_type_ty
{
ITS_WHITESPACE_PRESERVE,
ITS_WHITESPACE_NORMALIZE,
ITS_WHITESPACE_TRIM
};
typedef struct its_rule_list_ty its_rule_list_ty;
typedef message_ty *
(*its_extract_callback_ty) (message_list_ty *mlp,
const char *msgctxt,
const char *msgid,
lex_pos_ty *pos,
const char *extracted_comment,
const char *marker,
enum its_whitespace_type_ty whitespace);
/* Creates a fresh its_rule_list_ty holding global ITS rules. */
extern its_rule_list_ty *its_rule_list_alloc (void);
/* Releases memory allocated for RULES. */
extern void its_rule_list_free (its_rule_list_ty *rules);
/* Loads global ITS rules from STRING. */
extern bool its_rule_list_add_from_string (struct its_rule_list_ty *rules,
const char *rule);
/* Loads global ITS rules from FILENAME. */
extern bool its_rule_list_add_from_file (its_rule_list_ty *rules,
const char *filename);
/* Extracts messages from FP, accoding to the loaded ITS rules. */
extern void its_rule_list_extract (its_rule_list_ty *rules,
FILE *fp, const char *real_filename,
const char *logical_filename,
flag_context_list_table_ty *flag_table,
msgdomain_list_ty *mdlp,
its_extract_callback_ty callback);
#ifdef __cplusplus
}
#endif
#endif /* _ITS_H_ */
+437
View File
@@ -0,0 +1,437 @@
/* XML resource locating rules
Copyright (C) 2015 Free Software Foundation, Inc.
This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* Specification. */
#include "locating-rule.h"
#include "basename.h"
#include "concat-filename.h"
#include "c-strcase.h"
#if HAVE_DIRENT_H
# include <dirent.h>
#endif
#if HAVE_DIRENT_H
# define HAVE_DIR 1
#else
# define HAVE_DIR 0
#endif
#include "dir-list.h"
#include <errno.h>
#include "error.h"
#include "filename.h"
#include <fnmatch.h>
#include "gettext.h"
#include "hash.h"
#include <libxml/parser.h>
#include <libxml/uri.h>
#include "xalloc.h"
#define _(str) gettext (str)
#define LOCATING_RULES_NS "https://www.gnu.org/s/gettext/ns/locating-rules/1.0"
struct document_locating_rule_ty
{
char *ns;
char *local_name;
char *target;
};
struct document_locating_rule_list_ty
{
struct document_locating_rule_ty *items;
size_t nitems;
size_t nitems_max;
};
struct locating_rule_ty
{
char *pattern;
char *name;
struct document_locating_rule_list_ty doc_rules;
char *target;
};
struct locating_rule_list_ty
{
struct locating_rule_ty *items;
size_t nitems;
size_t nitems_max;
};
static char *
get_attribute (xmlNode *node, const char *attr)
{
xmlChar *value;
char *result;
value = xmlGetProp (node, BAD_CAST attr);
result = xstrdup ((const char *) value);
xmlFree (value);
return result;
}
static const char *
document_locating_rule_match (struct document_locating_rule_ty *rule,
xmlDoc *doc)
{
xmlNode *root;
root = xmlDocGetRootElement (doc);
if (rule->ns != NULL)
{
if (root->ns == NULL
|| !xmlStrEqual (root->ns->href, BAD_CAST rule->ns))
return NULL;
}
if (rule->local_name != NULL)
{
if (!xmlStrEqual (root->name,
BAD_CAST rule->local_name))
return NULL;
}
return rule->target;
}
static const char *
locating_rule_match (struct locating_rule_ty *rule,
const char *filename,
const char *name)
{
if (name != NULL)
{
if (rule->name == NULL || c_strcasecmp (name, rule->name) != 0)
return NULL;
}
else
{
const char *base;
char *reduced;
int err;
base = strrchr (filename, '/');
if (!base)
base = filename;
reduced = xstrdup (base);
/* Remove a trailing ".in" - it's a generic suffix. */
while (strlen (reduced) >= 3
&& memcmp (reduced + strlen (reduced) - 3, ".in", 3) == 0)
reduced[strlen (reduced) - 3] = '\0';
err = fnmatch (rule->pattern, basename (reduced), FNM_PATHNAME);
free (reduced);
if (err != 0)
return NULL;
}
/* Check documentRules. */
if (rule->doc_rules.nitems > 0)
{
const char *target;
xmlDoc *doc;
size_t i;
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;
}
for (i = 0, target = NULL; i < rule->doc_rules.nitems; i++)
{
target =
document_locating_rule_match (&rule->doc_rules.items[i], doc);
if (target)
break;
}
xmlFreeDoc (doc);
if (target != NULL)
return target;
}
if (rule->target != NULL)
return rule->target;
return NULL;
}
const char *
locating_rule_list_locate (struct locating_rule_list_ty *rules,
const char *filename,
const char *name)
{
const char *target = NULL;
size_t i;
for (i = 0; i < rules->nitems; i++)
{
if (IS_ABSOLUTE_PATH (filename))
{
target = locating_rule_match (&rules->items[i], filename, name);
if (target != NULL)
return target;
}
else
{
int j;
for (j = 0; ; ++j)
{
const char *dir = dir_list_nth (j);
char *new_filename;
if (dir == NULL)
break;
new_filename = xconcatenated_filename (dir, filename, NULL);
target = locating_rule_match (&rules->items[i], new_filename,
name);
free (new_filename);
if (target != NULL)
return target;
}
}
}
return NULL;
}
static void
missing_attribute (xmlNode *node, const char *attribute)
{
error (0, 0, _("\"%s\" node does not have \"%s\""), node->name, attribute);
}
static void
document_locating_rule_destroy (struct document_locating_rule_ty *rule)
{
free (rule->ns);
free (rule->local_name);
free (rule->target);
}
static void
document_locating_rule_list_add (struct document_locating_rule_list_ty *rules,
xmlNode *node)
{
struct document_locating_rule_ty rule;
if (!xmlHasProp (node, BAD_CAST "target"))
{
missing_attribute (node, "target");
return;
}
memset (&rule, 0, sizeof (struct document_locating_rule_ty));
if (xmlHasProp (node, BAD_CAST "ns"))
rule.ns = get_attribute (node, "ns");
if (xmlHasProp (node, BAD_CAST "localName"))
rule.local_name = get_attribute (node, "localName");
rule.target = get_attribute (node, "target");
if (rules->nitems == rules->nitems_max)
{
rules->nitems_max = 2 * rules->nitems_max + 1;
rules->items =
xrealloc (rules->items,
sizeof (struct document_locating_rule_ty)
* rules->nitems_max);
}
memcpy (&rules->items[rules->nitems++], &rule,
sizeof (struct document_locating_rule_ty));
}
static void
locating_rule_destroy (struct locating_rule_ty *rule)
{
size_t i;
for (i = 0; i < rule->doc_rules.nitems; i++)
document_locating_rule_destroy (&rule->doc_rules.items[i]);
free (rule->doc_rules.items);
free (rule->name);
free (rule->pattern);
free (rule->target);
}
static bool
locating_rule_list_add_from_file (struct locating_rule_list_ty *rules,
const char *rule_file_name)
{
xmlDoc *doc;
xmlNode *root, *node;
doc = xmlReadFile (rule_file_name, "utf-8",
XML_PARSE_NONET
| XML_PARSE_NOWARNING
| XML_PARSE_NOBLANKS
| XML_PARSE_NOERROR);
if (doc == NULL)
{
error (0, 0, _("cannot read XML file %s"), rule_file_name);
return false;
}
root = xmlDocGetRootElement (doc);
if (!(xmlStrEqual (root->name, BAD_CAST "locatingRules")
#if 0
&& root->ns
&& xmlStrEqual (root->ns->href, BAD_CAST LOCATING_RULES_NS)
#endif
))
{
error (0, 0, _("the root element is not \"locatingRules\""));
xmlFreeDoc (doc);
return false;
}
for (node = root->children; node; node = node->next)
{
if (xmlStrEqual (node->name, BAD_CAST "locatingRule"))
{
struct locating_rule_ty rule;
if (!xmlHasProp (node, BAD_CAST "pattern"))
{
missing_attribute (node, "pattern");
xmlFreeDoc (doc);
continue;
}
memset (&rule, 0, sizeof (struct locating_rule_ty));
rule.pattern = get_attribute (node, "pattern");
if (xmlHasProp (node, BAD_CAST "name"))
rule.name = get_attribute (node, "name");
if (xmlHasProp (node, BAD_CAST "target"))
rule.target = get_attribute (node, "target");
else
{
xmlNode *n;
for (n = node->children; n; n = n->next)
{
if (xmlStrEqual (n->name, BAD_CAST "documentRule"))
document_locating_rule_list_add (&rule.doc_rules, n);
}
}
if (rules->nitems == rules->nitems_max)
{
rules->nitems_max = 2 * rules->nitems_max + 1;
rules->items =
xrealloc (rules->items,
sizeof (struct locating_rule_ty) * rules->nitems_max);
}
memcpy (&rules->items[rules->nitems++], &rule,
sizeof (struct locating_rule_ty));
}
}
xmlFreeDoc (doc);
return true;
}
bool
locating_rule_list_add_from_directory (struct locating_rule_list_ty *rules,
const char *directory)
{
#if HAVE_DIR
DIR *dirp;
dirp = opendir (directory);
if (dirp == NULL)
return false;
for (;;)
{
struct dirent *dp;
errno = 0;
dp = readdir (dirp);
if (dp != NULL)
{
const char *name = dp->d_name;
size_t namlen = strlen (name);
if (namlen > 4 && memcmp (name + namlen - 4, ".loc", 4) == 0)
{
char *locator_file_name =
xconcatenated_filename (directory, name, NULL);
locating_rule_list_add_from_file (rules, locator_file_name);
free (locator_file_name);
}
}
else if (errno != 0)
return false;
else
break;
}
if (closedir (dirp))
return false;
#endif
return true;
}
struct locating_rule_list_ty *
locating_rule_list_alloc (void)
{
struct locating_rule_list_ty *result;
xmlCheckVersion (LIBXML_VERSION);
result = XCALLOC (1, struct locating_rule_list_ty);
return result;
}
void
locating_rule_list_destroy (struct locating_rule_list_ty *rules)
{
while (rules->nitems-- > 0)
locating_rule_destroy (&rules->items[rules->nitems]);
free (rules->items);
}
void
locating_rule_list_free (struct locating_rule_list_ty *rules)
{
if (rules != NULL)
locating_rule_list_destroy (rules);
free (rules);
}
+50
View File
@@ -0,0 +1,50 @@
/* XML resource locating rules
Copyright (C) 2015 Free Software Foundation, Inc.
This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#ifndef _LOCATING_RULE_H
#define _LOCATING_RULE_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct locating_rule_list_ty locating_rule_list_ty;
/* Creates a fresh locating_rule_list_ty. */
extern struct locating_rule_list_ty *locating_rule_list_alloc (void);
extern bool
locating_rule_list_add_from_directory (locating_rule_list_ty *rules,
const char *directory);
/* Determines the location of resource associated with FILENAME,
accoding to the loaded locating rules. */
extern const char *locating_rule_list_locate (locating_rule_list_ty *rules,
const char *filename,
const char *name);
/* Releases memory allocated for RULES. */
extern void locating_rule_list_free (locating_rule_list_ty *rules);
#ifdef __cplusplus
}
#endif
#endif /* _LOCATING_RULE_H */
+204 -31
View File
@@ -29,6 +29,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/stat.h>
#include <locale.h>
#include <limits.h>
@@ -71,6 +72,8 @@
#include "propername.h"
#include "sentence.h"
#include "unistr.h"
#include "its.h"
#include "locating-rule.h"
#include "gettext.h"
/* A convenience macro. I don't like writing gettext() every time. */
@@ -105,6 +108,10 @@
#include "x-desktop.h"
#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
#define ENDOF(a) ((a) + SIZEOF(a))
/* If nonzero add all comments immediately preceding one of the keywords. */
static bool add_all_comments = false;
@@ -205,6 +212,17 @@ const char *xgettext_current_source_encoding;
iconv_t xgettext_current_source_iconv;
#endif
static locating_rule_list_ty *its_locating_rules;
#define ITS_ROOT_UNTRANSLATABLE \
"<its:rules xmlns:its=\"http://www.w3.org/2005/11/its\"" \
" version=\"2.0\">" \
" <its:translateRule selector=\"/*\" translate=\"no\"/>" \
"</its:rules>"
/* If nonzero add comments used by itstool. */
static bool add_itstool_comments = false;
/* Long options. */
static const struct option long_options[] =
{
@@ -228,6 +246,7 @@ static const struct option long_options[] =
{ "from-code", required_argument, NULL, CHAR_MAX + 3 },
{ "help", no_argument, NULL, 'h' },
{ "indent", no_argument, NULL, 'i' },
{ "itstool", no_argument, NULL, CHAR_MAX + 19 },
{ "join-existing", no_argument, NULL, 'j' },
{ "kde", no_argument, NULL, CHAR_MAX + 10 },
{ "keyword", optional_argument, NULL, 'k' },
@@ -288,6 +307,9 @@ static void usage (int status)
static void read_exclusion_file (char *file_name);
static void extract_from_file (const char *file_name, extractor_ty extractor,
msgdomain_list_ty *mdlp);
static void extract_from_xml_file (const char *file_name,
its_rule_list_ty *rules,
msgdomain_list_ty *mdlp);
static message_ty *construct_header (void);
static void finalize_header (msgdomain_list_ty *mdlp);
static extractor_ty language_to_extractor (const char *name);
@@ -306,6 +328,7 @@ main (int argc, char *argv[])
bool some_additional_keywords = false;
bool sort_by_msgid = false;
bool sort_by_filepos = false;
char *its_dirs[2] = { NULL, NULL };
const char *file_name;
const char *files_from = NULL;
string_list_ty *file_list;
@@ -378,7 +401,6 @@ main (int argc, char *argv[])
x_tcl_extract_all ();
x_perl_extract_all ();
x_php_extract_all ();
x_glade_extract_all ();
x_lua_extract_all ();
x_javascript_extract_all ();
x_vala_extract_all ();
@@ -458,7 +480,6 @@ main (int argc, char *argv[])
x_tcl_keyword (optarg);
x_perl_keyword (optarg);
x_php_keyword (optarg);
x_glade_keyword (optarg);
x_lua_keyword (optarg);
x_javascript_keyword (optarg);
x_vala_keyword (optarg);
@@ -634,6 +655,10 @@ main (int argc, char *argv[])
error (EXIT_FAILURE, 0, _("sentence end type '%s' unknown"), optarg);
break;
case CHAR_MAX + 19: /* --itstool */
add_itstool_comments = true;
break;
default:
usage (EXIT_FAILURE);
/* NOTREACHED */
@@ -694,6 +719,30 @@ xgettext cannot work without keywords to look for"));
usage (EXIT_FAILURE);
}
{
const char *gettextdatadir;
char *versioned_gettextdatadir;
/* 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]);
}
/* Determine extractor from language. */
if (language != NULL)
extractor = language_to_extractor (language);
@@ -792,6 +841,7 @@ This version was built without iconv()."),
{
const char *filename;
extractor_ty this_file_extractor;
its_rule_list_ty *its_rules = NULL;
filename = file_list->item[i];
@@ -799,11 +849,9 @@ This version was built without iconv()."),
this_file_extractor = extractor;
else
{
const char *language_from_extension = NULL;
const char *base;
char *reduced;
const char *extension;
const char *language;
const char *p;
base = strrchr (filename, '/');
if (!base)
@@ -815,39 +863,103 @@ This version was built without iconv()."),
&& memcmp (reduced + strlen (reduced) - 3, ".in", 3) == 0)
reduced[strlen (reduced) - 3] = '\0';
/* Work out what the file extension is. */
language = NULL;
p = reduced + strlen (reduced);
for (; p > reduced && language == NULL; p--)
/* If no language is specified with -L, deduce it the extension. */
if (language == NULL)
{
if (*p == '.')
{
extension = p + 1;
const char *p;
/* Derive the language from the extension, and the extractor
function from the language. */
language = extension_to_language (extension);
/* Work out what the file extension is. */
p = reduced + strlen (reduced);
for (; p > reduced && language_from_extension == NULL; p--)
{
if (*p == '.')
{
const char *extension = p + 1;
/* Derive the language from the extension, and
the extractor function from the language. */
language_from_extension =
extension_to_language (extension);
}
}
}
if (language == NULL)
/* If language is not determined from the file name
extension, check ITS locating rules. */
if (language_from_extension == NULL
&& strcmp (filename, "-") != 0)
{
extension = strrchr (reduced, '.');
if (extension == NULL)
extension = "";
else
extension++;
error (0, 0, _("\
warning: file '%s' extension '%s' is unknown; will try C"), filename, extension);
language = "C";
const char *its_basename;
its_basename = locating_rule_list_locate (its_locating_rules,
filename,
language);
if (its_basename != NULL)
{
size_t j;
its_rules = its_rule_list_alloc ();
/* If the ITS file is identified by the name,
set the root element untranslatable. */
if (language != NULL)
its_rule_list_add_from_string (its_rules,
ITS_ROOT_UNTRANSLATABLE);
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 (its_rules,
its_filename);
free (its_filename);
if (ok)
break;
}
if (j == SIZEOF (its_dirs))
{
its_rule_list_free (its_rules);
its_rules = NULL;
}
}
}
if (its_rules == NULL)
{
if (language_from_extension == NULL)
{
const char *extension = strrchr (reduced, '.');
if (extension == NULL)
extension = "";
else
extension++;
error (0, 0, _("\
warning: file '%s' extension '%s' is unknown; will try C"), filename, extension);
language_from_extension = "C";
}
this_file_extractor =
language_to_extractor (language_from_extension);
}
this_file_extractor = language_to_extractor (language);
free (reduced);
}
/* Extract the strings from the file. */
extract_from_file (filename, this_file_extractor, mdlp);
if (its_rules != NULL)
{
/* Extract the strings from the file, using ITS. */
extract_from_xml_file (filename, its_rules, mdlp);
its_rule_list_free (its_rules);
}
else
/* Extract the strings from the file. */
extract_from_file (filename, this_file_extractor, mdlp);
}
string_list_free (file_list);
@@ -889,6 +1001,12 @@ warning: file '%s' extension '%s' is unknown; will try C"), filename, extension)
/* Write the PO file. */
msgdomain_list_print (mdlp, file_name, output_syntax, force_po, do_debug);
if (its_locating_rules)
locating_rule_list_free (its_locating_rules);
for (i = 0; i < SIZEOF (its_dirs); i++)
free (its_dirs[i]);
exit (EXIT_SUCCESS);
}
@@ -1051,6 +1169,8 @@ Output details:\n"));
printf (_("\
--stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
printf (_("\
--itstool write out itstool comments\n"));
printf (_("\
-w, --width=NUMBER set output page width\n"));
printf (_("\
--no-wrap do not break long message lines, longer than\n\
@@ -2112,6 +2232,63 @@ extract_from_file (const char *file_name, extractor_ty extractor,
free (real_file_name);
}
static message_ty *
xgettext_its_extract_callback (message_list_ty *mlp,
const char *msgctxt,
const char *msgid,
lex_pos_ty *pos,
const char *extracted_comment,
const char *marker,
enum its_whitespace_type_ty whitespace)
{
message_ty *message;
message = remember_a_message (mlp,
msgctxt == NULL ? NULL : xstrdup (msgctxt),
xstrdup (msgid),
null_context, pos,
extracted_comment, NULL);
if (add_itstool_comments)
{
char *dot = xasprintf ("(itstool) path: %s", marker);
message_comment_dot_append (message, dot);
free (dot);
if (whitespace == ITS_WHITESPACE_PRESERVE)
message->do_wrap = no;
}
return message;
}
static void
extract_from_xml_file (const char *file_name,
its_rule_list_ty *rules,
msgdomain_list_ty *mdlp)
{
char *logical_file_name;
char *real_file_name;
FILE *fp = xgettext_open (file_name, &logical_file_name, &real_file_name);
/* Set the default for the source file encoding. May be overridden by
the extractor function. */
xgettext_current_source_encoding = xgettext_global_source_encoding;
#if HAVE_ICONV
xgettext_current_source_iconv = xgettext_global_source_iconv;
#endif
its_rule_list_extract (rules, fp, real_file_name, logical_file_name,
NULL,
mdlp,
xgettext_its_extract_callback);
if (fp != stdin)
fclose (fp);
free (logical_file_name);
free (real_file_name);
}
/* Error message about non-ASCII character in a specific lexical context. */
@@ -3663,10 +3840,6 @@ finalize_header (msgdomain_list_ty *mdlp)
}
#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
#define ENDOF(a) ((a) + SIZEOF(a))
static extractor_ty
language_to_extractor (const char *name)
{
+1
View File
@@ -113,6 +113,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
xgettext-vala-1 \
xgettext-gsettings-1 \
xgettext-desktop-1 \
xgettext-its-1 \
format-awk-1 format-awk-2 \
format-boost-1 format-boost-2 \
format-c-1 format-c-2 format-c-3 format-c-4 format-c-5 \
+4
View File
@@ -1,6 +1,10 @@
# Variable needed by LTLIBINTL.
top_builddir=../..
# Variable needed by xgettext.
GETTEXTDATADIR="$abs_top_srcdir"
export GETTEXTDATADIR
OBJEXT="@OBJEXT@"
EXEEXT="@EXEEXT@"
CC="@CC@"
+255
View File
@@ -0,0 +1,255 @@
#!/bin/sh
. "${srcdir=.}/init.sh"; path_prepend_ . ../src
# Test of ITS support.
: ${XGETTEXT=xgettext}
GETTEXTDATADIR=.
export GETTEXTDATADIR
cat <<\EOF > empty.xml
<?xml version="1.0"?>
<empty></empty>
EOF
${XGETTEXT} --itstool -o empty.pot empty.xml 2>empty.err || { cat empty.err; exit 1; }
test -d its || mkdir its
cat <<\EOF > its/empty-1.loc
<?xml version="1.0"?>
<locatingRules/>
EOF
${XGETTEXT} --itstool -o empty.pot empty.xml 2>empty.err || { cat empty.err; exit 1; }
cat <<\EOF > its/empty-2.loc
<?xml version="1.0"?>
<locatingRules>
<locatingRule pattern="*.xml">
<documentRule prefix="" localName="empty" target="empty.its"/>
</locatingRule>
</locatingRules>
EOF
${XGETTEXT} --itstool -o empty.pot empty.xml 2>empty.err || { cat empty.err; exit 1; }
cat <<\EOF > its/empty.its
<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its" version="1.0">
</its:rules>
EOF
${XGETTEXT} --itstool -o empty.pot empty.xml 2>empty.err || { cat empty.err; exit 1; }
cat <<\EOF > its/messages.loc
<?xml version="1.0"?>
<locatingRules>
<locatingRule pattern="*.xml">
<documentRule localName="messages" target="messages.its"/>
</locatingRule>
<locatingRule pattern="*.msg">
<documentRule localName="messages" target="messages.its"/>
</locatingRule>
</locatingRules>
EOF
cat <<\EOF > its/messages.its
<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its"
xmlns:gt="https://www.gnu.org/s/gettext/ns/its/extensions/1.0"
xmlns:msg="http://www.gnu.org/s/gettext/ns/messages/1.0"
version="1.0">
<!-- Invalid: no selector -->
<its:translateRule translate="yes"/>
<!-- Invalid: no translate -->
<its:translateRule selector="/"/>
<its:translateRule selector="//msg:message/@comment" translate="yes"/>
<its:translateRule selector="//msg:note" translate="no"/>
<its:translateRule selector="//msg:p[@translatable = 'no']"
translate="no"/>
<!-- Invalid: no selector -->
<its:locNoteRule locNoteType="alert"/>
<!-- Invalid: no locNoteType -->
<its:locNoteRule selector="/"/>
<its:locNoteRule selector="//msg:message/*" locNoteType="alert"
locNotePointer="../msg:note"/>
<its:locNoteRule selector="//msg:code" locNoteType="alert">
<its:locNote>This is code</its:locNote>
</its:locNoteRule>
<its:locNoteRule selector="//msg:message/@comment" locNoteType="alert">
<its:locNote>This is a comment</its:locNote>
</its:locNoteRule>
<!-- Invalid: no selector -->
<its:withinTextRule withinText="yes"/>
<!-- Invalid: no withinText -->
<its:withinTextRule selector="/"/>
<its:withinTextRule selector="//msg:span | //msg:link" withinText="yes"/>
<!-- Invalid: no selector -->
<its:preserveSpaceRule space="preserve"/>
<!-- Invalid: no space -->
<its:preserveSpaceRule selector="/"/>
<its:preserveSpaceRule selector="//msg:code" space="preserve"/>
<gt:contextRule selector="//msg:p[@context = 'yes']"
contextPointer="substring-before(., '|')"
textPointer="substring-after(., '|')"/>
</its:rules>
EOF
cat <<\EOF >messages.xml
<?xml version="1.0"?>
<!DOCTYPE messages PUBLIC "" "" [
<!ENTITY foo "bar">
]>
<messages xmlns="http://www.gnu.org/s/gettext/ns/messages/1.0"
xmlns:its="http://www.w3.org/2005/11/its">
<message>
<p>This is a test message &foo;&gt;&lt;&amp;&quot;"</p>
</message>
<message>
<p its:translate="no">This is a non-translatable message</p>
</message>
<message>
<p>This is a test message, with an <span>element</span> in a <link href="http://www.gnu.org/s/gettext">text</link></p>
</message>
<message>
<code> $ echo ' ' &gt;&gt; /dev/null
$ cat &lt; /dev/yes
$ sleep 10 &amp;
</code>
</message>
<message comment="This is a comment &lt;&gt;&amp;&quot;">
<p>This is a test message, with an attribute</p>
</message>
<message>
<note>
This is a localization note
</note>
<p>This is a test message, with a localization note</p>
</message>
<message>
<p its:locNote="This is a local localization note" its:locNoteType="alert">
This is a test message, with a local localization note
</p>
</message>
<message>
<!-- empty element, which shouldn't be extracted -->
<p></p>
</message>
<message>
<p xml:space="preserve"> This is a message with space preserved</p>
</message>
<message>
<p translatable="no">This is a non-translatable string</p>
</message>
<message its:translate="no">
<p>This is a non-translatable string</p>
</message>
<message>
<!-- This is a comment -->
<p context="yes">context|A translatable string with a context prefixed</p>
</message>
<message>
<p xml:space="trim"> Leading/trailing whitespaces are removed,
but not middle
</p>
</message>
</messages>
EOF
cat <<\EOF >messages.ok
#. (itstool) path: message/p
#: messages.xml:8
msgid "This is a test message &foo;&gt;&lt;&amp;\"\""
msgstr ""
#. (itstool) path: message/p
#: messages.xml:14
msgid "This is a test message, with an <span>element</span> in a <link href=\"http://www.gnu.org/s/gettext\">text</link>"
msgstr ""
#. This is code
#. (itstool) path: message/code
#: messages.xml:17
#, no-wrap
msgid ""
" $ echo ' ' &gt;&gt; /dev/null\n"
" $ cat &lt; /dev/yes\n"
" $ sleep 10 &amp;\n"
msgstr ""
#. This is a comment
#. (itstool) path: messages/message@comment
#: messages.xml:22
msgid "This is a comment &lt;&gt;&amp;&quot;"
msgstr ""
#. (itstool) path: message/p
#: messages.xml:23
msgid "This is a test message, with an attribute"
msgstr ""
#. This is a localization note
#. (itstool) path: message/p
#: messages.xml:29
msgid "This is a test message, with a localization note"
msgstr ""
#. This is a local localization note
#. (itstool) path: message/p
#: messages.xml:32
msgid "This is a test message, with a local localization note"
msgstr ""
#. (itstool) path: message/p
#: messages.xml:41
#, no-wrap
msgid " This is a message with space preserved"
msgstr ""
#. This is a comment
#. (itstool) path: message/p
#: messages.xml:51
msgctxt "context"
msgid "A translatable string with a context prefixed"
msgstr ""
#. (itstool) path: message/p
#: messages.xml:54
msgid ""
"Leading/trailing whitespaces are removed,\n"
" but not middle"
msgstr ""
EOF
: ${DIFF=diff}
${XGETTEXT} --itstool --no-wrap --omit-header -o messages.pot messages.xml 2>messages.err || { cat messages.err; exit 1; }
${DIFF} messages.ok messages.pot
result=$?
test $result = 0 || exit $result
# Check if locating rules can work with --directory, and extra ".in"
# file name extension.
test -d data || mkdir data
test -d po || mkdir po
cp messages.xml data/messages.msg.in
cd po
GETTEXTDATADIR=..
export GETTEXTDATADIR
${XGETTEXT} --itstool --no-wrap --omit-header --directory=.. -o messages.pot.in data/messages.msg.in 2>messages.err || { cat messages.err; exit 1; }
sed -e 's!^#: data/messages.msg.in!#: messages.xml!' \
< messages.pot.in > messages.pot
${DIFF} ../messages.ok messages.pot
result=$?
test $result = 0 || exit $result