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