* gettext-tools/src/x-rst.h (EXTENSIONS_RST, SCANNERS_RST): Add RSJ scanner.
(extract_rsj): New declaration.
* gettext-tools/src/x-rst.c: Implement extraction from .rsj files.
* gettext-tools/src/xgettext.c (usage): Mention RSJ along with RST.
* gettext-tools/tests/xgettext-rst-1: New file.
* gettext-tools/tests/xgettext-rst-2: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add them.
* gettext-tools/tests/lang-pascal: Extract from .rsj file, if the compiler
generated it.
* gettext-tools/doc/gettext.texi (RST): Mention that .rsj files are supported
as well.
* gettext-tools/doc/xgettext.texi (Choice of input file language): Mention RSJ
along with RST.
* NEWS: Mention the change.
npgettext, dnpgettext, dcnpgettext).
o better detection of question mark and slash as operators (as opposed
to regular expression delimiters).
+ - Pascal:
+ xgettext can now extract strings from .rsj files, produced by the
+ Free Pascal compiler version 3.0.0 or newer.
Version 0.19.8 - June 2016
@node RST, Glade, POT, List of Data Formats
@subsection Resource String Table
@cindex RST
+@cindex RSJ
+
+RST is the format of resource string table files of the Free Pascal compiler
+versions older than 3.0.0. RSJ is the new format of resource string table
+files, created by the Free Pascal compiler version 3.0.0 or newer.
@table @asis
@item RPMs
fp-compiler
@item File extension
-@code{rst}
+@code{rst}, @code{rsj}
@item Extractor
@code{xgettext}, @code{rstconv}
@code{Python}, @code{Lisp}, @code{EmacsLisp}, @code{librep}, @code{Scheme},
@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},
-@code{Vala}, @code{GSettings}, @code{Desktop}.
+@code{NXStringTable}, @code{RST}, @code{RSJ}, @code{Glade}, @code{Lua},
+@code{JavaScript}, @code{Vala}, @code{GSettings}, @code{Desktop}.
@item -C
@itemx --c++
-/* xgettext RST backend.
- Copyright (C) 2001-2003, 2005-2009, 2015-2016 Free Software Foundation, Inc.
+/* xgettext RST/RSJ backend.
+ Copyright (C) 2001-2003, 2005-2009, 2015-2016, 2018 Free Software Foundation, Inc.
This file was written by Bruno Haible <haible@clisp.cons.org>, 2001.
#include "x-rst.h"
#include <errno.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "c-ctype.h"
+#include "po-charset.h"
#include "message.h"
#include "xgettext.h"
#include "error.h"
This backend attempts to be functionally equivalent to the 'rstconv'
program, part of the Free Pascal run time library, written by
- Sebastian Guenther. Except that the locations are output as
- "ModuleName.ConstName", not "ModuleName:ConstName".
+ Sebastian Guenther. Except that
+ * the locations are output as "ModuleName.ConstName",
+ not "ModuleName:ConstName",
+ * we add the flag '#, object-pascal-format' where appropriate.
*/
void
real_filename);
}
}
+
+
+/* RSJ stands for Resource String Table in JSON.
+
+ An RSJ file is a JSON file that contains several string definitions.
+ It has the format (modulo whitespace)
+ {
+ "version": 1,
+ "strings":
+ [
+ {
+ "hash": <integer>,
+ "name": <string>,
+ "sourcebytes": [ <integer>... ],
+ "value": <string>
+ },
+ ...
+ ]
+ }
+ The sourcebytes array contains the original source bytes, in the
+ source encoding (not guaranteed to be ISO-8859-1, see
+ <http://wiki.freepascal.org/FPC_Unicode_support#Source_file_codepage>).
+
+ This backend attempts to be functionally equivalent to the 'rstconv'
+ program, part of the Free Pascal run time library, written by
+ Sebastian Guenther. Except that
+ * we use the "value" as msgid, not the "sourcebytes",
+ * the locations are output as "ModuleName.ConstName",
+ not "ModuleName:ConstName",
+ * we add the flag '#, object-pascal-format' where appropriate.
+ */
+
+/* For the JSON syntax, refer to RFC 8259. */
+
+/* ======================== Reading of characters. ======================== */
+
+/* Real filename, used in error messages about the input file. */
+static const char *real_file_name;
+
+/* Logical filename and line number, used to label the extracted messages. */
+static char *logical_file_name;
+static int line_number;
+
+/* The input file stream. */
+static FILE *fp;
+
+
+/* 1. line_number handling. */
+
+static int
+phase1_getc ()
+{
+ int c = getc (fp);
+
+ if (c == EOF)
+ {
+ if (ferror (fp))
+ error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
+ real_file_name);
+ return EOF;
+ }
+
+ if (c == '\n')
+ line_number++;
+
+ return c;
+}
+
+/* Supports only one pushback character. */
+static void
+phase1_ungetc (int c)
+{
+ if (c != EOF)
+ {
+ if (c == '\n')
+ --line_number;
+
+ ungetc (c, fp);
+ }
+}
+
+
+/* 2. Skipping whitespace. */
+
+/* Tests whether a phase1_getc() result is JSON whitespace. */
+static inline bool
+is_whitespace (int c)
+{
+ return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
+}
+
+static int
+phase2_getc ()
+{
+ int c;
+
+ do
+ c = phase1_getc ();
+ while (is_whitespace (c));
+
+ return c;
+}
+
+static void
+phase2_ungetc (int c)
+{
+ phase1_ungetc (c);
+}
+
+
+/* ========================== Reading of tokens. ========================== */
+
+/* Result of parsing a token. */
+
+enum parse_result
+{
+ pr_parsed, /* successfully parsed */
+ pr_none, /* the next token is of a different type */
+ pr_syntax /* syntax error inside the token */
+};
+
+static char *buffer;
+static int bufmax;
+
+/* Parses an integer. Returns it in buffer, of length bufmax.
+ Returns pr_parsed or pr_none. */
+static enum parse_result
+parse_integer ()
+{
+ int c;
+ int bufpos;
+
+ c = phase2_getc ();
+ bufpos = 0;
+ for (;;)
+ {
+ if (bufpos >= bufmax)
+ {
+ bufmax = 2 * bufmax + 10;
+ buffer = xrealloc (buffer, bufmax);
+ }
+ if (!(c >= '0' && c <= '9'))
+ break;
+ buffer[bufpos++] = c;
+ c = phase1_getc ();
+ }
+ phase1_ungetc (c);
+ buffer[bufpos] = '\0';
+ return (bufpos == 0 ? pr_none : pr_parsed);
+}
+
+static struct mixed_string_buffer *stringbuf;
+
+/* Parses a string. Returns it in stringbuf, in UTF-8 encoding.
+ Returns a parse_result. */
+static enum parse_result
+parse_string ()
+{
+ int c;
+
+ c = phase2_getc ();
+ if (c != '"')
+ {
+ phase2_ungetc (c);
+ return pr_none;
+ }
+ stringbuf = mixed_string_buffer_alloc (lc_string,
+ logical_file_name,
+ line_number);
+ for (;;)
+ {
+ c = phase1_getc ();
+ /* Keep line_number in sync. */
+ stringbuf->line_number = line_number;
+ if (c == EOF || (c >= 0 && c < 0x20))
+ return pr_syntax;
+ if (c == '"')
+ break;
+ if (c == '\\')
+ {
+ c = phase1_getc ();
+ if (c == 'u')
+ {
+ unsigned int n = 0;
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ c = phase1_getc ();
+
+ if (c >= '0' && c <= '9')
+ n = (n << 4) + (c - '0');
+ else if (c >= 'A' && c <= 'F')
+ n = (n << 4) + (c - 'A' + 10);
+ else if (c >= 'a' && c <= 'f')
+ n = (n << 4) + (c - 'a' + 10);
+ else
+ return pr_syntax;
+ }
+ mixed_string_buffer_append_unicode (stringbuf, n);
+ }
+ else
+ {
+ switch (c)
+ {
+ case '"':
+ case '\\':
+ case '/':
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ default:
+ return pr_syntax;
+ }
+ mixed_string_buffer_append_char (stringbuf, c);
+ }
+ }
+ else
+ mixed_string_buffer_append_char (stringbuf, c);
+ }
+ return pr_parsed;
+}
+
+void
+extract_rsj (FILE *f,
+ const char *real_filename, const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp)
+{
+ message_list_ty *mlp = mdlp->item[0]->messages;
+ int c;
+
+ fp = f;
+ real_file_name = real_filename;
+ logical_file_name = xstrdup (logical_filename);
+ line_number = 1;
+
+ /* JSON is always in UTF-8. */
+ xgettext_current_source_encoding = po_charset_utf8;
+
+ /* Parse the initial opening brace. */
+ c = phase2_getc ();
+ if (c != '{')
+ goto invalid_json;
+
+ c = phase2_getc ();
+ if (c != '}')
+ {
+ phase2_ungetc (c);
+ for (;;)
+ {
+ /* Parse a string. */
+ char *s1;
+ if (parse_string () != pr_parsed)
+ goto invalid_json;
+ s1 = mixed_string_buffer_done (stringbuf);
+
+ /* Parse a colon. */
+ c = phase2_getc ();
+ if (c != ':')
+ goto invalid_json;
+
+ if (strcmp (s1, "version") == 0)
+ {
+ /* Parse an integer. */
+ if (parse_integer () != pr_parsed)
+ goto invalid_rsj;
+ if (strcmp (buffer, "1") != 0)
+ goto invalid_rsj_version;
+ }
+ else if (strcmp (s1, "strings") == 0)
+ {
+ /* Parse an array. */
+ c = phase2_getc ();
+ if (c != '[')
+ goto invalid_rsj;
+
+ c = phase2_getc ();
+ if (c != ']')
+ {
+ phase2_ungetc (c);
+ for (;;)
+ {
+ char *location = NULL;
+ char *msgid = NULL;
+ lex_pos_ty pos;
+
+ /* Parse an object. */
+ c = phase2_getc ();
+ if (c != '{')
+ goto invalid_rsj;
+
+ c = phase2_getc ();
+ if (c != '}')
+ {
+ phase2_ungetc (c);
+ for (;;)
+ {
+ /* Parse a string. */
+ char *s2;
+ if (parse_string () != pr_parsed)
+ goto invalid_json;
+ s2 = mixed_string_buffer_done (stringbuf);
+
+ /* Parse a colon. */
+ c = phase2_getc ();
+ if (c != ':')
+ goto invalid_json;
+
+ if (strcmp (s2, "hash") == 0)
+ {
+ /* Parse an integer. */
+ if (parse_integer () != pr_parsed)
+ goto invalid_rsj;
+ }
+ else if (strcmp (s2, "name") == 0)
+ {
+ /* Parse a string. */
+ enum parse_result r = parse_string ();
+ if (r == pr_none)
+ goto invalid_rsj;
+ if (r == pr_syntax || location != NULL)
+ goto invalid_json;
+ location = mixed_string_buffer_done (stringbuf);
+ }
+ else if (strcmp (s2, "sourcebytes") == 0)
+ {
+ /* Parse an array. */
+ c = phase2_getc ();
+ if (c != '[')
+ goto invalid_rsj;
+
+ c = phase2_getc ();
+ if (c != ']')
+ {
+ phase2_ungetc (c);
+ for (;;)
+ {
+ /* Parse an integer. */
+ if (parse_integer () != pr_parsed)
+ goto invalid_rsj;
+
+ /* Parse a comma. */
+ c = phase2_getc ();
+ if (c == ']')
+ break;
+ if (c != ',')
+ goto invalid_json;
+ }
+ }
+ }
+ else if (strcmp (s2, "value") == 0)
+ {
+ /* Parse a string. */
+ enum parse_result r = parse_string ();
+ if (r == pr_none)
+ goto invalid_rsj;
+ if (r == pr_syntax || msgid != NULL)
+ goto invalid_json;
+ msgid = mixed_string_buffer_done (stringbuf);
+ }
+ else
+ goto invalid_rsj;
+
+ free (s2);
+
+ /* Parse a comma. */
+ c = phase2_getc ();
+ if (c == '}')
+ break;
+ if (c != ',')
+ goto invalid_json;
+ }
+ }
+
+ if (location == NULL || msgid == NULL)
+ goto invalid_rsj;
+
+ pos.file_name = location;
+ pos.line_number = (size_t)(-1);
+
+ remember_a_message (mlp, NULL, msgid, null_context, &pos,
+ NULL, NULL);
+
+ /* Parse a comma. */
+ c = phase2_getc ();
+ if (c == ']')
+ break;
+ if (c != ',')
+ goto invalid_json;
+ }
+ }
+ }
+ else
+ goto invalid_rsj;
+
+ /* Parse a comma. */
+ c = phase2_getc ();
+ if (c == '}')
+ break;
+ if (c != ',')
+ goto invalid_json;
+ }
+ }
+
+ /* Seen the closing brace. */
+ c = phase2_getc ();
+ if (c != EOF)
+ goto invalid_json;
+
+ fp = NULL;
+ real_file_name = NULL;
+ logical_file_name = NULL;
+ line_number = 0;
+
+ return;
+
+ invalid_json:
+ error_with_progname = false;
+ error (EXIT_FAILURE, 0, _("%s:%d: invalid JSON syntax"),
+ logical_filename, line_number);
+ error_with_progname = true;
+ return;
+
+ invalid_rsj:
+ error_with_progname = false;
+ error (EXIT_FAILURE, 0, _("%s:%d: invalid RSJ syntax"),
+ logical_filename, line_number);
+ error_with_progname = true;
+ return;
+
+ invalid_rsj_version:
+ error_with_progname = false;
+ error (EXIT_FAILURE, 0,
+ _("%s:%d: invalid RSJ version. Only version 1 is supported."),
+ logical_filename, line_number);
+ error_with_progname = true;
+ return;
+}
-/* xgettext RST backend.
- Copyright (C) 2001-2003, 2006, 2015-2016 Free Software Foundation, Inc.
+/* xgettext RST/RSJ backend.
+ Copyright (C) 2001-2003, 2006, 2015-2016, 2018 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software: you can redistribute it and/or modify
#define EXTENSIONS_RST \
{ "rst", "RST" }, \
+ { "rsj", "RSJ" }, \
#define SCANNERS_RST \
{ "RST", extract_rst, \
- NULL, &formatstring_pascal, NULL, NULL }, \
+ NULL, &formatstring_pascal, NULL, NULL }, \
+ { "RSJ", extract_rsj, \
+ NULL, &formatstring_pascal, NULL, NULL }, \
/* Scan an RST file and add its translatable strings to mdlp. */
extern void extract_rst (FILE *fp, const char *real_filename,
flag_context_list_table_ty *flag_table,
msgdomain_list_ty *mdlp);
+/* Scan an RSJ file and add its translatable strings to mdlp. */
+extern void extract_rsj (FILE *fp, const char *real_filename,
+ const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp);
+
#ifdef __cplusplus
}
/* Extracts strings from C source file to Uniforum style .po file.
- Copyright (C) 1995-1998, 2000-2016 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2016, 2018 Free Software Foundation, Inc.
Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
This program is free software: you can redistribute it and/or modify
(C, C++, ObjectiveC, PO, Shell, Python, Lisp,\n\
EmacsLisp, librep, Scheme, Smalltalk, Java,\n\
JavaProperties, C#, awk, YCP, Tcl, Perl, PHP,\n\
- GCC-source, NXStringTable, RST, Glade, Lua,\n\
- JavaScript, Vala, Desktop)\n"));
+ GCC-source, NXStringTable, RST, RSJ, Glade,\n\
+ Lua, JavaScript, Vala, Desktop)\n"));
printf (_("\
-C, --c++ shorthand for --language=C++\n"));
printf (_("\
xgettext-php-1 xgettext-php-2 xgettext-php-3 xgettext-php-4 \
xgettext-po-1 xgettext-po-2 \
xgettext-properties-1 \
+ xgettext-rst-1 xgettext-rst-2 \
xgettext-python-1 xgettext-python-2 xgettext-python-3 \
xgettext-python-4 \
xgettext-scheme-1 xgettext-scheme-2 xgettext-scheme-3 \
}
: ${XGETTEXT=xgettext}
-${XGETTEXT} -o pascalprog.tmp --omit-header --add-location pascalprog.rst || Exit 1
+# fpc 3.0.0 or newer produces a .rsj file instead of a .rst file.
+if test -f pascalprog.rsj; then
+ suffix=rsj
+else
+ suffix=rst
+fi
+${XGETTEXT} -o pascalprog.tmp --omit-header --add-location pascalprog.${suffix} || Exit 1
LC_ALL=C tr -d '\r' < pascalprog.tmp > pascalprog.pot || Exit 1
cat <<EOF > pascalprog.ok
--- /dev/null
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test extractor of Free Pascal .rst files.
+
+cat <<\EOF > xg-rst-1.rst
+
+# hash value = 153469889
+hello.hello_world='Hello, world!'
+
+
+# hash value = 1323310
+hello.running_as='This program is running as process number %d.'
+
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} -o xg-rst-1.tmp xg-rst-1.rst || Exit 1
+# Don't simplify this to "grep ... < xg-rst-1.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-rst-1.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-rst-1.po
+
+cat <<\EOF > xg-rst-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 <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: hello.hello_world
+msgid "Hello, world!"
+msgstr ""
+
+#: hello.running_as
+#, object-pascal-format
+msgid "This program is running as process number %d."
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-rst-1.ok xg-rst-1.po
+result=$?
+
+exit $result
--- /dev/null
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test extractor of Free Pascal .rsj files.
+
+cat <<\EOF > xg-rst-2-oldstyle.pas
+program oldstyle;
+
+resourcestring
+ testcase = 'Böse Bübchen ärgern über die Maßen';
+
+begin
+end.
+EOF
+
+cat <<\EOF > xg-rst-2-hello.pas
+program hello;
+{$codepage utf8}
+
+resourcestring
+ hello_world = 'Hello, world!';
+ running_as = 'This program is running as process number %d.';
+ russian = 'Russian (Русский): Здравствуйте';
+ vietnamese = 'Vietnamese (Tiếng Việt): Chào bạn';
+ japanese = 'Japanese (日本語): こんにちは';
+ thai = 'Thai (ภาษาไทย): สวัสดีครับ';
+ { fpc 3.0.0 chokes on this: "Error: UTF-8 code greater than 65535 found" }
+ script = '𝒞';
+
+begin
+end.
+EOF
+
+# Result of "iconv -f UTF-8 -t ISO-8859-1 < xg-rst-2-oldstyle.pas > oldstyle.pas ; ppcx64 oldstyle.pas"
+cat <<\EOF > xg-rst-2-oldstyle.rsj
+{"version":1,"strings":[
+{"hash":197750254,"name":"oldstyle.testcase","sourcebytes":[66,246,115,101,32,66,252,98,99,104,101,110,32,228,114,103,101,114,110,32,252,98,101,114,32,100,105,101,32,77,97,223,101,110],"value":"B\u00F6se B\u00FCbchen \u00E4rgern \u00FCber die Ma\u00DFen"}
+]}
+EOF
+
+# Expected result of "ppcx64 xg-rst-2-hello.pas"
+cat <<\EOF > xg-rst-2-hello.rsj
+{"version":1,"strings":[
+{"hash":153469889,"name":"hello.hello_world","sourcebytes":[72,101,108,108,111,44,32,119,111,114,108,100,33],"value":"Hello, world!"},
+{"hash":1323310,"name":"hello.running_as","sourcebytes":[84,104,105,115,32,112,114,111,103,114,97,109,32,105,115,32,114,117,110,110,105,110,103,32,97,115,32,112,114,111,99,101,115,115,32,110,117,109,98,101,114,32,37,100,46],"value":"This program is running as process number %d."},
+{"hash":8471413,"name":"hello.russian","sourcebytes":[82,117,115,115,105,97,110,32,40,208,160,209,131,209,129,209,129,208,186,208,184,208,185,41,58,32,208,151,208,180,209,128,208,176,208,178,209,129,209,130,208,178,209,131,208,185,209,130,208,181],"value":"Russian (\u0420\u0443\u0441\u0441\u043A\u0438\u0439): \u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435"},
+{"hash":12693150,"name":"hello.vietnamese","sourcebytes":[86,105,101,116,110,97,109,101,115,101,32,40,84,105,225,186,191,110,103,32,86,105,225,187,135,116,41,58,32,67,104,195,160,111,32,98,225,186,161,110],"value":"Vietnamese (Ti\u1EBFng Vi\u1EC7t): Ch\u00E0o b\u1EA1n"},
+{"hash":48190495,"name":"hello.japanese","sourcebytes":[74,97,112,97,110,101,115,101,32,40,230,151,165,230,156,172,232,170,158,41,58,32,227,129,147,227,130,147,227,129,171,227,129,161,227,129,175],"value":"Japanese (\u65E5\u672C\u8A9E): \u3053\u3093\u306B\u3061\u306F"},
+{"hash":121047034,"name":"hello.thai","sourcebytes":[84,104,97,105,32,40,224,184,160,224,184,178,224,184,169,224,184,178,224,185,132,224,184,151,224,184,162,41,58,32,224,184,170,224,184,167,224,184,177,224,184,170,224,184,148,224,184,181,224,184,132,224,184,163,224,184,177,224,184,154],"value":"Thai (\u0E20\u0E32\u0E29\u0E32\u0E44\u0E17\u0E22): \u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35\u0E04\u0E23\u0E31\u0E1A"},
+{"hash":123456789,"name":"hello.script","sourcebytes":[240,157,146,158],"value":"\uD835\uDC9E"}
+]}
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} -o xg-rst-2.tmp xg-rst-2-oldstyle.rsj xg-rst-2-hello.rsj || Exit 1
+# Don't simplify this to "grep ... < xg-rst-2.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-rst-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-rst-2.po
+
+cat <<\EOF > xg-rst-2.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 <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: oldstyle.testcase
+msgid "Böse Bübchen ärgern über die Maßen"
+msgstr ""
+
+#: hello.hello_world
+msgid "Hello, world!"
+msgstr ""
+
+#: hello.running_as
+#, object-pascal-format
+msgid "This program is running as process number %d."
+msgstr ""
+
+#: hello.russian
+msgid "Russian (Русский): Здравствуйте"
+msgstr ""
+
+#: hello.vietnamese
+msgid "Vietnamese (Tiếng Việt): Chào bạn"
+msgstr ""
+
+#: hello.japanese
+msgid "Japanese (日本語): こんにちは"
+msgstr ""
+
+#: hello.thai
+msgid "Thai (ภาษาไทย): สวัสดีครับ"
+msgstr ""
+
+#: hello.script
+msgid "𝒞"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-rst-2.ok xg-rst-2.po
+result=$?
+
+exit $result