ff4ff35918
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
790 lines
25 KiB
C
790 lines
25 KiB
C
/* Writing C# satellite assemblies.
|
|
Copyright (C) 2003-2010, 2016, 2018-2020 Free Software Foundation, Inc.
|
|
Written by Bruno Haible <bruno@clisp.org>, 2003.
|
|
|
|
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 <https://www.gnu.org/licenses/>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
#include <alloca.h>
|
|
|
|
/* Specification. */
|
|
#include "write-csharp.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/stat.h>
|
|
#if !defined S_ISDIR && defined S_IFDIR
|
|
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
|
#endif
|
|
#if !S_IRUSR && S_IREAD
|
|
# define S_IRUSR S_IREAD
|
|
#endif
|
|
#if !S_IRUSR
|
|
# define S_IRUSR 00400
|
|
#endif
|
|
#if !S_IWUSR && S_IWRITE
|
|
# define S_IWUSR S_IWRITE
|
|
#endif
|
|
#if !S_IWUSR
|
|
# define S_IWUSR 00200
|
|
#endif
|
|
#if !S_IXUSR && S_IEXEC
|
|
# define S_IXUSR S_IEXEC
|
|
#endif
|
|
#if !S_IXUSR
|
|
# define S_IXUSR 00100
|
|
#endif
|
|
#if !S_IRGRP
|
|
# define S_IRGRP (S_IRUSR >> 3)
|
|
#endif
|
|
#if !S_IWGRP
|
|
# define S_IWGRP (S_IWUSR >> 3)
|
|
#endif
|
|
#if !S_IXGRP
|
|
# define S_IXGRP (S_IXUSR >> 3)
|
|
#endif
|
|
#if !S_IROTH
|
|
# define S_IROTH (S_IRUSR >> 6)
|
|
#endif
|
|
#if !S_IWOTH
|
|
# define S_IWOTH (S_IWUSR >> 6)
|
|
#endif
|
|
#if !S_IXOTH
|
|
# define S_IXOTH (S_IXUSR >> 6)
|
|
#endif
|
|
|
|
#include "attribute.h"
|
|
#include "c-ctype.h"
|
|
#include "relocatable.h"
|
|
#include "error.h"
|
|
#include "xerror.h"
|
|
#include "csharpcomp.h"
|
|
#include "message.h"
|
|
#include "msgfmt.h"
|
|
#include "msgl-iconv.h"
|
|
#include "msgl-header.h"
|
|
#include "plural-exp.h"
|
|
#include "po-charset.h"
|
|
#include "xalloc.h"
|
|
#include "xmalloca.h"
|
|
#include "concat-filename.h"
|
|
#include "fwriteerror.h"
|
|
#include "clean-temp.h"
|
|
#include "unistr.h"
|
|
#include "gettext.h"
|
|
|
|
#define _(str) gettext (str)
|
|
|
|
|
|
/* Convert a resource name to a class name.
|
|
Return a nonempty string consisting of alphanumerics and underscores
|
|
and starting with a letter or underscore. */
|
|
static char *
|
|
construct_class_name (const char *resource_name)
|
|
{
|
|
/* This code must be kept consistent with intl.cs, function
|
|
GettextResourceManager.ConstructClassName. */
|
|
/* We could just return an arbitrary fixed class name, like "Messages",
|
|
assuming that every assembly will only ever contain one
|
|
GettextResourceSet subclass, but this assumption would break the day
|
|
we want to support multi-domain PO files in the same format... */
|
|
bool valid;
|
|
const char *p;
|
|
|
|
/* Test for a valid ASCII identifier:
|
|
- nonempty,
|
|
- first character is A..Za..z_ - see x-csharp.c:is_identifier_start.
|
|
- next characters are A..Za..z_0..9 - see x-csharp.c:is_identifier_part.
|
|
*/
|
|
valid = (resource_name[0] != '\0');
|
|
for (p = resource_name; valid && *p != '\0'; p++)
|
|
{
|
|
char c = *p;
|
|
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
|
|
|| (p > resource_name && c >= '0' && c <= '9')))
|
|
valid = false;
|
|
}
|
|
if (valid)
|
|
return xstrdup (resource_name);
|
|
else
|
|
{
|
|
static const char hexdigit[] = "0123456789abcdef";
|
|
const char *str = resource_name;
|
|
const char *str_limit = str + strlen (str);
|
|
char *class_name = XNMALLOC (12 + 6 * (str_limit - str) + 1, char);
|
|
char *b;
|
|
|
|
b = class_name;
|
|
memcpy (b, "__UESCAPED__", 12); b += 12;
|
|
while (str < str_limit)
|
|
{
|
|
ucs4_t uc;
|
|
str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
|
|
if (uc >= 0x10000)
|
|
{
|
|
*b++ = '_';
|
|
*b++ = 'U';
|
|
*b++ = hexdigit[(uc >> 28) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 24) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 20) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 16) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 12) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 8) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 4) & 0x0f];
|
|
*b++ = hexdigit[uc & 0x0f];
|
|
}
|
|
else if (!((uc >= 'A' && uc <= 'Z') || (uc >= 'a' && uc <= 'z')
|
|
|| (uc >= '0' && uc <= '9')))
|
|
{
|
|
*b++ = '_';
|
|
*b++ = 'u';
|
|
*b++ = hexdigit[(uc >> 12) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 8) & 0x0f];
|
|
*b++ = hexdigit[(uc >> 4) & 0x0f];
|
|
*b++ = hexdigit[uc & 0x0f];
|
|
}
|
|
else
|
|
*b++ = uc;
|
|
}
|
|
*b++ = '\0';
|
|
return (char *) xrealloc (class_name, b - class_name);
|
|
}
|
|
}
|
|
|
|
|
|
/* Write a string in C# Unicode notation to the given stream. */
|
|
static void
|
|
write_csharp_string (FILE *stream, const char *str)
|
|
{
|
|
static const char hexdigit[] = "0123456789abcdef";
|
|
const char *str_limit = str + strlen (str);
|
|
|
|
fprintf (stream, "\"");
|
|
while (str < str_limit)
|
|
{
|
|
ucs4_t uc;
|
|
str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
|
|
if (uc == 0x0000)
|
|
fprintf (stream, "\\0");
|
|
else if (uc == 0x0007)
|
|
fprintf (stream, "\\a");
|
|
else if (uc == 0x0008)
|
|
fprintf (stream, "\\b");
|
|
else if (uc == 0x0009)
|
|
fprintf (stream, "\\t");
|
|
else if (uc == 0x000a)
|
|
fprintf (stream, "\\n");
|
|
else if (uc == 0x000b)
|
|
fprintf (stream, "\\v");
|
|
else if (uc == 0x000c)
|
|
fprintf (stream, "\\f");
|
|
else if (uc == 0x000d)
|
|
fprintf (stream, "\\r");
|
|
else if (uc == 0x0022)
|
|
fprintf (stream, "\\\"");
|
|
else if (uc == 0x005c)
|
|
fprintf (stream, "\\\\");
|
|
else if (uc >= 0x0020 && uc < 0x007f)
|
|
fprintf (stream, "%c", (int) uc);
|
|
else if (uc < 0x10000)
|
|
fprintf (stream, "\\u%c%c%c%c",
|
|
hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
|
|
hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
|
|
else
|
|
fprintf (stream, "\\U%c%c%c%c%c%c%c%c",
|
|
hexdigit[(uc >> 28) & 0x0f], hexdigit[(uc >> 24) & 0x0f],
|
|
hexdigit[(uc >> 20) & 0x0f], hexdigit[(uc >> 16) & 0x0f],
|
|
hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
|
|
hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
|
|
}
|
|
fprintf (stream, "\"");
|
|
}
|
|
|
|
|
|
/* Write a (msgctxt, msgid) pair as a string in C# Unicode notation to the
|
|
given stream. */
|
|
static void
|
|
write_csharp_msgid (FILE *stream, message_ty *mp)
|
|
{
|
|
const char *msgctxt = mp->msgctxt;
|
|
const char *msgid = mp->msgid;
|
|
|
|
if (msgctxt == NULL)
|
|
write_csharp_string (stream, msgid);
|
|
else
|
|
{
|
|
size_t msgctxt_len = strlen (msgctxt);
|
|
size_t msgid_len = strlen (msgid);
|
|
size_t combined_len = msgctxt_len + 1 + msgid_len;
|
|
char *combined;
|
|
|
|
combined = (char *) xmalloca (combined_len + 1);
|
|
memcpy (combined, msgctxt, msgctxt_len);
|
|
combined[msgctxt_len] = MSGCTXT_SEPARATOR;
|
|
memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1);
|
|
|
|
write_csharp_string (stream, combined);
|
|
|
|
freea (combined);
|
|
}
|
|
}
|
|
|
|
|
|
/* Write C# code that returns the value for a message. If the message
|
|
has plural forms, it is an expression of type System.String[], otherwise it
|
|
is an expression of type System.String. */
|
|
static void
|
|
write_csharp_msgstr (FILE *stream, message_ty *mp)
|
|
{
|
|
if (mp->msgid_plural != NULL)
|
|
{
|
|
bool first;
|
|
const char *p;
|
|
|
|
fprintf (stream, "new System.String[] { ");
|
|
for (p = mp->msgstr, first = true;
|
|
p < mp->msgstr + mp->msgstr_len;
|
|
p += strlen (p) + 1, first = false)
|
|
{
|
|
if (!first)
|
|
fprintf (stream, ", ");
|
|
write_csharp_string (stream, p);
|
|
}
|
|
fprintf (stream, " }");
|
|
}
|
|
else
|
|
{
|
|
if (mp->msgstr_len != strlen (mp->msgstr) + 1)
|
|
abort ();
|
|
|
|
write_csharp_string (stream, mp->msgstr);
|
|
}
|
|
}
|
|
|
|
|
|
/* Tests whether a plural expression, evaluated according to the C rules,
|
|
can only produce the values 0 and 1. */
|
|
static bool
|
|
is_expression_boolean (struct expression *exp)
|
|
{
|
|
switch (exp->operation)
|
|
{
|
|
case var:
|
|
case mult:
|
|
case divide:
|
|
case module:
|
|
case plus:
|
|
case minus:
|
|
return false;
|
|
case lnot:
|
|
case less_than:
|
|
case greater_than:
|
|
case less_or_equal:
|
|
case greater_or_equal:
|
|
case equal:
|
|
case not_equal:
|
|
case land:
|
|
case lor:
|
|
return true;
|
|
case num:
|
|
return (exp->val.num == 0 || exp->val.num == 1);
|
|
case qmop:
|
|
return is_expression_boolean (exp->val.args[1])
|
|
&& is_expression_boolean (exp->val.args[2]);
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
|
|
/* Write C# code that evaluates a plural expression according to the C rules.
|
|
The variable is called 'n'. */
|
|
static void
|
|
write_csharp_expression (FILE *stream, const struct expression *exp, bool as_boolean)
|
|
{
|
|
/* We use parentheses everywhere. This frees us from tracking the priority
|
|
of arithmetic operators. */
|
|
if (as_boolean)
|
|
{
|
|
/* Emit a C# expression of type 'bool'. */
|
|
switch (exp->operation)
|
|
{
|
|
case num:
|
|
fprintf (stream, "%s", exp->val.num ? "true" : "false");
|
|
return;
|
|
case lnot:
|
|
fprintf (stream, "(!");
|
|
write_csharp_expression (stream, exp->val.args[0], true);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case less_than:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " < ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case greater_than:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " > ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case less_or_equal:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " <= ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case greater_or_equal:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " >= ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case equal:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " == ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case not_equal:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " != ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case land:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], true);
|
|
fprintf (stream, " && ");
|
|
write_csharp_expression (stream, exp->val.args[1], true);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case lor:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], true);
|
|
fprintf (stream, " || ");
|
|
write_csharp_expression (stream, exp->val.args[1], true);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case qmop:
|
|
if (is_expression_boolean (exp->val.args[1])
|
|
&& is_expression_boolean (exp->val.args[2]))
|
|
{
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], true);
|
|
fprintf (stream, " ? ");
|
|
write_csharp_expression (stream, exp->val.args[1], true);
|
|
fprintf (stream, " : ");
|
|
write_csharp_expression (stream, exp->val.args[2], true);
|
|
fprintf (stream, ")");
|
|
return;
|
|
}
|
|
FALLTHROUGH;
|
|
case var:
|
|
case mult:
|
|
case divide:
|
|
case module:
|
|
case plus:
|
|
case minus:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp, false);
|
|
fprintf (stream, " != 0)");
|
|
return;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Emit a C# expression of type 'long'. */
|
|
switch (exp->operation)
|
|
{
|
|
case var:
|
|
fprintf (stream, "n");
|
|
return;
|
|
case num:
|
|
fprintf (stream, "%lu", exp->val.num);
|
|
return;
|
|
case mult:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " * ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case divide:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " / ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case module:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " %% ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case plus:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " + ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case minus:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], false);
|
|
fprintf (stream, " - ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case qmop:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp->val.args[0], true);
|
|
fprintf (stream, " ? ");
|
|
write_csharp_expression (stream, exp->val.args[1], false);
|
|
fprintf (stream, " : ");
|
|
write_csharp_expression (stream, exp->val.args[2], false);
|
|
fprintf (stream, ")");
|
|
return;
|
|
case lnot:
|
|
case less_than:
|
|
case greater_than:
|
|
case less_or_equal:
|
|
case greater_or_equal:
|
|
case equal:
|
|
case not_equal:
|
|
case land:
|
|
case lor:
|
|
fprintf (stream, "(");
|
|
write_csharp_expression (stream, exp, true);
|
|
fprintf (stream, " ? 1 : 0)");
|
|
return;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Write the C# code for the GettextResourceSet subclass to the given stream.
|
|
Note that we use fully qualified class names and no "using" statements,
|
|
because applications can have their own classes called X.Y.Hashtable or
|
|
X.Y.String. */
|
|
static void
|
|
write_csharp_code (FILE *stream, const char *culture_name, const char *class_name, message_list_ty *mlp)
|
|
{
|
|
const char *last_dot;
|
|
const char *class_name_last_part;
|
|
unsigned int plurals;
|
|
size_t j;
|
|
|
|
fprintf (stream,
|
|
"/* Automatically generated by GNU msgfmt. Do not modify! */\n");
|
|
|
|
/* We chose to use a "using" statement here, to avoid a bug in the pnet-0.6.0
|
|
compiler. */
|
|
fprintf (stream, "using GNU.Gettext;\n");
|
|
|
|
/* Assign a strong name to the assembly, so that two different localizations
|
|
of the same domain can be loaded one after the other. This strong name
|
|
tells the Global Assembly Cache that they are meant to be different. */
|
|
fprintf (stream, "[assembly: System.Reflection.AssemblyCulture(");
|
|
write_csharp_string (stream, culture_name);
|
|
fprintf (stream, ")]\n");
|
|
|
|
last_dot = strrchr (class_name, '.');
|
|
if (last_dot != NULL)
|
|
{
|
|
fprintf (stream, "namespace ");
|
|
fwrite (class_name, 1, last_dot - class_name, stream);
|
|
fprintf (stream, " {\n");
|
|
class_name_last_part = last_dot + 1;
|
|
}
|
|
else
|
|
class_name_last_part = class_name;
|
|
fprintf (stream, "public class %s : GettextResourceSet {\n",
|
|
class_name_last_part);
|
|
|
|
/* Determine whether there are plural messages. */
|
|
plurals = 0;
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
if (mlp->item[j]->msgid_plural != NULL)
|
|
plurals++;
|
|
|
|
/* Emit the constructor. */
|
|
fprintf (stream, " public %s ()\n", class_name_last_part);
|
|
fprintf (stream, " : base () {\n");
|
|
fprintf (stream, " }\n");
|
|
|
|
/* Emit the TableInitialized field. */
|
|
fprintf (stream, " private bool TableInitialized;\n");
|
|
|
|
/* Emit the ReadResources method. */
|
|
fprintf (stream, " protected override void ReadResources () {\n");
|
|
/* In some implementations, such as mono < 2009-02-27, the ReadResources
|
|
method is called just once, when Table == null. In other implementations,
|
|
such as mono >= 2009-02-27, it is called at every GetObject call, and it
|
|
is responsible for doing the initialization only once, even when called
|
|
simultaneously from multiple threads. */
|
|
fprintf (stream, " if (!TableInitialized) {\n");
|
|
fprintf (stream, " lock (this) {\n");
|
|
fprintf (stream, " if (!TableInitialized) {\n");
|
|
/* In some implementations, the ResourceSet constructor initializes Table
|
|
before calling ReadResources(). In other implementations, the
|
|
ReadResources() method is expected to initialize the Table. */
|
|
fprintf (stream, " if (Table == null)\n");
|
|
fprintf (stream, " Table = new System.Collections.Hashtable();\n");
|
|
fprintf (stream, " System.Collections.Hashtable t = Table;\n");
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
fprintf (stream, " t.Add(");
|
|
write_csharp_msgid (stream, mlp->item[j]);
|
|
fprintf (stream, ",");
|
|
write_csharp_msgstr (stream, mlp->item[j]);
|
|
fprintf (stream, ");\n");
|
|
}
|
|
fprintf (stream, " TableInitialized = true;\n");
|
|
fprintf (stream, " }\n");
|
|
fprintf (stream, " }\n");
|
|
fprintf (stream, " }\n");
|
|
fprintf (stream, " }\n");
|
|
|
|
/* Emit the msgid_plural strings. Only used by msgunfmt. */
|
|
if (plurals)
|
|
{
|
|
fprintf (stream, " public static System.Collections.Hashtable GetMsgidPluralTable () {\n");
|
|
fprintf (stream, " System.Collections.Hashtable t = new System.Collections.Hashtable();\n");
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
if (mlp->item[j]->msgid_plural != NULL)
|
|
{
|
|
fprintf (stream, " t.Add(");
|
|
write_csharp_msgid (stream, mlp->item[j]);
|
|
fprintf (stream, ",");
|
|
write_csharp_string (stream, mlp->item[j]->msgid_plural);
|
|
fprintf (stream, ");\n");
|
|
}
|
|
fprintf (stream, " return t;\n");
|
|
fprintf (stream, " }\n");
|
|
}
|
|
|
|
/* Emit the PluralEval function. It is a subroutine for GetPluralString. */
|
|
if (plurals)
|
|
{
|
|
message_ty *header_entry;
|
|
const struct expression *plural;
|
|
unsigned long int nplurals;
|
|
|
|
header_entry = message_list_search (mlp, NULL, "");
|
|
extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
|
|
&plural, &nplurals);
|
|
|
|
fprintf (stream, " protected override long PluralEval (long n) {\n");
|
|
fprintf (stream, " return ");
|
|
write_csharp_expression (stream, plural, false);
|
|
fprintf (stream, ";\n");
|
|
fprintf (stream, " }\n");
|
|
}
|
|
|
|
/* Terminate the class. */
|
|
fprintf (stream, "}\n");
|
|
|
|
if (last_dot != NULL)
|
|
/* Terminate the namespace. */
|
|
fprintf (stream, "}\n");
|
|
}
|
|
|
|
|
|
int
|
|
msgdomain_write_csharp (message_list_ty *mlp, const char *canon_encoding,
|
|
const char *resource_name, const char *locale_name,
|
|
const char *directory)
|
|
{
|
|
int retval;
|
|
struct temp_dir *tmpdir;
|
|
char *culture_name;
|
|
char *output_file;
|
|
char *class_name;
|
|
char *csharp_file_name;
|
|
FILE *csharp_file;
|
|
const char *gettextlibdir;
|
|
const char *csharp_sources[1];
|
|
const char *libdirs[1];
|
|
const char *libraries[1];
|
|
|
|
/* If no entry for this resource/domain, don't even create the file. */
|
|
if (mlp->nitems == 0)
|
|
return 0;
|
|
|
|
retval = 1;
|
|
|
|
/* Convert the messages to Unicode. */
|
|
iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
|
|
|
|
/* Support for "reproducible builds": Delete information that may vary
|
|
between builds in the same conditions. */
|
|
message_list_delete_header_field (mlp, "POT-Creation-Date:");
|
|
|
|
/* Create a temporary directory where we can put the C# file.
|
|
A simple temporary file would also be possible but would require us to
|
|
define our own variant of mkstemp(): On one hand the functions mktemp(),
|
|
tmpnam(), tempnam() present a security risk, and on the other hand the
|
|
function mkstemp() doesn't allow to specify a fixed suffix of the file.
|
|
It is simpler to create a temporary directory. */
|
|
tmpdir = create_temp_dir ("msg", NULL, false);
|
|
if (tmpdir == NULL)
|
|
goto quit1;
|
|
|
|
/* Assign a default value to the resource name. */
|
|
if (resource_name == NULL)
|
|
resource_name = "Messages";
|
|
|
|
/* Convert the locale name to a .NET specific culture name. */
|
|
culture_name = xstrdup (locale_name);
|
|
{
|
|
char *p;
|
|
for (p = culture_name; *p != '\0'; p++)
|
|
if (*p == '_')
|
|
*p = '-';
|
|
if (strncmp (culture_name, "sr-CS", 5) == 0)
|
|
memcpy (culture_name, "sr-SP", 5);
|
|
p = strchr (culture_name, '@');
|
|
if (p != NULL)
|
|
{
|
|
if (strcmp (p, "@latin") == 0)
|
|
strcpy (p, "-Latn");
|
|
else if (strcmp (p, "@cyrillic") == 0)
|
|
strcpy (p, "-Cyrl");
|
|
}
|
|
if (strcmp (culture_name, "sr-SP") == 0)
|
|
{
|
|
free (culture_name);
|
|
culture_name = xstrdup ("sr-SP-Latn");
|
|
}
|
|
else if (strcmp (culture_name, "uz-UZ") == 0)
|
|
{
|
|
free (culture_name);
|
|
culture_name = xstrdup ("uz-UZ-Latn");
|
|
}
|
|
}
|
|
|
|
/* Compute the output file name. This code must be kept consistent with
|
|
intl.cs, function GetSatelliteAssembly(). */
|
|
{
|
|
char *output_dir = xconcatenated_filename (directory, culture_name, NULL);
|
|
struct stat statbuf;
|
|
|
|
/* Try to create the output directory if it does not yet exist. */
|
|
if (stat (output_dir, &statbuf) < 0 && errno == ENOENT)
|
|
if (mkdir (output_dir, S_IRUSR | S_IWUSR | S_IXUSR
|
|
| S_IRGRP | S_IWGRP | S_IXGRP
|
|
| S_IROTH | S_IWOTH | S_IXOTH) < 0)
|
|
{
|
|
error (0, errno, _("failed to create directory \"%s\""), output_dir);
|
|
free (output_dir);
|
|
goto quit2;
|
|
}
|
|
|
|
output_file =
|
|
xconcatenated_filename (output_dir, resource_name, ".resources.dll");
|
|
|
|
free (output_dir);
|
|
}
|
|
|
|
/* Compute the class name. This code must be kept consistent with intl.cs,
|
|
function InstantiateResourceSet(). */
|
|
{
|
|
char *class_name_part1 = construct_class_name (resource_name);
|
|
char *p;
|
|
|
|
class_name =
|
|
XNMALLOC (strlen (class_name_part1) + 1 + strlen (culture_name) + 1, char);
|
|
sprintf (class_name, "%s_%s", class_name_part1, culture_name);
|
|
for (p = class_name + strlen (class_name_part1) + 1; *p != '\0'; p++)
|
|
if (*p == '-')
|
|
*p = '_';
|
|
free (class_name_part1);
|
|
}
|
|
|
|
/* Compute the temporary C# file name. It must end in ".cs", so that
|
|
the C# compiler recognizes that it is C# source code. */
|
|
csharp_file_name =
|
|
xconcatenated_filename (tmpdir->dir_name, "resset.cs", NULL);
|
|
|
|
/* Create the C# file. */
|
|
register_temp_file (tmpdir, csharp_file_name);
|
|
csharp_file = fopen_temp (csharp_file_name, "w", false);
|
|
if (csharp_file == NULL)
|
|
{
|
|
error (0, errno, _("failed to create \"%s\""), csharp_file_name);
|
|
unregister_temp_file (tmpdir, csharp_file_name);
|
|
goto quit3;
|
|
}
|
|
|
|
write_csharp_code (csharp_file, culture_name, class_name, mlp);
|
|
|
|
if (fwriteerror_temp (csharp_file))
|
|
{
|
|
error (0, errno, _("error while writing \"%s\" file"), csharp_file_name);
|
|
goto quit3;
|
|
}
|
|
|
|
/* Make it possible to override the .dll location. This is
|
|
necessary for running the testsuite before "make install". */
|
|
gettextlibdir = getenv ("GETTEXTCSHARPLIBDIR");
|
|
if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
|
|
gettextlibdir = relocate (LIBDIR);
|
|
|
|
/* Compile the C# file to a .dll file. */
|
|
csharp_sources[0] = csharp_file_name;
|
|
libdirs[0] = gettextlibdir;
|
|
libraries[0] = "GNU.Gettext";
|
|
if (compile_csharp_class (csharp_sources, 1, libdirs, 1, libraries, 1,
|
|
output_file, true, false, verbose > 0))
|
|
{
|
|
if (!verbose)
|
|
error (0, 0, _("compilation of C# class failed, please try --verbose"));
|
|
else
|
|
error (0, 0, _("compilation of C# class failed"));
|
|
goto quit3;
|
|
}
|
|
|
|
retval = 0;
|
|
|
|
quit3:
|
|
free (csharp_file_name);
|
|
free (class_name);
|
|
free (output_file);
|
|
quit2:
|
|
free (culture_name);
|
|
cleanup_temp_dir (tmpdir);
|
|
quit1:
|
|
return retval;
|
|
}
|