1 /* Writing tcl/msgcat .msg files.
2    Copyright (C) 2002-2003, 2005, 2007-2009, 2016 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <bruno@clisp.org>, 2002.
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <alloca.h>
23 
24 /* Specification.  */
25 #include "write-tcl.h"
26 
27 #include <errno.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #include "error.h"
34 #include "xerror.h"
35 #include "message.h"
36 #include "msgl-iconv.h"
37 #include "msgl-header.h"
38 #include "po-charset.h"
39 #include "xalloc.h"
40 #include "xmalloca.h"
41 #include "concat-filename.h"
42 #include "fwriteerror.h"
43 #include "unistr.h"
44 #include "gettext.h"
45 
46 #define _(str) gettext (str)
47 
48 
49 /* Write a string in Tcl Unicode notation to the given stream.
50    Tcl 8 uses Unicode for its internal string representation.
51    In tcl-8.3.3, the .msg files are read in using the locale dependent
52    encoding.  The only way to specify strings in an encoding independent
53    form is the \unnnn notation.  Newer tcl versions have this fixed:
54    they read the .msg files in UTF-8 encoding.  */
55 static void
write_tcl_string(FILE * stream,const char * str)56 write_tcl_string (FILE *stream, const char *str)
57 {
58   static const char hexdigit[] = "0123456789abcdef";
59   const char *str_limit = str + strlen (str);
60 
61   fprintf (stream, "\"");
62   while (str < str_limit)
63     {
64       ucs4_t uc;
65       unsigned int count;
66       count = u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
67       if (uc < 0x10000)
68         {
69           /* Single UCS-2 'char'.  */
70           if (uc == 0x000a)
71             fprintf (stream, "\\n");
72           else if (uc == 0x000d)
73             fprintf (stream, "\\r");
74           else if (uc == 0x0022)
75             fprintf (stream, "\\\"");
76           else if (uc == 0x0024)
77             fprintf (stream, "\\$");
78           else if (uc == 0x005b)
79             fprintf (stream, "\\[");
80           else if (uc == 0x005c)
81             fprintf (stream, "\\\\");
82           else if (uc == 0x005d)
83             fprintf (stream, "\\]");
84           /* No need to escape '{' and '}' because we don't have opening
85              braces outside the strings.  */
86 #if 0
87           else if (uc == 0x007b)
88             fprintf (stream, "\\{");
89           else if (uc == 0x007d)
90             fprintf (stream, "\\}");
91 #endif
92           else if (uc >= 0x0020 && uc < 0x007f)
93             fprintf (stream, "%c", (int) uc);
94           else
95             fprintf (stream, "\\u%c%c%c%c",
96                      hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
97                      hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
98         }
99       else
100         /* The \unnnn notation doesn't support characters >= 0x10000.
101            We output them as UTF-8 byte sequences and hope that either
102            the Tcl version reading them will be new enough or that the
103            user is using an UTF-8 locale.  */
104         fwrite (str, 1, count, stream);
105       str += count;
106     }
107   fprintf (stream, "\"");
108 }
109 
110 
111 static void
write_msg(FILE * output_file,message_list_ty * mlp,const char * locale_name)112 write_msg (FILE *output_file, message_list_ty *mlp, const char *locale_name)
113 {
114   size_t j;
115 
116   /* We don't care about esthetic formattic of the output (like respecting
117      a maximum line width, or including the translator comments) because
118      the \unnnn notation is unesthetic anyway.  Translators shall edit
119      the PO file.  */
120   for (j = 0; j < mlp->nitems; j++)
121     {
122       message_ty *mp = mlp->item[j];
123 
124       if (is_header (mp))
125         /* Tcl's msgcat unit ignores this, but msgunfmt needs it.  */
126         fprintf (output_file, "set ::msgcat::header ");
127       else
128         {
129           fprintf (output_file, "::msgcat::mcset %s ", locale_name);
130           write_tcl_string (output_file, mp->msgid);
131           fprintf (output_file, " ");
132         }
133       write_tcl_string (output_file, mp->msgstr);
134       fprintf (output_file, "\n");
135     }
136 }
137 
138 int
msgdomain_write_tcl(message_list_ty * mlp,const char * canon_encoding,const char * locale_name,const char * directory)139 msgdomain_write_tcl (message_list_ty *mlp, const char *canon_encoding,
140                      const char *locale_name,
141                      const char *directory)
142 {
143   /* If no entry for this domain don't even create the file.  */
144   if (mlp->nitems == 0)
145     return 0;
146 
147   /* Determine whether mlp has entries with context.  */
148   {
149     bool has_context;
150     size_t j;
151 
152     has_context = false;
153     for (j = 0; j < mlp->nitems; j++)
154       if (mlp->item[j]->msgctxt != NULL)
155         has_context = true;
156     if (has_context)
157       {
158         multiline_error (xstrdup (""),
159                          xstrdup (_("\
160 message catalog has context dependent translations\n\
161 but the Tcl message catalog format doesn't support contexts\n")));
162         return 1;
163       }
164   }
165 
166   /* Determine whether mlp has plural entries.  */
167   {
168     bool has_plural;
169     size_t j;
170 
171     has_plural = false;
172     for (j = 0; j < mlp->nitems; j++)
173       if (mlp->item[j]->msgid_plural != NULL)
174         has_plural = true;
175     if (has_plural)
176       {
177         multiline_error (xstrdup (""),
178                          xstrdup (_("\
179 message catalog has plural form translations\n\
180 but the Tcl message catalog format doesn't support plural handling\n")));
181         return 1;
182       }
183   }
184 
185   /* Convert the messages to Unicode.  */
186   iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
187 
188   /* Support for "reproducible builds": Delete information that may vary
189      between builds in the same conditions.  */
190   message_list_delete_header_field (mlp, "POT-Creation-Date:");
191 
192   /* Now create the file.  */
193   {
194     size_t len;
195     char *frobbed_locale_name;
196     char *p;
197     char *file_name;
198     FILE *output_file;
199 
200     /* Convert the locale name to lowercase and remove any encoding.  */
201     len = strlen (locale_name);
202     frobbed_locale_name = (char *) xmalloca (len + 1);
203     memcpy (frobbed_locale_name, locale_name, len + 1);
204     for (p = frobbed_locale_name; *p != '\0'; p++)
205       if (*p >= 'A' && *p <= 'Z')
206         *p = *p - 'A' + 'a';
207       else if (*p == '.')
208         {
209           *p = '\0';
210           break;
211         }
212 
213     file_name = xconcatenated_filename (directory, frobbed_locale_name, ".msg");
214 
215     output_file = fopen (file_name, "w");
216     if (output_file == NULL)
217       {
218         error (0, errno, _("error while opening \"%s\" for writing"),
219                file_name);
220         freea (frobbed_locale_name);
221         return 1;
222       }
223 
224     write_msg (output_file, mlp, frobbed_locale_name);
225 
226     /* Make sure nothing went wrong.  */
227     if (fwriteerror (output_file))
228       error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"),
229              file_name);
230 
231     freea (frobbed_locale_name);
232   }
233 
234   return 0;
235 }
236