bug-gettext
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[bug-gettext] [PATCH 3/3] Support GSettings schema file


From: Daiki Ueno
Subject: [bug-gettext] [PATCH 3/3] Support GSettings schema file
Date: Mon, 5 Aug 2013 22:29:58 +0200

---
 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 <address@hidden>, 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 <address@hidden>, 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 <address@hidden>, 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 <address@hidden>\n"
+"Language-Team: LANGUAGE <address@hidden>\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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]