[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[bug-gettext] [PATCHv2 1/2] xgettext: Add support for Desktop Entry file
From: |
Daiki Ueno |
Subject: |
[bug-gettext] [PATCHv2 1/2] xgettext: Add support for Desktop Entry files |
Date: |
Mon, 17 Mar 2014 18:38:41 +0900 |
The lexer is separated in read-desktop.[ch], so it can be shared with
the msgfmt command implementation.
---
gettext-tools/doc/xgettext.texi | 8 +-
gettext-tools/src/Makefile.am | 6 +-
gettext-tools/src/read-desktop.c | 651 +++++++++++++++++++++++++++++++++
gettext-tools/src/read-desktop.h | 123 +++++++
gettext-tools/src/x-desktop.c | 190 ++++++++++
gettext-tools/src/x-desktop.h | 47 +++
gettext-tools/src/xgettext.c | 8 +-
gettext-tools/tests/Makefile.am | 1 +
gettext-tools/tests/xgettext-desktop-1 | 64 ++++
9 files changed, 1092 insertions(+), 6 deletions(-)
create mode 100644 gettext-tools/src/read-desktop.c
create mode 100644 gettext-tools/src/read-desktop.h
create mode 100644 gettext-tools/src/x-desktop.c
create mode 100644 gettext-tools/src/x-desktop.h
create mode 100755 gettext-tools/tests/xgettext-desktop-1
diff --git a/gettext-tools/doc/xgettext.texi b/gettext-tools/doc/xgettext.texi
index 1089e9c..35f7ced 100644
--- a/gettext-tools/doc/xgettext.texi
+++ b/gettext-tools/doc/xgettext.texi
@@ -74,7 +74,7 @@ are @code{C}, @code{C++}, @code{ObjectiveC}, @code{PO},
@code{Shell},
@code{Smalltalk}, @code{Java}, @code{JavaProperties}, @code{C#}, @code{awk},
@code{YCP}, @code{Tcl}, @code{Perl}, @code{PHP}, @code{GCC-source},
@code{NXStringTable}, @code{RST}, @code{Glade}, @code{Lua}, @code{JavaScript},
address@hidden, @code{GSettings}.
address@hidden, @code{GSettings}, @code{Desktop}.
@item -C
@itemx --c++
@@ -181,7 +181,7 @@ escaped.
This option has an effect with most languages, namely C, C++, ObjectiveC,
Shell, Python, Lisp, EmacsLisp, librep, Java, C#, awk, Tcl, Perl, PHP,
-GCC-source, Glade, Lua, JavaScript, Vala, GSettings.
+GCC-source, Glade, Lua, JavaScript, Vala, GSettings, Desktop.
The default keyword specifications, which are always looked for if not
explicitly disabled, are language dependent. They are:
@@ -261,6 +261,10 @@ For JavaScript: @code{_}, @code{gettext},
@code{dgettext:2},
For Vala: @code{_}, @code{Q_}, @code{N_}, @code{NC_}, @code{dgettext:2},
@code{dcgettext:2}, @code{ngettext:1,2}, @code{dngettext:2,3},
@code{dpgettext:2c,3}, @code{dpgettext2:2c,3}.
+
address@hidden
+For Desktop: @code{Name}, @code{GenericName}, @code{Comment},
address@hidden, @code{Keywords}.
@end itemize
To disable the default keyword specifications, the option @samp{-k} or
diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am
index e6713af..e69c00c 100644
--- a/gettext-tools/src/Makefile.am
+++ b/gettext-tools/src/Makefile.am
@@ -148,7 +148,8 @@ color.c write-catalog.c write-properties.c
write-stringtable.c write-po.c \
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 \
-$(FORMAT_SOURCE)
+$(FORMAT_SOURCE) \
+read-desktop.c
# msggrep needs pattern matching.
LIBGREP = ../libgrep/libgrep.a
@@ -178,7 +179,8 @@ 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 x-gsettings.c \
- libexpat-compat.c
+ libexpat-compat.c \
+ x-desktop.c
if !WOE32DLL
msgattrib_SOURCES = msgattrib.c
else
diff --git a/gettext-tools/src/read-desktop.c b/gettext-tools/src/read-desktop.c
new file mode 100644
index 0000000..4aad936
--- /dev/null
+++ b/gettext-tools/src/read-desktop.c
@@ -0,0 +1,651 @@
+/* Reading Desktop Entry files.
+ Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014 Free
Software Foundation, Inc.
+ This file was written by Daiki Ueno <address@hidden>.
+
+ 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 "read-desktop.h"
+
+#include "xalloc.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "error.h"
+#include "error-progname.h"
+#include "xalloc.h"
+#include "xvasprintf.h"
+#include "c-ctype.h"
+#include "po-lex.h"
+#include "po-xerror.h"
+#include "gettext.h"
+
+#define _(str) gettext (str)
+
+/* The syntax of a Desktop Entry file is defined at
+ http://standards.freedesktop.org/desktop-entry-spec/latest/index.html. */
+
+desktop_reader_ty *
+desktop_reader_alloc (desktop_reader_class_ty *method_table)
+{
+ desktop_reader_ty *reader;
+
+ reader = (desktop_reader_ty *) xmalloc (method_table->size);
+ reader->methods = method_table;
+ if (method_table->constructor)
+ method_table->constructor (reader);
+ return reader;
+}
+
+void
+desktop_reader_free (desktop_reader_ty *reader)
+{
+ if (reader->methods->destructor)
+ reader->methods->destructor (reader);
+ free (reader);
+}
+
+void
+desktop_reader_handle_group (desktop_reader_ty *reader, const char *group)
+{
+ if (reader->methods->handle_group)
+ reader->methods->handle_group (reader, group);
+}
+
+void
+desktop_reader_handle_pair (desktop_reader_ty *reader,
+ lex_pos_ty *key_pos,
+ const char *key,
+ const char *locale,
+ const char *value)
+{
+ if (reader->methods->handle_pair)
+ reader->methods->handle_pair (reader, key_pos, key, locale, value);
+}
+
+void
+desktop_reader_handle_comment (desktop_reader_ty *reader, const char *s)
+{
+ if (reader->methods->handle_comment)
+ reader->methods->handle_comment (reader, s);
+}
+
+void
+desktop_reader_handle_text (desktop_reader_ty *reader, const char *s)
+{
+ if (reader->methods->handle_text)
+ reader->methods->handle_text (reader, s);
+}
+
+/* Real filename, used in error messages about the input file. */
+static const char *real_file_name;
+
+/* File name and line number. */
+extern lex_pos_ty gram_pos;
+
+/* The input file stream. */
+static FILE *fp;
+
+
+static int
+phase1_getc ()
+{
+ int c;
+
+ c = getc (fp);
+
+ if (c == EOF)
+ {
+ if (ferror (fp))
+ {
+ const char *errno_description = strerror (errno);
+ po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
+ xasprintf ("%s: %s",
+ xasprintf (_("error while reading \"%s\""),
+ real_file_name),
+ errno_description));
+ }
+ return EOF;
+ }
+
+ return c;
+}
+
+static inline void
+phase1_ungetc (int c)
+{
+ if (c != EOF)
+ ungetc (c, fp);
+}
+
+
+static unsigned char phase2_pushback[2];
+static int phase2_pushback_length;
+
+static int
+phase2_getc ()
+{
+ int c;
+
+ if (phase2_pushback_length)
+ c = phase2_pushback[--phase2_pushback_length];
+ else
+ {
+ c = phase1_getc ();
+
+ if (c == '\r')
+ {
+ int c2 = phase1_getc ();
+ if (c2 == '\n')
+ c = c2;
+ else
+ phase1_ungetc (c2);
+ }
+ }
+
+ if (c == '\n')
+ gram_pos.line_number++;
+
+ return c;
+}
+
+static void
+phase2_ungetc (int c)
+{
+ if (c == '\n')
+ --gram_pos.line_number;
+ if (c != EOF)
+ phase2_pushback[phase2_pushback_length++] = c;
+}
+
+static char *
+read_until_newline (void)
+{
+ char *buffer = NULL;
+ size_t bufmax = 0;
+ size_t buflen;
+
+ buflen = 0;
+ for (;;)
+ {
+ int c;
+
+ c = phase2_getc ();
+
+ if (buflen >= bufmax)
+ {
+ bufmax += 100;
+ buffer = xrealloc (buffer, bufmax);
+ }
+
+ if (c == EOF || c == '\n')
+ break;
+
+ buffer[buflen++] = c;
+ }
+ buffer[buflen] = '\0';
+ return buffer;
+}
+
+static char *
+read_group_name (void)
+{
+ char *buffer = NULL;
+ size_t bufmax = 0;
+ size_t buflen;
+
+ buflen = 0;
+ for (;;)
+ {
+ int c;
+
+ c = phase2_getc ();
+
+ if (buflen >= bufmax)
+ {
+ bufmax += 100;
+ buffer = xrealloc (buffer, bufmax);
+ }
+
+ if (c == EOF || c == '\n' || c == ']')
+ break;
+
+ buffer[buflen++] = c;
+ }
+ buffer[buflen] = '\0';
+ return buffer;
+}
+
+static char *
+read_key_name (const char **locale)
+{
+ char *buffer = NULL;
+ size_t bufmax = 0;
+ size_t buflen;
+ const char *locale_start = NULL;
+
+ buflen = 0;
+ for (;;)
+ {
+ int c;
+
+ c = phase2_getc ();
+
+ if (buflen >= bufmax)
+ {
+ bufmax += 100;
+ buffer = xrealloc (buffer, bufmax);
+ }
+
+ if (c == EOF || c == '\n')
+ break;
+
+ if (!locale_start)
+ {
+ if (c == '[')
+ {
+ buffer[buflen++] = '\0';
+ locale_start = &buffer[buflen];
+ continue;
+ }
+ else if (!c_isalnum (c) && c != '-')
+ {
+ phase2_ungetc (c);
+ break;
+ }
+ }
+ else
+ {
+ if (c == ']')
+ {
+ buffer[buflen++] = '\0';
+ break;
+ }
+ else if (!c_isascii (c))
+ {
+ phase2_ungetc (c);
+ break;
+ }
+ }
+
+ buffer[buflen++] = c;
+ }
+ buffer[buflen] = '\0';
+
+ if (locale_start)
+ *locale = locale_start;
+
+ return buffer;
+}
+
+void
+desktop_parse (desktop_reader_ty *reader, FILE *file,
+ const char *real_filename, const char *logical_filename)
+{
+ fp = file;
+ real_file_name = real_filename;
+ gram_pos.file_name = xstrdup (real_file_name);
+ gram_pos.line_number = 1;
+
+ for (;;)
+ {
+ int c;
+
+ c = phase2_getc ();
+
+ if (c == EOF)
+ break;
+
+ if (c == '[')
+ {
+ /* A group header. */
+ char *group_name;
+
+ group_name = read_group_name ();
+
+ do
+ c = phase2_getc ();
+ while (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f');
+
+ if (c == EOF)
+ break;
+
+ phase2_ungetc (c);
+
+ desktop_reader_handle_group (reader, group_name);
+ free (group_name);
+ }
+ else if (c == '#')
+ {
+ /* A comment line. */
+ char *comment;
+
+ comment = read_until_newline ();
+ desktop_reader_handle_comment (reader, comment);
+ free (comment);
+ }
+ else if (c_isalnum (c) || c == '-')
+ {
+ /* A key/value pair. */
+ char *key_name;
+ const char *locale;
+
+ phase2_ungetc (c);
+
+ locale = NULL;
+ key_name = read_key_name (&locale);
+ do
+ c = phase2_getc ();
+ while (c == ' ' || c == '\t' || c == '\r' || c == '\f');
+
+ if (c == EOF)
+ break;
+
+ if (c != '=')
+ {
+ po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
+ real_filename, gram_pos.line_number, 0, false,
+ xasprintf (_("missing '=' after \"%s\""), key_name));
+ }
+ else
+ {
+ char *value;
+
+ do
+ c = phase2_getc ();
+ while (c == ' ' || c == '\t' || c == '\r' || c == '\f');
+
+ if (c == EOF)
+ break;
+
+ phase2_ungetc (c);
+
+ value = read_until_newline ();
+ desktop_reader_handle_pair (reader, &gram_pos,
+ key_name, locale, value);
+ free (value);
+ }
+ free (key_name);
+ }
+ else
+ {
+ char *text;
+
+ phase2_ungetc (c);
+
+ text = read_until_newline ();
+ desktop_reader_handle_text (reader, text);
+ free (text);
+ }
+ }
+
+ fp = NULL;
+ real_file_name = NULL;
+ gram_pos.line_number = 0;
+}
+
+char *
+desktop_escape_string (const char *s)
+{
+ char *buffer, *p;
+
+ p = buffer = XNMALLOC (strlen (s) * 2 + 1, char);
+
+ /* The first character must not be a whitespace. */
+ if (*s == ' ')
+ {
+ p = stpcpy (p, "\\s");
+ s++;
+ }
+ else if (*s == '\t')
+ {
+ p = stpcpy (p, "\\t");
+ s++;
+ }
+
+ for (;; s++)
+ {
+ if (*s == '\0')
+ {
+ *p = '\0';
+ break;
+ }
+
+ switch (*s)
+ {
+ case '\n':
+ p = stpcpy (p, "\\n");
+ break;
+ case '\r':
+ p = stpcpy (p, "\\r");
+ break;
+ case '\\':
+ p = stpcpy (p, "\\\\");
+ break;
+ default:
+ *p++ = *s;
+ break;
+ }
+ }
+
+ return buffer;
+}
+
+char *
+desktop_unescape_string (const char *s)
+{
+ char *buffer, *p;
+
+ p = buffer = XNMALLOC (strlen (s) + 1, char);
+ for (;; s++)
+ {
+ if (*s == '\0')
+ {
+ *p = '\0';
+ break;
+ }
+
+ if (*s == '\\')
+ {
+ s++;
+
+ if (*s == '\0')
+ {
+ *p = '\0';
+ break;
+ }
+
+ switch (*s)
+ {
+ case 's':
+ *p++ = ' ';
+ break;
+ case 'n':
+ *p++ = '\n';
+ break;
+ case 't':
+ *p++ = '\t';
+ break;
+ case 'r':
+ *p++ = '\r';
+ break;
+ default:
+ *p++ = *s;
+ break;
+ }
+ }
+ else
+ *p++ = *s;
+ }
+ return buffer;
+}
+
+char *
+desktop_escape_string_list (string_list_ty *list)
+{
+ string_list_ty *new_list;
+ size_t i;
+ char *result;
+
+ new_list = string_list_alloc ();
+ for (i = 0; i < list->nitems; i++)
+ {
+ const char *s = list->item[i];
+ char *buffer, *p;
+
+ p = buffer = XNMALLOC (strlen (s) * 2 + 2, char);
+
+ /* The first character must not be a whitespace. */
+ if (i == 0)
+ {
+ if (*s == ' ')
+ {
+ p = stpcpy (p, "\\s");
+ s++;
+ }
+ else if (*s == '\t')
+ {
+ p = stpcpy (p, "\\t");
+ s++;
+ }
+ }
+
+ for (;; s++)
+ {
+ if (*s == '\0')
+ {
+ *p++ = ';';
+ *p = '\0';
+ break;
+ }
+
+ switch (*s)
+ {
+ case '\n':
+ p = stpcpy (p, "\\n");
+ break;
+ case '\r':
+ p = stpcpy (p, "\\r");
+ break;
+ case ';':
+ p = stpcpy (p, "\\;");
+ break;
+ case '\\':
+ p = stpcpy (p, "\\\\");
+ break;
+ default:
+ *p++ = *s;
+ break;
+ }
+ }
+
+ string_list_append (new_list, buffer);
+ free (buffer);
+ }
+
+ result = string_list_concat_destroy (new_list);
+ free (new_list);
+ return result;
+}
+
+string_list_ty *
+desktop_unescape_string_list (const char *s)
+{
+ string_list_ty *new_list;
+
+ new_list = string_list_alloc ();
+
+ for (;; s++)
+ {
+ const char *start, *end;
+ char *buffer, *p;
+
+ /* Compute the length of escaped element. */
+ for (start = s; *s != '\0' && *s != ';'; s++)
+ if (*s == '\\')
+ s++;
+
+ if (*s == '\0')
+ break;
+
+ end = s;
+
+ p = buffer = XNMALLOC (s - start + 1, char);
+
+ /* Unescape an element. */
+ for (s = start; s < end; s++)
+ {
+ if (*s == '\\')
+ {
+ s++;
+
+ switch (*s)
+ {
+ case 's':
+ *p++ = ' ';
+ break;
+ case 'n':
+ *p++ = '\n';
+ break;
+ case 't':
+ *p++ = '\t';
+ break;
+ case 'r':
+ *p++ = '\r';
+ break;
+ case ';':
+ *p++ = ';';
+ break;
+ default:
+ *p++ = *s;
+ break;
+ }
+ }
+ else
+ *p++ = *s;
+ }
+
+ *p = '\0';
+ string_list_append (new_list, buffer);
+ free (buffer);
+ }
+ return new_list;
+}
+
+void
+desktop_add_keyword (hash_table *keywords, const char *name, bool is_list)
+{
+ hash_insert_entry (keywords, name, strlen (name), (void *) is_list);
+}
+
+void
+desktop_add_default_keywords (hash_table *keywords)
+{
+ /* When adding new keywords here, also update the documentation in
+ xgettext.texi! */
+ desktop_add_keyword (keywords, "Name", false);
+ desktop_add_keyword (keywords, "GenericName", false);
+ desktop_add_keyword (keywords, "Comment", false);
+ desktop_add_keyword (keywords, "Icon", false);
+ desktop_add_keyword (keywords, "Keywords", true);
+}
diff --git a/gettext-tools/src/read-desktop.h b/gettext-tools/src/read-desktop.h
new file mode 100644
index 0000000..d424554
--- /dev/null
+++ b/gettext-tools/src/read-desktop.h
@@ -0,0 +1,123 @@
+/* Reading Desktop Entry files.
+ Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014 Free
Software Foundation, Inc.
+ This file was written by Daiki Ueno <address@hidden>.
+
+ 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 _READ_DESKTOP_H
+#define _READ_DESKTOP_H
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "hash.h"
+#include "po-lex.h"
+#include "str-list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Forward declaration. */
+struct desktop_reader_ty;
+
+
+/* This first structure, playing the role of the "Class" in OO sense,
+ contains pointers to functions. Each function is a method for the
+ class (base or derived). Use a NULL pointer where no action is
+ required. */
+
+typedef struct desktop_reader_class_ty desktop_reader_class_ty;
+struct desktop_reader_class_ty
+{
+ /* how many bytes to malloc for an instance of this class */
+ size_t size;
+
+ /* what to do immediately after the instance is malloc()ed */
+ void (*constructor) (struct desktop_reader_ty *pop);
+
+ /* what to do immediately before the instance is free()ed */
+ void (*destructor) (struct desktop_reader_ty *pop);
+
+ /* what to do with a group header */
+ void (*handle_group) (struct desktop_reader_ty *pop,
+ const char *group);
+
+ /* what to do with a key/value pair */
+ void (*handle_pair) (struct desktop_reader_ty *pop,
+ lex_pos_ty *key_pos,
+ const char *key,
+ const char *locale,
+ const char *value);
+
+ /* what to do with a comment */
+ void (*handle_comment) (struct desktop_reader_ty *pop, const char *s);
+
+ /* what to do with other lines */
+ void (*handle_text) (struct desktop_reader_ty *pop, const char *s);
+};
+
+/* This next structure defines the base class passed to the methods.
+ Derived methods will often need to cast their first argument before
+ using it (this corresponds to the implicit 'this' argument in C++).
+
+ When declaring derived classes, use the DESKTOP_READER_TY define
+ at the start of the structure, to declare inherited instance variables,
+ etc. */
+
+#define DESKTOP_READER_TY \
+ desktop_reader_class_ty *methods;
+
+typedef struct desktop_reader_ty desktop_reader_ty;
+struct desktop_reader_ty
+{
+ DESKTOP_READER_TY
+};
+
+desktop_reader_ty *desktop_reader_alloc (desktop_reader_class_ty *methods);
+void desktop_reader_free (desktop_reader_ty *reader);
+
+void desktop_reader_handle_group (desktop_reader_ty *reader,
+ const char *group);
+
+void desktop_reader_handle_pair (desktop_reader_ty *reader,
+ lex_pos_ty *key_pos,
+ const char *key,
+ const char *locale,
+ const char *value);
+
+void desktop_reader_handle_comment (desktop_reader_ty *reader,
+ const char *s);
+
+void desktop_reader_handle_text (desktop_reader_ty *reader,
+ const char *s);
+
+
+void desktop_parse (desktop_reader_ty *reader, FILE *file,
+ const char *real_filename, const char *logical_filename);
+
+
+char *desktop_escape_string (const char *s);
+char *desktop_unescape_string (const char *s);
+char *desktop_escape_string_list (string_list_ty *l);
+string_list_ty *desktop_unescape_string_list (const char *s);
+
+void desktop_add_keyword (hash_table *keywords, const char *name, bool
is_list);
+void desktop_add_default_keywords (hash_table *keywords);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _READ_DESKTOP_H */
diff --git a/gettext-tools/src/x-desktop.c b/gettext-tools/src/x-desktop.c
new file mode 100644
index 0000000..7ebc17b
--- /dev/null
+++ b/gettext-tools/src/x-desktop.c
@@ -0,0 +1,190 @@
+/* xgettext Desktop Entry backend.
+ Copyright (C) 2014 Free Software Foundation, Inc.
+
+ This file was written by Daiki Ueno <address@hidden>, 2014.
+
+ 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-desktop.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "message.h"
+#include "xgettext.h"
+#include "error.h"
+#include "error-progname.h"
+#include "xalloc.h"
+#include "xvasprintf.h"
+#include "hash.h"
+#include "gettext.h"
+#include "read-desktop.h"
+#include "po-charset.h"
+
+#define _(s) gettext(s)
+
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+
+/* ====================== Keyword set customization. ====================== */
+
+/* The syntax of a Desktop Entry file is defined at
+ http://standards.freedesktop.org/desktop-entry-spec/latest/index.html
+
+ Basically, values with 'localestring' type can be translated.
+
+ The type of a value is determined by looking at the key associated
+ with it. The list of available keys are listed on:
+ http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s05.html */
+
+static hash_table keywords;
+static bool default_keywords = true;
+
+static void
+add_keyword (const char *name, hash_table *keywords, bool is_list)
+{
+ if (name == NULL)
+ default_keywords = false;
+ else
+ {
+ if (keywords->table == NULL)
+ hash_init (keywords, 100);
+
+ desktop_add_keyword (keywords, name, is_list);
+ }
+}
+
+void
+x_desktop_keyword (const char *name)
+{
+ add_keyword (name, &keywords, false);
+}
+
+static void
+init_keywords (void)
+{
+ if (default_keywords)
+ {
+ if (keywords.table == NULL)
+ hash_init (&keywords, 100);
+
+ desktop_add_default_keywords (&keywords);
+ default_keywords = false;
+ }
+}
+
+typedef struct extract_desktop_reader_ty extract_desktop_reader_ty;
+struct extract_desktop_reader_ty
+{
+ DESKTOP_READER_TY
+
+ message_list_ty *mlp;
+};
+
+static void
+extract_desktop_handle_group (struct desktop_reader_ty *reader,
+ const char *group)
+{
+ savable_comment_reset ();
+}
+
+static void
+extract_desktop_handle_pair (struct desktop_reader_ty *reader,
+ lex_pos_ty *key_pos,
+ const char *key,
+ const char *locale,
+ const char *value)
+{
+ extract_desktop_reader_ty *extract_reader =
+ (extract_desktop_reader_ty *) reader;
+ void *keyword_value;
+
+ if (!locale /* Skip already translated entry. */
+ && hash_find_entry (&keywords, key, strlen (key), &keyword_value) == 0)
+ {
+ bool is_list = (bool) keyword_value;
+
+ if (is_list)
+ {
+ string_list_ty *slp = desktop_unescape_string_list (value);
+ size_t i;
+
+ for (i = 0; i < slp->nitems; i++)
+ remember_a_message (extract_reader->mlp, NULL,
+ xstrdup (slp->item[i]),
+ null_context, key_pos,
+ NULL, savable_comment);
+ string_list_free (slp);
+ }
+ else
+ remember_a_message (extract_reader->mlp, NULL,
+ desktop_unescape_string (value),
+ null_context, key_pos,
+ NULL, savable_comment);
+ }
+ savable_comment_reset ();
+}
+
+static void
+extract_desktop_handle_comment (struct desktop_reader_ty *reader,
+ const char *s)
+{
+ savable_comment_add (s);
+}
+
+static void
+extract_desktop_handle_text (struct desktop_reader_ty *reader,
+ const char *s)
+{
+ savable_comment_reset ();
+}
+
+desktop_reader_class_ty extract_methods =
+ {
+ sizeof (extract_desktop_reader_ty),
+ NULL,
+ NULL,
+ extract_desktop_handle_group,
+ extract_desktop_handle_pair,
+ extract_desktop_handle_comment,
+ extract_desktop_handle_text
+ };
+
+void
+extract_desktop (FILE *f,
+ const char *real_filename, const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp)
+{
+ desktop_reader_ty *reader = desktop_reader_alloc (&extract_methods);
+ extract_desktop_reader_ty *extract_reader =
+ (extract_desktop_reader_ty *) reader;
+
+ init_keywords ();
+ xgettext_current_source_encoding = po_charset_utf8;
+
+ extract_reader->mlp = mdlp->item[0]->messages;
+
+ desktop_parse (reader, f, real_filename, logical_filename);
+ desktop_reader_free (reader);
+
+ reader = NULL;
+}
diff --git a/gettext-tools/src/x-desktop.h b/gettext-tools/src/x-desktop.h
new file mode 100644
index 0000000..d9f1ff4
--- /dev/null
+++ b/gettext-tools/src/x-desktop.h
@@ -0,0 +1,47 @@
+/* xgettext Desktop Entry backend.
+ Copyright (C) 2014 Free Software Foundation, Inc.
+ Written by Daiki Ueno <address@hidden>, 2014.
+
+ 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_DESKTOP \
+ { "desktop", "Desktop" }, \
+
+#define SCANNERS_DESKTOP \
+ { "Desktop", extract_desktop, NULL, NULL, NULL }, \
+
+/* Scan a Desktop Entry file and add its translatable strings to mdlp. */
+extern void extract_desktop (FILE *fp, const char *real_filename,
+ const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp);
+
+extern void x_desktop_keyword (const char *keyword);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c
index 8e89a33..21e544a 100644
--- a/gettext-tools/src/xgettext.c
+++ b/gettext-tools/src/xgettext.c
@@ -96,6 +96,7 @@
#include "x-javascript.h"
#include "x-vala.h"
#include "x-gsettings.h"
+#include "x-desktop.h"
/* If nonzero add all comments immediately preceding one of the keywords. */
@@ -448,6 +449,7 @@ main (int argc, char *argv[])
x_lua_keyword (optarg);
x_javascript_keyword (optarg);
x_vala_keyword (optarg);
+ x_desktop_keyword (optarg);
if (optarg == NULL)
no_default_keywords = true;
else
@@ -885,7 +887,7 @@ Choice of input file language:\n"));
EmacsLisp, librep, Scheme, Smalltalk, Java,\n\
JavaProperties, C#, awk, YCP, Tcl, Perl,
PHP,\n\
GCC-source, NXStringTable, RST, Glade, Lua,\n\
- JavaScript, Vala)\n"));
+ JavaScript, Vala, Desktop)\n"));
printf (_("\
-C, --c++ shorthand for --language=C++\n"));
printf (_("\
@@ -927,7 +929,7 @@ Language specific options:\n"));
(only languages C, C++, ObjectiveC, Shell,\n\
Python, Lisp, EmacsLisp, librep, Scheme,
Java,\n\
C#, awk, Tcl, Perl, PHP, GCC-source, Glade,\n\
- Lua, JavaScript, Vala)\n"));
+ Lua, JavaScript, Vala, Desktop)\n"));
printf (_("\
--flag=WORD:ARG:FLAG additional flag for strings inside the
argument\n\
number ARG of keyword WORD\n"));
@@ -3257,6 +3259,7 @@ language_to_extractor (const char *name)
SCANNERS_JAVASCRIPT
SCANNERS_VALA
SCANNERS_GSETTINGS
+ SCANNERS_DESKTOP
/* Here may follow more languages and their scanners: pike, etc...
Make sure new scanners honor the --exclude-file option. */
};
@@ -3344,6 +3347,7 @@ extension_to_language (const char *extension)
EXTENSIONS_JAVASCRIPT
EXTENSIONS_VALA
EXTENSIONS_GSETTINGS
+ EXTENSIONS_DESKTOP
/* Here may follow more file extensions... */
};
diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am
index e90bec4..eb9367c 100644
--- a/gettext-tools/tests/Makefile.am
+++ b/gettext-tools/tests/Makefile.am
@@ -105,6 +105,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5
gettext-6 gettext-7 \
xgettext-javascript-4 xgettext-javascript-5 xgettext-javascript-6 \
xgettext-vala-1 \
xgettext-gsettings-1 \
+ xgettext-desktop-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-desktop-1
b/gettext-tools/tests/xgettext-desktop-1
new file mode 100755
index 0000000..78cd763
--- /dev/null
+++ b/gettext-tools/tests/xgettext-desktop-1
@@ -0,0 +1,64 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test of Desktop Entry support.
+
+cat <<EOF > xg.desktop
+[Desktop Entry]
+Type=Application
+Name =Foo
+Comment= \sThis is a \nmultiline\t comment; for testing
+Comment[foo]=Already translated comment
+Keywords=Keyword1;Keyword2;Key\;word3;
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments -o - xg.desktop | grep -v 'POT-Creation-Date' >
xg-desktop.pot || exit 1
+
+cat <<EOF > xg-desktop.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.desktop:4
+msgid "Foo"
+msgstr ""
+
+#: xg.desktop:5
+msgid ""
+" This is a \n"
+"multiline\t comment; for testing"
+msgstr ""
+
+#: xg.desktop:7
+msgid "Keyword1"
+msgstr ""
+
+#: xg.desktop:7
+msgid "Keyword2"
+msgstr ""
+
+#: xg.desktop:7
+msgid "Key;word3"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-desktop.ok xg-desktop.pot
+result=$?
+
+exit $result
--
1.8.4.2