]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Add support for .rsj files.
authorBruno Haible <bruno@clisp.org>
Fri, 5 Jan 2018 22:42:46 +0000 (23:42 +0100)
committerBruno Haible <bruno@clisp.org>
Fri, 5 Jan 2018 22:42:46 +0000 (23:42 +0100)
* 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.

NEWS
gettext-tools/doc/gettext.texi
gettext-tools/doc/xgettext.texi
gettext-tools/src/x-rst.c
gettext-tools/src/x-rst.h
gettext-tools/src/xgettext.c
gettext-tools/tests/Makefile.am
gettext-tools/tests/lang-pascal
gettext-tools/tests/xgettext-rst-1 [new file with mode: 0644]
gettext-tools/tests/xgettext-rst-2 [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index d13901722510d93d4792587231057688d6d41700..a1eeeb0cba1b402003e4e53bd2c190c04edfe0df 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,9 @@
       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
 
index 046214828efeed05f4ead9b2761e38b47bdb68c2..dfb1fd353c9bcaef536a0d3f7255b2fcbaa37d4a 100644 (file)
@@ -12283,6 +12283,11 @@ gettext
 @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
@@ -12292,7 +12297,7 @@ fpk
 fp-compiler
 
 @item File extension
-@code{rst}
+@code{rst}, @code{rsj}
 
 @item Extractor
 @code{xgettext}, @code{rstconv}
index e2700d9f5b195a9891ebb1ec21be38a9a25262f9..dc67fe6c8dc762d3f54575454bca31a32d3faabc 100644 (file)
@@ -73,8 +73,8 @@ are @code{C}, @code{C++}, @code{ObjectiveC}, @code{PO}, @code{Shell},
 @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++
index acaadf4536b1f395fe18a7c58de7fa9de7db8a2a..5a387a11213e830dc022f48c3f4d35a7a01d107d 100644 (file)
@@ -1,5 +1,5 @@
-/* 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
@@ -234,3 +238,455 @@ extract_rst (FILE *f,
              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;
+}
index 751549830af958f737757f64d5fdd21fc211ca47..657e70de4047690a851cf25f07880b0bca2233f1 100644 (file)
@@ -1,5 +1,5 @@
-/* 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
@@ -29,10 +29,13 @@ extern "C" {
 
 #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,
@@ -40,6 +43,12 @@ 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
 }
index a80ee51ac390117a4b2576d3e1a061ebd22cd3d4..08b52b45152a1e8eda65dc2fc29fad6e0e30f58d 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -1079,8 +1079,8 @@ Choice of input file language:\n"));
                                 (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 (_("\
index 11ed2c9bb9ca12263d1ac8065c021bf6b2a394c3..4f870d900736a2bbfc7680ccc7eddbacaae15201 100644 (file)
@@ -97,6 +97,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
        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 \
index a6781e8d84f857bb3ef107f1bc7900a670387271..cd2e9571557a5e345ef2d62be99902a8843dff52 100755 (executable)
@@ -34,7 +34,13 @@ EOF
 }
 
 : ${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
diff --git a/gettext-tools/tests/xgettext-rst-1 b/gettext-tools/tests/xgettext-rst-1
new file mode 100644 (file)
index 0000000..be1efad
--- /dev/null
@@ -0,0 +1,56 @@
+#! /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
diff --git a/gettext-tools/tests/xgettext-rst-2 b/gettext-tools/tests/xgettext-rst-2
new file mode 100644 (file)
index 0000000..a3bf3e7
--- /dev/null
@@ -0,0 +1,117 @@
+#! /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