From e99f676bfa386007af42c41ad0f489dcf74b6614 Mon Sep 17 00:00:00 2001 From: James Westman Date: Sat, 7 Sep 2024 19:29:01 -0500 Subject: [PATCH] Add support for Blueprint --- gettext-tools/src/Makefile.am | 6 +- gettext-tools/src/x-blueprint.c | 559 +++++++++++++++++++++++ gettext-tools/src/x-blueprint.h | 46 ++ gettext-tools/src/xgettext.c | 4 + gettext-tools/tests/Makefile.am | 1 + gettext-tools/tests/xgettext-blueprint-1 | 213 +++++++++ 6 files changed, 827 insertions(+), 2 deletions(-) create mode 100644 gettext-tools/src/x-blueprint.c create mode 100644 gettext-tools/src/x-blueprint.h create mode 100755 gettext-tools/tests/xgettext-blueprint-1 diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am index 6d1a1c26a..41b528ccc 100644 --- a/gettext-tools/src/Makefile.am +++ b/gettext-tools/src/Makefile.am @@ -90,7 +90,8 @@ noinst_HEADERS = \ x-ycp.h \ x-rst.h \ x-desktop.h \ - x-glade.h x-gsettings.h x-appdata.h + x-glade.h x-gsettings.h x-appdata.h \ + x-blueprint.h EXTRA_DIST += FILES project-id @@ -283,7 +284,8 @@ xgettext_SOURCES += \ x-php.c \ x-ycp.c \ x-rst.c \ - x-desktop.c + x-desktop.c \ + x-blueprint.c if !WOE32DLL msgattrib_SOURCES = msgattrib.c else diff --git a/gettext-tools/src/x-blueprint.c b/gettext-tools/src/x-blueprint.c new file mode 100644 index 000000000..09cb26c4f --- /dev/null +++ b/gettext-tools/src/x-blueprint.c @@ -0,0 +1,559 @@ +/* xgettext Desktop Entry backend. + Copyright (C) 2014-2024 Free Software Foundation, Inc. + + This file was written by Daiki Ueno , 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 . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-desktop.h" + +#include +#include +#include +#include +#include + +#include +#include +#include "message.h" +#include "xgettext.h" +#include "xg-message.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "mem-hash-map.h" +#include "gettext.h" +#include "read-desktop.h" +#include "po-charset.h" +#include "po-xerror.h" +#include "c-ctype.h" + +#define _(s) gettext(s) + +static bool extract_all = false; + + +void +x_blueprint_extract_all () +{ + extract_all = true; +} + + +enum token_type_ty { + TOKEN_EOF, + TOKEN_COMMENT, + TOKEN_LPAREN, + TOKEN_RPAREN, + TOKEN_COMMA, + TOKEN_QUOTED, + TOKEN_TRANSLATED, + TOKEN_TRANSLATED_WITH_CONTEXT, + TOKEN_EXTERNAL, + TOKEN_WHITESPACE, + TOKEN_OTHER +}; +typedef enum token_type_ty token_type_ty; + + +typedef struct extract_blp_ty extract_blp_ty; +struct extract_blp_ty { + FILE *fp; + const char *filename; + message_list_ty *mlp; + + int lineno; + int colno; + int prev_lineno; + + char *comment; + int comment_line; + bool hold_comment; + + char *buf; + size_t buflen; + size_t bufpos; +}; + + +static void +steal_buffer (extract_blp_ty *extract, char **dest) +{ + if (*dest) + free (*dest); + + *dest = extract->buf; + extract->buf = NULL; + extract->buflen = 0; +} + + +static void +clear_comment (extract_blp_ty *extract) +{ + if (extract->comment) + free (extract->comment); + extract->comment = NULL; + extract->comment_line = -99; +} + + +static void +buf_append (extract_blp_ty *extract, char c) +{ + if (extract->bufpos + 1 > extract->buflen) + { + extract->buflen += 128; + extract->buf = xrealloc (extract->buf, extract->buflen); + extract->buf[extract->buflen - 1] = 0; + } + extract->buf[extract->bufpos++] = c; +} + + +static void +emit_error (extract_blp_ty *extract, int severity, const char *message) +{ + po_xerror (severity, NULL, extract->filename, extract->lineno, extract->colno, false, message); +} + + +static int +phase0 (extract_blp_ty *extract) +{ + char c; + + c = getc (extract->fp); + + if (c == '\n') + { + ++extract->lineno; + extract->colno = 0; + } + else if (c == EOF) + { + if (ferror (extract->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\""), + extract->filename), + errno_description)); + } + } + else + extract->colno++; + + return c; +} + + +static void +phase0_ungetc (extract_blp_ty *extract, char c) +{ + if (c == '\n') + { + --extract->lineno; + extract->colno = 0; + } + else if (c != EOF) + --extract->colno; + + ungetc (c, extract->fp); +} + +static token_type_ty +phase1 (extract_blp_ty *extract) +{ + int i; + char c; + char *buf; + size_t buflen; + size_t bufpos; + char close_quote; + + /* remember the line number at the token's start */ + extract->prev_lineno = extract->lineno; + c = phase0 (extract); + + switch (c) + { + case EOF: + return TOKEN_EOF; + + case ',': + return TOKEN_COMMA; + + case '$': + return TOKEN_EXTERNAL; + + case '(': + return TOKEN_LPAREN; + + case ')': + return TOKEN_RPAREN; + + case '/': + c = phase0 (extract); + extract->bufpos = 0; + buf_append (extract, c); + + switch (c) + { + case '/': + /* single-line comment */ + for (;;) + { + c = phase0 (extract); + if (c == '\n' || c == EOF) + break; + buf_append (extract, c); + } + + break; + + case '*': + /* multi-line comment */ + for (;;) + { + c = phase0 (extract); + + switch (c) + { + case '*': + buf_append (extract, c); + c = phase0 (extract); + if (c == '/') + { + buf_append (extract, 0); + return TOKEN_COMMENT; + } + else + phase0_ungetc (extract, c); + break; + + case EOF: + buf_append (extract, 0); + return TOKEN_COMMENT; + + default: + buf_append (extract, c); + } + } + + case EOF: + return TOKEN_EOF; + } + + case 'A' ... 'Z': + case 'a' ... 'z': + case '0' ... '9': + case '_': + /* identifier or number */ + + extract->bufpos = 0; + buf_append (extract, c); + + for (;;) + { + c = phase0 (extract); + if (!isalnum (c) && c != '_' && c != '-') + { + phase0_ungetc (extract, c); + buf_append (extract, 0); + + if (strcmp (extract->buf, "_") == 0) + return TOKEN_TRANSLATED; + else if (strcmp (extract->buf, "C_") == 0) + return TOKEN_TRANSLATED_WITH_CONTEXT; + else + return TOKEN_OTHER; + } + + buf_append (extract, c); + } + + case '"': + case '\'': + /* quoted string */ + + close_quote = c; + extract->bufpos = 0; + + for (;;) + { + c = phase0 (extract); + switch (c) + { + case EOF: + buf_append (extract, 0); + return TOKEN_OTHER; + + case '"': + case '\'': + if (c == close_quote) + { + buf_append (extract, 0); + return TOKEN_QUOTED; + } + else + buf_append (extract, c); + break; + + case '\\': + c = phase0 (extract); + switch (c) + { + case EOF: + buf_append (extract, 0); + return TOKEN_OTHER; + + case '\n': + case 'n': + buf_append (extract, '\n'); + break; + + case 't': + buf_append (extract, '\t'); + break; + + case '"': + case '\'': + case '\\': + buf_append (extract, c); + break; + + default: + /* invalid syntax, just leave it */ + emit_error (extract, PO_SEVERITY_WARNING, _("invalid escape sequence")); + buf_append (extract, '\\'); + buf_append (extract, c); + } + break; + + default: + buf_append (extract, c); + break; + } + } + + default: + if (isspace (c)) + return TOKEN_WHITESPACE; + else + return TOKEN_OTHER; + } +} + + +static token_type_ty +phase2 (extract_blp_ty *extract) +{ + token_type_ty token; + + for (;;) + { + token = phase1 (extract); + + switch (token) + { + case TOKEN_WHITESPACE: + break; + + case TOKEN_COMMENT: + if (!extract->hold_comment) + { + steal_buffer (extract, &extract->comment); + extract->comment_line = extract->lineno; + } + break; + + default: + return token; + } + } +} + + +static void +phase3 (extract_blp_ty *extract) +{ + token_type_ty token; + token_type_ty translated_token; + lex_pos_ty pos; + char *context = NULL; + char *message = NULL; + char *comment = NULL; + bool external = false; + int lineno; + + for (;;) + { + token = phase2 (extract); + + switch (token) + { + case TOKEN_EOF: + return; + + case TOKEN_EXTERNAL: + external = true; + break; + + case TOKEN_COMMENT: + break; + + case TOKEN_TRANSLATED: + case TOKEN_TRANSLATED_WITH_CONTEXT: + if (external) + break; + + external = false; + + translated_token = token; + /* use the line number of the translation marker token */ + lineno = extract->lineno; + + /* The translator comment must be before the _ or C_ */ + extract->hold_comment = true; + + token = phase2 (extract); + + if (token != TOKEN_LPAREN) + { + po_xerror (PO_SEVERITY_WARNING, NULL, extract->filename, extract->lineno, extract->colno, false, + _("expected '('")); + break; + } + + if (translated_token == TOKEN_TRANSLATED_WITH_CONTEXT) + { + token = phase2 (extract); + if (token != TOKEN_QUOTED) + { + emit_error(extract, PO_SEVERITY_WARNING, _("expected quoted context string")); + break; + } + steal_buffer (extract, &context); + + token = phase2 (extract); + if (token != TOKEN_COMMA) + { + emit_error(extract, PO_SEVERITY_WARNING, _("expected ','")); + break; + } + } + + token = phase2 (extract); + if (token != TOKEN_QUOTED) + { + emit_error(extract, PO_SEVERITY_WARNING, _("expected quoted message string")); + break; + } + steal_buffer (extract, &message); + + token = phase2 (extract); + if (token != TOKEN_RPAREN) + { + emit_error(extract, PO_SEVERITY_WARNING, _("expected ')'")); + break; + } + + pos.file_name = extract->filename; + pos.line_number = lineno; + + if (extract->comment_line >= lineno - 1) + comment = extract->comment; + else + comment = NULL; + + remember_a_message (extract->mlp, + context, + message, + true, false, null_context_region (), + &pos, comment, + NULL, false); + + message = NULL; + context = NULL; + comment = NULL; + clear_comment (extract); + break; + + case TOKEN_QUOTED: + if (!extract_all) + break; + + external = false; + + pos.file_name = extract->filename; + pos.line_number = extract->prev_lineno; + + if (extract->comment_line >= extract->prev_lineno - 1) + comment = extract->comment; + else + comment = NULL; + + steal_buffer (extract, &message); + + remember_a_message (extract->mlp, + NULL, + message, + true, false, null_context_region (), + &pos, comment, + NULL, false); + + message = NULL; + comment = NULL; + + break; + + default: + external = false; + break; + } + + extract->hold_comment = false; + } +} + + +void +extract_blueprint (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + extract_blp_ty extract; + + extract.fp = f; + extract.filename = real_filename; + extract.mlp = mdlp->item[0]->messages; + + extract.lineno = 1; + extract.colno = 0; + + extract.comment = NULL; + extract.hold_comment = false; + + extract.buf = NULL; + extract.buflen = 0; + + phase3 (&extract); +} diff --git a/gettext-tools/src/x-blueprint.h b/gettext-tools/src/x-blueprint.h new file mode 100644 index 000000000..df53801b8 --- /dev/null +++ b/gettext-tools/src/x-blueprint.h @@ -0,0 +1,46 @@ +/* xgettext Desktop Entry backend. + Copyright (C) 2014, 2018, 2020 Free Software Foundation, Inc. + Written by Daiki Ueno , 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 . */ + + +#include + +#include "message.h" +#include "xg-arglist-context.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_BLUEPRINT \ + { "blp", "Blueprint" }, \ + +#define SCANNERS_BLUEPRINT \ + { "Blueprint", extract_blueprint, NULL, NULL, NULL, NULL }, \ + +/* Scan a Blueprint file and add its translatable strings to mdlp. */ +extern void extract_blueprint (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_blueprint_extract_all (void); + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index a9c2f04dd..32d929343 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -124,6 +124,7 @@ #include "x-glade.h" #include "x-gsettings.h" #include "x-appdata.h" +#include "x-blueprint.h" #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) @@ -398,6 +399,7 @@ main (int argc, char *argv[]) break; case 'a': + x_blueprint_extract_all (); x_c_extract_all (); x_sh_extract_all (); x_python_extract_all (); @@ -2245,6 +2247,7 @@ language_to_extractor (const char *name) SCANNERS_GLADE SCANNERS_GSETTINGS SCANNERS_APPDATA + SCANNERS_BLUEPRINT /* Here may follow more languages and their scanners: pike, etc... Make sure new scanners honor the --exclude-file option. */ }; @@ -2338,6 +2341,7 @@ extension_to_language (const char *extension) EXTENSIONS_GLADE EXTENSIONS_GSETTINGS EXTENSIONS_APPDATA + EXTENSIONS_BLUEPRINT /* Here may follow more file extensions... */ }; diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index c27d4b390..04e6b842d 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -87,6 +87,7 @@ TESTS = gettext-1 gettext-2 \ xgettext-appdata-1 \ xgettext-awk-1 xgettext-awk-2 xgettext-awk-3 \ xgettext-awk-stackovfl-1 xgettext-awk-stackovfl-2 \ + xgettext-blueprint-1 \ xgettext-c-2 xgettext-c-3 xgettext-c-4 xgettext-c-5 xgettext-c-6 \ xgettext-c-7 xgettext-c-8 \ xgettext-c-comment-1 xgettext-c-comment-2 xgettext-c-comment-3 \ diff --git a/gettext-tools/tests/xgettext-blueprint-1 b/gettext-tools/tests/xgettext-blueprint-1 new file mode 100755 index 000000000..7c50d2851 --- /dev/null +++ b/gettext-tools/tests/xgettext-blueprint-1 @@ -0,0 +1,213 @@ +#!/bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test of Blueprint support. + +: ${XGETTEXT=xgettext} + +cat <<\EOF > xg-blueprint-1.blp + +using Gtk 4.0; + +\$MyObject { + // _("Commented out string"); + prop1: "Untranslated string"; + prop2: _("Extract this first string"); + prop3: C_("context", "Extract this second string"); + prop4: /* The correct translator comment */ C_ /* Weirdly placed comment */ ( /* This is not the comment */ "context", "Extract this third string"); + prop5: _ + ( + "Weird\ +whitespace" + ); + prop6: _('Extract this single quoted string'); + prop7: _('Extract this string with escape\nsequences\t\'\""'); + prop7: _("escape\nsequence 2\t'\'\""); + + /* Only one */ /* comment */ + prop8: _("Test string with comment"); + + /* Comment must be on preceding line */ + + prop9: _("Test string without comment"); + + prop10: bind \$_("Badly named application function, not translation") sync-create; + + prop11: 'Single quoted untranslated string'; + + prop12: _("Extract this ✨fancy✨ string"); +} + +/* not a translation */ +menu () +EOF + +${XGETTEXT} --add-comments -o xg-blueprint-1.tmp xg-blueprint-1.blp 2>xg-blueprint-1.err +test $? = 0 || { cat xg-blueprint-1.err; Exit 1; } +func_filter_POT_Creation_Date xg-blueprint-1.tmp xg-blueprint-1.pot + +cat <<\EOF > xg-blueprint-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 , 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 \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: xg-blueprint-1.blp:7 +msgid "Extract this first string" +msgstr "" + +#: xg-blueprint-1.blp:8 +msgctxt "context" +msgid "Extract this second string" +msgstr "" + +#. * The correct translator comment * +#: xg-blueprint-1.blp:9 +msgctxt "context" +msgid "Extract this third string" +msgstr "" + +#: xg-blueprint-1.blp:10 +msgid "" +"Weird\n" +"whitespace" +msgstr "" + +#: xg-blueprint-1.blp:15 +msgid "Extract this single quoted string" +msgstr "" + +#: xg-blueprint-1.blp:16 +msgid "" +"Extract this string with escape\n" +"sequences\t'\"\"" +msgstr "" + +#: xg-blueprint-1.blp:17 +msgid "" +"escape\n" +"sequence 2\t''\"" +msgstr "" + +#. * comment * +#: xg-blueprint-1.blp:20 +msgid "Test string with comment" +msgstr "" + +#: xg-blueprint-1.blp:24 +msgid "Test string without comment" +msgstr "" + +#: xg-blueprint-1.blp:30 +msgid "Extract this ✨fancy✨ string" +msgstr "" +EOF + +: ${DIFF=diff} +${DIFF} xg-blueprint-1.ok xg-blueprint-1.pot +result=$? +test $result = 0 || exit $result + +# Test --extract-all option. +${XGETTEXT} --extract-all --add-comments -o xg-blueprint-1.tmp xg-blueprint-1.blp 2>xg-blueprint-1.err +test $? = 0 || { cat xg-blueprint-1.err; Exit 1; } +func_filter_POT_Creation_Date xg-blueprint-1.tmp xg-blueprint-1.pot + +cat <<\EOF > xg-blueprint-1.all.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 , 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 \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: xg-blueprint-1.blp:6 +msgid "Untranslated string" +msgstr "" + +#: xg-blueprint-1.blp:7 +msgid "Extract this first string" +msgstr "" + +#: xg-blueprint-1.blp:8 +msgctxt "context" +msgid "Extract this second string" +msgstr "" + +#. * The correct translator comment * +#: xg-blueprint-1.blp:9 +msgctxt "context" +msgid "Extract this third string" +msgstr "" + +#: xg-blueprint-1.blp:10 +msgid "" +"Weird\n" +"whitespace" +msgstr "" + +#: xg-blueprint-1.blp:15 +msgid "Extract this single quoted string" +msgstr "" + +#: xg-blueprint-1.blp:16 +msgid "" +"Extract this string with escape\n" +"sequences\t'\"\"" +msgstr "" + +#: xg-blueprint-1.blp:17 +msgid "" +"escape\n" +"sequence 2\t''\"" +msgstr "" + +#. * comment * +#: xg-blueprint-1.blp:20 +msgid "Test string with comment" +msgstr "" + +#: xg-blueprint-1.blp:24 +msgid "Test string without comment" +msgstr "" + +#: xg-blueprint-1.blp:26 +msgid "Badly named application function, not translation" +msgstr "" + +#: xg-blueprint-1.blp:28 +msgid "Single quoted untranslated string" +msgstr "" + +#: xg-blueprint-1.blp:30 +msgid "Extract this ✨fancy✨ string" +msgstr "" +EOF + +${DIFF} xg-blueprint-1.all.ok xg-blueprint-1.pot +result=$? +test $result = 0 || exit $result -- 2.46.0