Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / write-csharp.c
1 /* Writing C# satellite assemblies.
2    Copyright (C) 2003-2010, 2015 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2003.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 #include <alloca.h>
22
23 /* Specification.  */
24 #include "write-csharp.h"
25
26 #include <errno.h>
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31
32 #include <sys/stat.h>
33 #if !defined S_ISDIR && defined S_IFDIR
34 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
35 #endif
36 #if !S_IRUSR && S_IREAD
37 # define S_IRUSR S_IREAD
38 #endif
39 #if !S_IRUSR
40 # define S_IRUSR 00400
41 #endif
42 #if !S_IWUSR && S_IWRITE
43 # define S_IWUSR S_IWRITE
44 #endif
45 #if !S_IWUSR
46 # define S_IWUSR 00200
47 #endif
48 #if !S_IXUSR && S_IEXEC
49 # define S_IXUSR S_IEXEC
50 #endif
51 #if !S_IXUSR
52 # define S_IXUSR 00100
53 #endif
54 #if !S_IRGRP
55 # define S_IRGRP (S_IRUSR >> 3)
56 #endif
57 #if !S_IWGRP
58 # define S_IWGRP (S_IWUSR >> 3)
59 #endif
60 #if !S_IXGRP
61 # define S_IXGRP (S_IXUSR >> 3)
62 #endif
63 #if !S_IROTH
64 # define S_IROTH (S_IRUSR >> 6)
65 #endif
66 #if !S_IWOTH
67 # define S_IWOTH (S_IWUSR >> 6)
68 #endif
69 #if !S_IXOTH
70 # define S_IXOTH (S_IXUSR >> 6)
71 #endif
72
73 #include "c-ctype.h"
74 #include "relocatable.h"
75 #include "error.h"
76 #include "xerror.h"
77 #include "csharpcomp.h"
78 #include "message.h"
79 #include "msgfmt.h"
80 #include "msgl-iconv.h"
81 #include "plural-exp.h"
82 #include "po-charset.h"
83 #include "xalloc.h"
84 #include "xmalloca.h"
85 #include "concat-filename.h"
86 #include "fwriteerror.h"
87 #include "clean-temp.h"
88 #include "unistr.h"
89 #include "gettext.h"
90
91 #define _(str) gettext (str)
92
93
94 /* Convert a resource name to a class name.
95    Return a nonempty string consisting of alphanumerics and underscores
96    and starting with a letter or underscore.  */
97 static char *
98 construct_class_name (const char *resource_name)
99 {
100   /* This code must be kept consistent with intl.cs, function
101      GettextResourceManager.ConstructClassName.  */
102   /* We could just return an arbitrary fixed class name, like "Messages",
103      assuming that every assembly will only ever contain one
104      GettextResourceSet subclass, but this assumption would break the day
105      we want to support multi-domain PO files in the same format...  */
106   bool valid;
107   const char *p;
108
109   /* Test for a valid ASCII identifier:
110      - nonempty,
111      - first character is A..Za..z_ - see x-csharp.c:is_identifier_start.
112      - next characters are A..Za..z_0..9 - see x-csharp.c:is_identifier_part.
113    */
114   valid = (resource_name[0] != '\0');
115   for (p = resource_name; valid && *p != '\0'; p++)
116     {
117       char c = *p;
118       if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
119             || (p > resource_name && c >= '0' && c <= '9')))
120         valid = false;
121     }
122   if (valid)
123     return xstrdup (resource_name);
124   else
125     {
126       static const char hexdigit[] = "0123456789abcdef";
127       const char *str = resource_name;
128       const char *str_limit = str + strlen (str);
129       char *class_name = XNMALLOC (12 + 6 * (str_limit - str) + 1, char);
130       char *b;
131
132       b = class_name;
133       memcpy (b, "__UESCAPED__", 12); b += 12;
134       while (str < str_limit)
135         {
136           ucs4_t uc;
137           str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
138           if (uc >= 0x10000)
139             {
140               *b++ = '_';
141               *b++ = 'U';
142               *b++ = hexdigit[(uc >> 28) & 0x0f];
143               *b++ = hexdigit[(uc >> 24) & 0x0f];
144               *b++ = hexdigit[(uc >> 20) & 0x0f];
145               *b++ = hexdigit[(uc >> 16) & 0x0f];
146               *b++ = hexdigit[(uc >> 12) & 0x0f];
147               *b++ = hexdigit[(uc >> 8) & 0x0f];
148               *b++ = hexdigit[(uc >> 4) & 0x0f];
149               *b++ = hexdigit[uc & 0x0f];
150             }
151           else if (!((uc >= 'A' && uc <= 'Z') || (uc >= 'a' && uc <= 'z')
152                      || (uc >= '0' && uc <= '9')))
153             {
154               *b++ = '_';
155               *b++ = 'u';
156               *b++ = hexdigit[(uc >> 12) & 0x0f];
157               *b++ = hexdigit[(uc >> 8) & 0x0f];
158               *b++ = hexdigit[(uc >> 4) & 0x0f];
159               *b++ = hexdigit[uc & 0x0f];
160             }
161           else
162             *b++ = uc;
163         }
164       *b++ = '\0';
165       return (char *) xrealloc (class_name, b - class_name);
166     }
167 }
168
169
170 /* Write a string in C# Unicode notation to the given stream.  */
171 static void
172 write_csharp_string (FILE *stream, const char *str)
173 {
174   static const char hexdigit[] = "0123456789abcdef";
175   const char *str_limit = str + strlen (str);
176
177   fprintf (stream, "\"");
178   while (str < str_limit)
179     {
180       ucs4_t uc;
181       str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
182       if (uc == 0x0000)
183         fprintf (stream, "\\0");
184       else if (uc == 0x0007)
185         fprintf (stream, "\\a");
186       else if (uc == 0x0008)
187         fprintf (stream, "\\b");
188       else if (uc == 0x0009)
189         fprintf (stream, "\\t");
190       else if (uc == 0x000a)
191         fprintf (stream, "\\n");
192       else if (uc == 0x000b)
193         fprintf (stream, "\\v");
194       else if (uc == 0x000c)
195         fprintf (stream, "\\f");
196       else if (uc == 0x000d)
197         fprintf (stream, "\\r");
198       else if (uc == 0x0022)
199         fprintf (stream, "\\\"");
200       else if (uc == 0x005c)
201         fprintf (stream, "\\\\");
202       else if (uc >= 0x0020 && uc < 0x007f)
203         fprintf (stream, "%c", (int) uc);
204       else if (uc < 0x10000)
205         fprintf (stream, "\\u%c%c%c%c",
206                  hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
207                  hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
208       else
209         fprintf (stream, "\\U%c%c%c%c%c%c%c%c",
210                  hexdigit[(uc >> 28) & 0x0f], hexdigit[(uc >> 24) & 0x0f],
211                  hexdigit[(uc >> 20) & 0x0f], hexdigit[(uc >> 16) & 0x0f],
212                  hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
213                  hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
214     }
215   fprintf (stream, "\"");
216 }
217
218
219 /* Write a (msgctxt, msgid) pair as a string in C# Unicode notation to the
220    given stream.  */
221 static void
222 write_csharp_msgid (FILE *stream, message_ty *mp)
223 {
224   const char *msgctxt = mp->msgctxt;
225   const char *msgid = mp->msgid;
226
227   if (msgctxt == NULL)
228     write_csharp_string (stream, msgid);
229   else
230     {
231       size_t msgctxt_len = strlen (msgctxt);
232       size_t msgid_len = strlen (msgid);
233       size_t combined_len = msgctxt_len + 1 + msgid_len;
234       char *combined;
235
236       combined = (char *) xmalloca (combined_len);
237       memcpy (combined, msgctxt, msgctxt_len);
238       combined[msgctxt_len] = MSGCTXT_SEPARATOR;
239       memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1);
240
241       write_csharp_string (stream, combined);
242
243       freea (combined);
244     }
245 }
246
247
248 /* Write C# code that returns the value for a message.  If the message
249    has plural forms, it is an expression of type System.String[], otherwise it
250    is an expression of type System.String.  */
251 static void
252 write_csharp_msgstr (FILE *stream, message_ty *mp)
253 {
254   if (mp->msgid_plural != NULL)
255     {
256       bool first;
257       const char *p;
258
259       fprintf (stream, "new System.String[] { ");
260       for (p = mp->msgstr, first = true;
261            p < mp->msgstr + mp->msgstr_len;
262            p += strlen (p) + 1, first = false)
263         {
264           if (!first)
265             fprintf (stream, ", ");
266           write_csharp_string (stream, p);
267         }
268       fprintf (stream, " }");
269     }
270   else
271     {
272       if (mp->msgstr_len != strlen (mp->msgstr) + 1)
273         abort ();
274
275       write_csharp_string (stream, mp->msgstr);
276     }
277 }
278
279
280 /* Tests whether a plural expression, evaluated according to the C rules,
281    can only produce the values 0 and 1.  */
282 static bool
283 is_expression_boolean (struct expression *exp)
284 {
285   switch (exp->operation)
286     {
287     case var:
288     case mult:
289     case divide:
290     case module:
291     case plus:
292     case minus:
293       return false;
294     case lnot:
295     case less_than:
296     case greater_than:
297     case less_or_equal:
298     case greater_or_equal:
299     case equal:
300     case not_equal:
301     case land:
302     case lor:
303       return true;
304     case num:
305       return (exp->val.num == 0 || exp->val.num == 1);
306     case qmop:
307       return is_expression_boolean (exp->val.args[1])
308              && is_expression_boolean (exp->val.args[2]);
309     default:
310       abort ();
311     }
312 }
313
314
315 /* Write C# code that evaluates a plural expression according to the C rules.
316    The variable is called 'n'.  */
317 static void
318 write_csharp_expression (FILE *stream, const struct expression *exp, bool as_boolean)
319 {
320   /* We use parentheses everywhere.  This frees us from tracking the priority
321      of arithmetic operators.  */
322   if (as_boolean)
323     {
324       /* Emit a C# expression of type 'bool'.  */
325       switch (exp->operation)
326         {
327         case num:
328           fprintf (stream, "%s", exp->val.num ? "true" : "false");
329           return;
330         case lnot:
331           fprintf (stream, "(!");
332           write_csharp_expression (stream, exp->val.args[0], true);
333           fprintf (stream, ")");
334           return;
335         case less_than:
336           fprintf (stream, "(");
337           write_csharp_expression (stream, exp->val.args[0], false);
338           fprintf (stream, " < ");
339           write_csharp_expression (stream, exp->val.args[1], false);
340           fprintf (stream, ")");
341           return;
342         case greater_than:
343           fprintf (stream, "(");
344           write_csharp_expression (stream, exp->val.args[0], false);
345           fprintf (stream, " > ");
346           write_csharp_expression (stream, exp->val.args[1], false);
347           fprintf (stream, ")");
348           return;
349         case less_or_equal:
350           fprintf (stream, "(");
351           write_csharp_expression (stream, exp->val.args[0], false);
352           fprintf (stream, " <= ");
353           write_csharp_expression (stream, exp->val.args[1], false);
354           fprintf (stream, ")");
355           return;
356         case greater_or_equal:
357           fprintf (stream, "(");
358           write_csharp_expression (stream, exp->val.args[0], false);
359           fprintf (stream, " >= ");
360           write_csharp_expression (stream, exp->val.args[1], false);
361           fprintf (stream, ")");
362           return;
363         case equal:
364           fprintf (stream, "(");
365           write_csharp_expression (stream, exp->val.args[0], false);
366           fprintf (stream, " == ");
367           write_csharp_expression (stream, exp->val.args[1], false);
368           fprintf (stream, ")");
369           return;
370         case not_equal:
371           fprintf (stream, "(");
372           write_csharp_expression (stream, exp->val.args[0], false);
373           fprintf (stream, " != ");
374           write_csharp_expression (stream, exp->val.args[1], false);
375           fprintf (stream, ")");
376           return;
377         case land:
378           fprintf (stream, "(");
379           write_csharp_expression (stream, exp->val.args[0], true);
380           fprintf (stream, " && ");
381           write_csharp_expression (stream, exp->val.args[1], true);
382           fprintf (stream, ")");
383           return;
384         case lor:
385           fprintf (stream, "(");
386           write_csharp_expression (stream, exp->val.args[0], true);
387           fprintf (stream, " || ");
388           write_csharp_expression (stream, exp->val.args[1], true);
389           fprintf (stream, ")");
390           return;
391         case qmop:
392           if (is_expression_boolean (exp->val.args[1])
393               && is_expression_boolean (exp->val.args[2]))
394             {
395               fprintf (stream, "(");
396               write_csharp_expression (stream, exp->val.args[0], true);
397               fprintf (stream, " ? ");
398               write_csharp_expression (stream, exp->val.args[1], true);
399               fprintf (stream, " : ");
400               write_csharp_expression (stream, exp->val.args[2], true);
401               fprintf (stream, ")");
402               return;
403             }
404           /*FALLTHROUGH*/
405         case var:
406         case mult:
407         case divide:
408         case module:
409         case plus:
410         case minus:
411           fprintf (stream, "(");
412           write_csharp_expression (stream, exp, false);
413           fprintf (stream, " != 0)");
414           return;
415         default:
416           abort ();
417         }
418     }
419   else
420     {
421       /* Emit a C# expression of type 'long'.  */
422       switch (exp->operation)
423         {
424         case var:
425           fprintf (stream, "n");
426           return;
427         case num:
428           fprintf (stream, "%lu", exp->val.num);
429           return;
430         case mult:
431           fprintf (stream, "(");
432           write_csharp_expression (stream, exp->val.args[0], false);
433           fprintf (stream, " * ");
434           write_csharp_expression (stream, exp->val.args[1], false);
435           fprintf (stream, ")");
436           return;
437         case divide:
438           fprintf (stream, "(");
439           write_csharp_expression (stream, exp->val.args[0], false);
440           fprintf (stream, " / ");
441           write_csharp_expression (stream, exp->val.args[1], false);
442           fprintf (stream, ")");
443           return;
444         case module:
445           fprintf (stream, "(");
446           write_csharp_expression (stream, exp->val.args[0], false);
447           fprintf (stream, " %% ");
448           write_csharp_expression (stream, exp->val.args[1], false);
449           fprintf (stream, ")");
450           return;
451         case plus:
452           fprintf (stream, "(");
453           write_csharp_expression (stream, exp->val.args[0], false);
454           fprintf (stream, " + ");
455           write_csharp_expression (stream, exp->val.args[1], false);
456           fprintf (stream, ")");
457           return;
458         case minus:
459           fprintf (stream, "(");
460           write_csharp_expression (stream, exp->val.args[0], false);
461           fprintf (stream, " - ");
462           write_csharp_expression (stream, exp->val.args[1], false);
463           fprintf (stream, ")");
464           return;
465         case qmop:
466           fprintf (stream, "(");
467           write_csharp_expression (stream, exp->val.args[0], true);
468           fprintf (stream, " ? ");
469           write_csharp_expression (stream, exp->val.args[1], false);
470           fprintf (stream, " : ");
471           write_csharp_expression (stream, exp->val.args[2], false);
472           fprintf (stream, ")");
473           return;
474         case lnot:
475         case less_than:
476         case greater_than:
477         case less_or_equal:
478         case greater_or_equal:
479         case equal:
480         case not_equal:
481         case land:
482         case lor:
483           fprintf (stream, "(");
484           write_csharp_expression (stream, exp, true);
485           fprintf (stream, " ? 1 : 0)");
486           return;
487         default:
488           abort ();
489         }
490     }
491 }
492
493
494 /* Write the C# code for the GettextResourceSet subclass to the given stream.
495    Note that we use fully qualified class names and no "using" statements,
496    because applications can have their own classes called X.Y.Hashtable or
497    X.Y.String.  */
498 static void
499 write_csharp_code (FILE *stream, const char *culture_name, const char *class_name, message_list_ty *mlp)
500 {
501   const char *last_dot;
502   const char *class_name_last_part;
503   unsigned int plurals;
504   size_t j;
505
506   fprintf (stream,
507            "/* Automatically generated by GNU msgfmt.  Do not modify!  */\n");
508
509   /* We have to use a "using" statement here, to avoid a bug in the pnet-0.6.0
510      compiler.  */
511   fprintf (stream, "using GNU.Gettext;\n");
512
513   /* Assign a strong name to the assembly, so that two different localizations
514      of the same domain can be loaded one after the other.  This strong name
515      tells the Global Assembly Cache that they are meant to be different.  */
516   fprintf (stream, "[assembly: System.Reflection.AssemblyCulture(");
517   write_csharp_string (stream, culture_name);
518   fprintf (stream, ")]\n");
519
520   last_dot = strrchr (class_name, '.');
521   if (last_dot != NULL)
522     {
523       fprintf (stream, "namespace ");
524       fwrite (class_name, 1, last_dot - class_name, stream);
525       fprintf (stream, " {\n");
526       class_name_last_part = last_dot + 1;
527     }
528   else
529     class_name_last_part = class_name;
530   fprintf (stream, "public class %s : GettextResourceSet {\n",
531            class_name_last_part);
532
533   /* Determine whether there are plural messages.  */
534   plurals = 0;
535   for (j = 0; j < mlp->nitems; j++)
536     if (mlp->item[j]->msgid_plural != NULL)
537       plurals++;
538
539   /* Emit the constructor.  */
540   fprintf (stream, "  public %s ()\n", class_name_last_part);
541   fprintf (stream, "    : base () {\n");
542   fprintf (stream, "  }\n");
543
544   /* Emit the TableInitialized field.  */
545   fprintf (stream, "  private bool TableInitialized;\n");
546
547   /* Emit the ReadResources method.  */
548   fprintf (stream, "  protected override void ReadResources () {\n");
549   /* In some implementations, such as mono < 2009-02-27, the ReadResources
550      method is called just once, when Table == null.  In other implementations,
551      such as mono >= 2009-02-27, it is called at every GetObject call, and it
552      is responsible for doing the initialization only once, even when called
553      simultaneously from multiple threads.  */
554   fprintf (stream, "    if (!TableInitialized) {\n");
555   fprintf (stream, "      lock (this) {\n");
556   fprintf (stream, "        if (!TableInitialized) {\n");
557   /* In some implementations, the ResourceSet constructor initializes Table
558      before calling ReadResources().  In other implementations, the
559      ReadResources() method is expected to initialize the Table.  */
560   fprintf (stream, "          if (Table == null)\n");
561   fprintf (stream, "            Table = new System.Collections.Hashtable();\n");
562   fprintf (stream, "          System.Collections.Hashtable t = Table;\n");
563   for (j = 0; j < mlp->nitems; j++)
564     {
565       fprintf (stream, "          t.Add(");
566       write_csharp_msgid (stream, mlp->item[j]);
567       fprintf (stream, ",");
568       write_csharp_msgstr (stream, mlp->item[j]);
569       fprintf (stream, ");\n");
570     }
571   fprintf (stream, "          TableInitialized = true;\n");
572   fprintf (stream, "        }\n");
573   fprintf (stream, "      }\n");
574   fprintf (stream, "    }\n");
575   fprintf (stream, "  }\n");
576
577   /* Emit the msgid_plural strings.  Only used by msgunfmt.  */
578   if (plurals)
579     {
580       fprintf (stream, "  public static System.Collections.Hashtable GetMsgidPluralTable () {\n");
581       fprintf (stream, "    System.Collections.Hashtable t = new System.Collections.Hashtable();\n");
582       for (j = 0; j < mlp->nitems; j++)
583         if (mlp->item[j]->msgid_plural != NULL)
584           {
585             fprintf (stream, "    t.Add(");
586             write_csharp_msgid (stream, mlp->item[j]);
587             fprintf (stream, ",");
588             write_csharp_string (stream, mlp->item[j]->msgid_plural);
589             fprintf (stream, ");\n");
590           }
591       fprintf (stream, "    return t;\n");
592       fprintf (stream, "  }\n");
593     }
594
595   /* Emit the PluralEval function.  It is a subroutine for GetPluralString.  */
596   if (plurals)
597     {
598       message_ty *header_entry;
599       const struct expression *plural;
600       unsigned long int nplurals;
601
602       header_entry = message_list_search (mlp, NULL, "");
603       extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
604                                  &plural, &nplurals);
605
606       fprintf (stream, "  protected override long PluralEval (long n) {\n");
607       fprintf (stream, "    return ");
608       write_csharp_expression (stream, plural, false);
609       fprintf (stream, ";\n");
610       fprintf (stream, "  }\n");
611     }
612
613   /* Terminate the class.  */
614   fprintf (stream, "}\n");
615
616   if (last_dot != NULL)
617     /* Terminate the namespace.  */
618     fprintf (stream, "}\n");
619 }
620
621
622 int
623 msgdomain_write_csharp (message_list_ty *mlp, const char *canon_encoding,
624                         const char *resource_name, const char *locale_name,
625                         const char *directory)
626 {
627   int retval;
628   struct temp_dir *tmpdir;
629   char *culture_name;
630   char *output_file;
631   char *class_name;
632   char *csharp_file_name;
633   FILE *csharp_file;
634   const char *gettextlibdir;
635   const char *csharp_sources[1];
636   const char *libdirs[1];
637   const char *libraries[1];
638
639   /* If no entry for this resource/domain, don't even create the file.  */
640   if (mlp->nitems == 0)
641     return 0;
642
643   retval = 1;
644
645   /* Convert the messages to Unicode.  */
646   iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
647
648   /* Create a temporary directory where we can put the C# file.
649      A simple temporary file would also be possible but would require us to
650      define our own variant of mkstemp(): On one hand the functions mktemp(),
651      tmpnam(), tempnam() present a security risk, and on the other hand the
652      function mkstemp() doesn't allow to specify a fixed suffix of the file.
653      It is simpler to create a temporary directory.  */
654   tmpdir = create_temp_dir ("msg", NULL, false);
655   if (tmpdir == NULL)
656     goto quit1;
657
658   /* Assign a default value to the resource name.  */
659   if (resource_name == NULL)
660     resource_name = "Messages";
661
662   /* Convert the locale name to a .NET specific culture name.  */
663   culture_name = xstrdup (locale_name);
664   {
665     char *p;
666     for (p = culture_name; *p != '\0'; p++)
667       if (*p == '_')
668         *p = '-';
669     if (strncmp (culture_name, "sr-CS", 5) == 0)
670       memcpy (culture_name, "sr-SP", 5);
671     p = strchr (culture_name, '@');
672     if (p != NULL)
673       {
674         if (strcmp (p, "@latin") == 0)
675           strcpy (p, "-Latn");
676         else if (strcmp (p, "@cyrillic") == 0)
677           strcpy (p, "-Cyrl");
678       }
679     if (strcmp (culture_name, "sr-SP") == 0)
680       {
681         free (culture_name);
682         culture_name = xstrdup ("sr-SP-Latn");
683       }
684     else if (strcmp (culture_name, "uz-UZ") == 0)
685       {
686         free (culture_name);
687         culture_name = xstrdup ("uz-UZ-Latn");
688       }
689   }
690
691   /* Compute the output file name.  This code must be kept consistent with
692      intl.cs, function GetSatelliteAssembly().  */
693   {
694     char *output_dir = xconcatenated_filename (directory, culture_name, NULL);
695     struct stat statbuf;
696
697     /* Try to create the output directory if it does not yet exist.  */
698     if (stat (output_dir, &statbuf) < 0 && errno == ENOENT)
699       if (mkdir (output_dir, S_IRUSR | S_IWUSR | S_IXUSR
700                              | S_IRGRP | S_IWGRP | S_IXGRP
701                              | S_IROTH | S_IWOTH | S_IXOTH) < 0)
702         {
703           error (0, errno, _("failed to create directory \"%s\""), output_dir);
704           free (output_dir);
705           goto quit2;
706         }
707
708     output_file =
709       xconcatenated_filename (output_dir, resource_name, ".resources.dll");
710
711     free (output_dir);
712   }
713
714   /* Compute the class name.  This code must be kept consistent with intl.cs,
715      function InstantiateResourceSet().  */
716   {
717     char *class_name_part1 = construct_class_name (resource_name);
718     char *p;
719
720     class_name =
721       XNMALLOC (strlen (class_name_part1) + 1 + strlen (culture_name) + 1, char);
722     sprintf (class_name, "%s_%s", class_name_part1, culture_name);
723     for (p = class_name + strlen (class_name_part1) + 1; *p != '\0'; p++)
724       if (*p == '-')
725         *p = '_';
726     free (class_name_part1);
727   }
728
729   /* Compute the temporary C# file name.  It must end in ".cs", so that
730      the C# compiler recognizes that it is C# source code.  */
731   csharp_file_name =
732     xconcatenated_filename (tmpdir->dir_name, "resset.cs", NULL);
733
734   /* Create the C# file.  */
735   register_temp_file (tmpdir, csharp_file_name);
736   csharp_file = fopen_temp (csharp_file_name, "w");
737   if (csharp_file == NULL)
738     {
739       error (0, errno, _("failed to create \"%s\""), csharp_file_name);
740       unregister_temp_file (tmpdir, csharp_file_name);
741       goto quit3;
742     }
743
744   write_csharp_code (csharp_file, culture_name, class_name, mlp);
745
746   if (fwriteerror_temp (csharp_file))
747     {
748       error (0, errno, _("error while writing \"%s\" file"), csharp_file_name);
749       goto quit3;
750     }
751
752   /* Make it possible to override the .dll location.  This is
753      necessary for running the testsuite before "make install".  */
754   gettextlibdir = getenv ("GETTEXTCSHARPLIBDIR");
755   if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
756     gettextlibdir = relocate (LIBDIR);
757
758   /* Compile the C# file to a .dll file.  */
759   csharp_sources[0] = csharp_file_name;
760   libdirs[0] = gettextlibdir;
761   libraries[0] = "GNU.Gettext";
762   if (compile_csharp_class (csharp_sources, 1, libdirs, 1, libraries, 1,
763                             output_file, true, false, verbose > 0))
764     {
765       if (!verbose)
766         error (0, 0, _("compilation of C# class failed, please try --verbose"));
767       else
768         error (0, 0, _("compilation of C# class failed"));
769       goto quit3;
770     }
771
772   retval = 0;
773
774  quit3:
775   free (csharp_file_name);
776   free (class_name);
777   free (output_file);
778  quit2:
779   free (culture_name);
780   cleanup_temp_dir (tmpdir);
781  quit1:
782   return retval;
783 }