--- gettext-tools/src/Makefile.am | 5 +- gettext-tools/src/x-gsettings.c | 384 +++++++++++++++++++++++++++++++ gettext-tools/src/x-gsettings.h | 45 ++++ gettext-tools/src/xgettext.c | 3 + gettext-tools/tests/Makefile.am | 1 + gettext-tools/tests/xgettext-gsettings-1 | 90 ++++++++ 6 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 gettext-tools/src/x-gsettings.c create mode 100644 gettext-tools/src/x-gsettings.h create mode 100644 gettext-tools/tests/xgettext-gsettings-1
diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am index 6a12287..e6713af 100644 --- a/gettext-tools/src/Makefile.am +++ b/gettext-tools/src/Makefile.am @@ -52,7 +52,7 @@ 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 \ x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-lua.h \ -x-javascript.h x-vala.h libexpat-compat.h +x-javascript.h x-vala.h x-gsettings.h libexpat-compat.h EXTRA_DIST += FILES project-id ChangeLog.0 @@ -177,7 +177,8 @@ endif xgettext_SOURCES += \ x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c \ x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ - x-rst.c x-glade.c x-lua.c x-javascript.c x-vala.c libexpat-compat.c + x-rst.c x-glade.c x-lua.c x-javascript.c x-vala.c x-gsettings.c \ + libexpat-compat.c if !WOE32DLL msgattrib_SOURCES = msgattrib.c else diff --git a/gettext-tools/src/x-gsettings.c b/gettext-tools/src/x-gsettings.c new file mode 100644 index 0000000..5b2d4d7 --- /dev/null +++ b/gettext-tools/src/x-gsettings.c @@ -0,0 +1,384 @@ +/* xgettext GSettings schema file backend. + Copyright (C) 2002-2003, 2005-2009, 2013 Free Software Foundation, Inc. + + This file was written by Daiki Ueno <[email protected]>, 2013. + + 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 "x-gsettings.h" + +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "basename.h" +#include "progname.h" +#include "xalloc.h" +#include "hash.h" +#include "po-charset.h" +#include "gettext.h" +#include "libexpat-compat.h" + +#define _(s) gettext(s) + + +/* GSettings schema file is an XML based format. + The syntax is defined in glib/gio/gschema.dtd and: + https://developer.gnome.org/gio/unstable/GSettings.html */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + + +/* ============================= XML parsing. ============================= */ + +#if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT + +/* Accumulator for the extracted messages. */ +static message_list_ty *mlp; + +/* Logical filename, used to label the extracted messages. */ +static char *logical_file_name; + +/* XML parser. */ +static XML_Parser parser; + +enum whitespace_type_ty +{ + none, + normalize, + strip +}; +typedef enum whitespace_type_ty whitespace_type_ty; + +struct element_state +{ + bool extract_string; + whitespace_type_ty whitespace; + char *extracted_context; + int lineno; + char *buffer; + size_t bufmax; + size_t buflen; +}; +static struct element_state *stack; +static size_t stack_size; + +static char * +normalize_whitespace (const char *text, whitespace_type_ty whitespace) +{ + if (whitespace == none) + return xstrdup (text); + else + { + char *result, *p; + + /* Strip whitespaces at the beginning/end of the text. */ + result = xstrdup (text + strspn (text, " \t\n")); + for (p = result + strlen (result); + p > result && (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n'); + p--) + ; + if (p > result) + *++p = '\0'; + + /* Normalize whitespaces within the text. */ + if (whitespace == normalize) + { + char *end = result + strlen (result); + for (p = result; *p != '\0';) + { + size_t len = strspn (p, " \t\n"); + if (len > 0) + { + *p = ' '; + memmove (p + 1, p + len, end - (p + len)); + end -= len - 1; + *end = '\0'; + p++; + } + p += strcspn (p, " \t\n"); + } + } + return result; + } +} + +/* Ensures stack_size >= size. */ +static void +ensure_stack_size (size_t size) +{ + if (size > stack_size) + { + stack_size = 2 * stack_size; + if (stack_size < size) + stack_size = size; + stack = + (struct element_state *) + xrealloc (stack, stack_size * sizeof (struct element_state)); + } +} + +static size_t stack_depth; + +/* Callback called when <element> is seen. */ +static void +start_element_handler (void *userData, const char *name, + const char **attributes) +{ + struct element_state *p; + + /* Increase stack depth. */ + stack_depth++; + ensure_stack_size (stack_depth + 1); + + /* Don't extract a string for the containing element. */ + stack[stack_depth - 1].extract_string = false; + + p = &stack[stack_depth]; + p->extract_string = extract_all; + p->extracted_context = NULL; + + if (!p->extract_string) + { + bool has_translatable = false; + whitespace_type_ty whitespace = none; + const char *extracted_context = NULL; + if (strcmp (name, "summary") == 0 || strcmp (name, "description") == 0) + { + has_translatable = true; + whitespace = normalize; + } + else if (strcmp (name, "default") == 0) + { + const char *extracted_l10n = NULL; + const char **attp = attributes; + while (*attp != NULL) + { + if (strcmp (attp[0], "context") == 0) + extracted_context = attp[1]; + else if (strcmp (attp[0], "l10n") == 0) + extracted_l10n = attp[1]; + attp += 2; + } + if (extracted_l10n != NULL) + { + has_translatable = true; + whitespace = strip; + } + } + p->extract_string = has_translatable; + p->whitespace = whitespace; + p->extracted_context = + (has_translatable && extracted_context != NULL + ? xstrdup (extracted_context) + : NULL); + } + + p->lineno = XML_GetCurrentLineNumber (parser); + p->buffer = NULL; + p->bufmax = 0; + p->buflen = 0; + if (!p->extract_string) + savable_comment_reset (); +} + +/* Callback called when </element> is seen. */ +static void +end_element_handler (void *userData, const char *name) +{ + struct element_state *p = &stack[stack_depth]; + + /* Actually extract string. */ + if (p->extract_string) + { + /* Don't extract the empty string. */ + if (p->buflen > 0) + { + lex_pos_ty pos; + + if (p->buflen == p->bufmax) + p->buffer = (char *) xrealloc (p->buffer, p->buflen + 1); + p->buffer[p->buflen] = '\0'; + + pos.file_name = logical_file_name; + pos.line_number = p->lineno; + + if (p->buffer != NULL) + { + remember_a_message (mlp, p->extracted_context, + normalize_whitespace (p->buffer, + p->whitespace), + null_context, &pos, + NULL, savable_comment); + p->extracted_context = NULL; + } + } + } + + /* Free memory for this stack level. */ + if (p->extracted_context != NULL) + free (p->extracted_context); + if (p->buffer != NULL) + free (p->buffer); + + /* Decrease stack depth. */ + stack_depth--; + + savable_comment_reset (); +} + +/* Callback called when some text is seen. */ +static void +character_data_handler (void *userData, const char *s, int len) +{ + struct element_state *p = &stack[stack_depth]; + + /* Accumulate character data. */ + if (len > 0) + { + if (p->buflen + len > p->bufmax) + { + p->bufmax = 2 * p->bufmax; + if (p->bufmax < p->buflen + len) + p->bufmax = p->buflen + len; + p->buffer = (char *) xrealloc (p->buffer, p->bufmax); + } + memcpy (p->buffer + p->buflen, s, len); + p->buflen += len; + } +} + +/* Callback called when some comment text is seen. */ +static void +comment_handler (void *userData, const char *data) +{ + /* Split multiline comment into lines, and remove leading and trailing + whitespace. */ + char *copy = xstrdup (data); + char *p; + char *q; + + for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1) + { + while (p[0] == ' ' || p[0] == '\t') + p++; + while (q > p && (q[-1] == ' ' || q[-1] == '\t')) + q--; + *q = '\0'; + savable_comment_add (p); + } + q = p + strlen (p); + while (p[0] == ' ' || p[0] == '\t') + p++; + while (q > p && (q[-1] == ' ' || q[-1] == '\t')) + q--; + *q = '\0'; + savable_comment_add (p); + free (copy); +} + + +static void +do_extract_gsettings (FILE *fp, + const char *real_filename, const char *logical_filename, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + /* expat feeds us strings in UTF-8 encoding. */ + xgettext_current_source_encoding = po_charset_utf8; + + logical_file_name = xstrdup (logical_filename); + + parser = XML_ParserCreate (NULL); + if (parser == NULL) + error (EXIT_FAILURE, 0, _("memory exhausted")); + + XML_SetElementHandler (parser, start_element_handler, end_element_handler); + XML_SetCharacterDataHandler (parser, character_data_handler); + XML_SetCommentHandler (parser, comment_handler); + + stack_depth = 0; + + while (!feof (fp)) + { + char buf[4096]; + int count = fread (buf, 1, sizeof buf, fp); + + if (count == 0) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_filename); + /* EOF reached. */ + break; + } + + if (XML_Parse (parser, buf, count, 0) == 0) + error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, + (unsigned long) XML_GetCurrentLineNumber (parser), + (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, + XML_ErrorString (XML_GetErrorCode (parser))); + } + + if (XML_Parse (parser, NULL, 0, 1) == 0) + error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, + (unsigned long) XML_GetCurrentLineNumber (parser), + (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, + XML_ErrorString (XML_GetErrorCode (parser))); + + XML_ParserFree (parser); + + /* Close scanner. */ + logical_file_name = NULL; + parser = NULL; +} + +#endif + +void +extract_gsettings (FILE *fp, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + if (LIBEXPAT_AVAILABLE ()) + do_extract_gsettings (fp, real_filename, logical_filename, mdlp); + else + { + multiline_error (xstrdup (""), + xasprintf (_("\ +Language \"gsettings\" is not supported. %s relies on expat.\n\ +This version was built without expat.\n"), + basename (program_name))); + exit (EXIT_FAILURE); + } +} diff --git a/gettext-tools/src/x-gsettings.h b/gettext-tools/src/x-gsettings.h new file mode 100644 index 0000000..9575c18 --- /dev/null +++ b/gettext-tools/src/x-gsettings.h @@ -0,0 +1,45 @@ +/* xgettext GSettings schema file backend. + Copyright (C) 2002-2003, 2006, 2013 Free Software Foundation, Inc. + Written by Daiki Ueno <[email protected]>, 2013. + + 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/>. */ + + +#include <stdio.h> + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_GSETTINGS \ + { "gschema.xml", "gsettings" }, \ + +#define SCANNERS_GSETTINGS \ + { "gsettings", extract_gsettings, NULL, NULL, NULL }, \ + +/* Scan a gsettings XML file and add its translatable strings to mdlp. */ +extern void extract_gsettings (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index bb77ea4..de4686e 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -95,6 +95,7 @@ #include "x-lua.h" #include "x-javascript.h" #include "x-vala.h" +#include "x-gsettings.h" /* If nonzero add all comments immediately preceding one of the keywords. */ @@ -3231,6 +3232,7 @@ language_to_extractor (const char *name) SCANNERS_LUA SCANNERS_JAVASCRIPT SCANNERS_VALA + SCANNERS_GSETTINGS /* Here may follow more languages and their scanners: pike, etc... Make sure new scanners honor the --exclude-file option. */ }; @@ -3317,6 +3319,7 @@ extension_to_language (const char *extension) EXTENSIONS_LUA EXTENSIONS_JAVASCRIPT EXTENSIONS_VALA + EXTENSIONS_GSETTINGS /* Here may follow more file extensions... */ }; diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index 90d0b47..218edf8 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -104,6 +104,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \ xgettext-javascript-4 xgettext-javascript-5 \ xgettext-vala-1 \ + xgettext-gsettings-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 \ diff --git a/gettext-tools/tests/xgettext-gsettings-1 b/gettext-tools/tests/xgettext-gsettings-1 new file mode 100644 index 0000000..b5d1005 --- /dev/null +++ b/gettext-tools/tests/xgettext-gsettings-1 @@ -0,0 +1,90 @@ +#!/bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test of GSettings support. + +tmpfiles="" +trap 'rm -fr $tmpfiles' 1 2 3 15 + +tmpfiles="$tmpfiles xg-gs-1-empty.gschema.xml" +cat <<EOF > xg-gs-1-empty.gschema.xml +<?xml version="1.0"?> +<schemalist/> +EOF + +tmpfiles="$tmpfiles xg-gs-1.pot" +: ${XGETTEXT=xgettext} +${XGETTEXT} -o xg-gs-1.pot xg-gs-1-empty.gschema.xml 2>/dev/null +test $? = 0 || { + echo "Skipping test: xgettext was built without GSettings support" + rm -fr $tmpfiles; exit 77 +} + +tmpfiles="$tmpfiles xg-gs-1.gschema.xml" +cat <<EOF > xg-gs-1.gschema.xml +<?xml version="1.0"?> +<schemalist> + <schema id="org.gnome.example" path="/org/gnome/example/"> + <key name="foo" type="i"> + <default>0</default> + <summary context="Foo">Example Integer Key</summary> + <description> + The example integer key to test that the default value is not + translated. + </description> + </key> + <key name="bar" type="s"> + <default l10n="messages"> + ' foo bar baz' + </default> + </key> + </schema> +</schemalist> +EOF + +tmpfiles="$tmpfiles xg-gs-1.pot" +: ${XGETTEXT=xgettext} +${XGETTEXT} --add-comments -o - xg-gs-1.gschema.xml | grep -v 'POT-Creation-Date' > xg-gs-1.pot +test $? = 0 || { rm -fr $tmpfiles; exit 1; } + +tmpfiles="$tmpfiles xg-gs-1.ok" +cat <<EOF > xg-gs-1.ok +# 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" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <[email protected]>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: xg-gs-1.gschema.xml:6 +msgid "Example Integer Key" +msgstr "" + +#: xg-gs-1.gschema.xml:7 +msgid "" +"The example integer key to test that the default value is not translated." +msgstr "" + +#: xg-gs-1.gschema.xml:13 +msgid "' foo bar baz'" +msgstr "" +EOF + +: ${DIFF=diff} +${DIFF} xg-gs-1.ok xg-gs-1.pot +result=$? + +rm -fr $tmpfiles + +exit $result -- 1.8.3.1
