Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / write-stringtable.c
1 /* Writing NeXTstep/GNUstep .strings files.
2    Copyright (C) 2003, 2006-2008, 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
22 /* Specification.  */
23 #include "write-stringtable.h"
24
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "message.h"
30 #include "msgl-ascii.h"
31 #include "msgl-iconv.h"
32 #include "po-charset.h"
33 #include "c-strstr.h"
34 #include "ostream.h"
35 #include "xvasprintf.h"
36 #include "write-po.h"
37
38 /* The format of NeXTstep/GNUstep .strings files is documented in
39      gnustep-base-1.8.0/Tools/make_strings/Using.txt
40    and in the comments of method propertyListFromStringsFileFormat in
41      gnustep-base-1.8.0/Source/NSString.m
42    In summary, it's a Objective-C like file with pseudo-assignments of the form
43           "key" = "value";
44    where the key is the msgid and the value is the msgstr.
45  */
46
47 /* Handling of comments: We copy all comments from the PO file to the
48    .strings file. This is not really needed; it's a service for translators
49    who don't like PO files and prefer to maintain the .strings file.  */
50
51 /* Since the interpretation of text files in GNUstep depends on the locale's
52    encoding if they don't have a BOM, we choose one of three encodings with
53    a BOM: UCS-2BE, UCS-2LE, UTF-8.  Since the first two of these don't cope
54    with all of Unicode and we don't know whether GNUstep will switch to
55    UTF-16 instead of UCS-2, we use UTF-8 with BOM.  BOMs are bad because they
56    get in the way when concatenating files, but here we have no choice.  */
57
58 /* Writes a key or value to the stream, without newline.  */
59 static void
60 write_escaped_string (ostream_t stream, const char *str)
61 {
62   const char *str_limit = str + strlen (str);
63
64   ostream_write_str (stream, "\"");
65   while (str < str_limit)
66     {
67       unsigned char c = (unsigned char) *str++;
68
69       if (c == '\t')
70         ostream_write_str (stream, "\\t");
71       else if (c == '\n')
72         ostream_write_str (stream, "\\n");
73       else if (c == '\r')
74         ostream_write_str (stream, "\\r");
75       else if (c == '\f')
76         ostream_write_str (stream, "\\f");
77       else if (c == '\\' || c == '"')
78         {
79           char seq[2];
80           seq[0] = '\\';
81           seq[1] = c;
82           ostream_write_mem (stream, seq, 2);
83         }
84       else
85         {
86           char seq[1];
87           seq[0] = c;
88           ostream_write_mem (stream, seq, 1);
89         }
90     }
91   ostream_write_str (stream, "\"");
92 }
93
94 /* Writes a message to the stream.  */
95 static void
96 write_message (ostream_t stream, const message_ty *mp,
97                size_t page_width, bool debug)
98 {
99   /* Print translator comment if available.  */
100   if (mp->comment != NULL)
101     {
102       size_t j;
103
104       for (j = 0; j < mp->comment->nitems; ++j)
105         {
106           const char *s = mp->comment->item[j];
107
108           /* Test whether it is safe to output the comment in C style, or
109              whether we need C++ style for it.  */
110           if (c_strstr (s, "*/") == NULL)
111             {
112               ostream_write_str (stream, "/*");
113               if (*s != '\0' && *s != '\n')
114                 ostream_write_str (stream, " ");
115               ostream_write_str (stream, s);
116               ostream_write_str (stream, " */\n");
117             }
118           else
119             do
120               {
121                 const char *e;
122                 ostream_write_str (stream, "//");
123                 if (*s != '\0' && *s != '\n')
124                   ostream_write_str (stream, " ");
125                 e = strchr (s, '\n');
126                 if (e == NULL)
127                   {
128                     ostream_write_str (stream, s);
129                     s = NULL;
130                   }
131                 else
132                   {
133                     ostream_write_mem (stream, s, e - s);
134                     s = e + 1;
135                   }
136                 ostream_write_str (stream, "\n");
137               }
138             while (s != NULL);
139         }
140     }
141
142   /* Print xgettext extracted comments.  */
143   if (mp->comment_dot != NULL)
144     {
145       size_t j;
146
147       for (j = 0; j < mp->comment_dot->nitems; ++j)
148         {
149           const char *s = mp->comment_dot->item[j];
150
151           /* Test whether it is safe to output the comment in C style, or
152              whether we need C++ style for it.  */
153           if (c_strstr (s, "*/") == NULL)
154             {
155               ostream_write_str (stream, "/* Comment: ");
156               ostream_write_str (stream, s);
157               ostream_write_str (stream, " */\n");
158             }
159           else
160             {
161               bool first = true;
162               do
163                 {
164                   const char *e;
165                   ostream_write_str (stream, "//");
166                   if (first || (*s != '\0' && *s != '\n'))
167                     ostream_write_str (stream, " ");
168                   if (first)
169                     ostream_write_str (stream, "Comment: ");
170                   e = strchr (s, '\n');
171                   if (e == NULL)
172                     {
173                       ostream_write_str (stream, s);
174                       s = NULL;
175                     }
176                   else
177                     {
178                       ostream_write_mem (stream, s, e - s);
179                       s = e + 1;
180                     }
181                   ostream_write_str (stream, "\n");
182                   first = false;
183                 }
184               while (s != NULL);
185             }
186         }
187     }
188
189   /* Print the file position comments.  */
190   if (mp->filepos_count != 0)
191     {
192       size_t j;
193
194       for (j = 0; j < mp->filepos_count; ++j)
195         {
196           lex_pos_ty *pp = &mp->filepos[j];
197           const char *cp = pp->file_name;
198           char *str;
199
200           while (cp[0] == '.' && cp[1] == '/')
201             cp += 2;
202           str = xasprintf ("/* File: %s:%ld */\n", cp, (long) pp->line_number);
203           ostream_write_str (stream, str);
204           free (str);
205         }
206     }
207
208   /* Print flag information in special comment.  */
209   if (mp->is_fuzzy || mp->msgstr[0] == '\0')
210     ostream_write_str (stream, "/* Flag: untranslated */\n");
211   if (mp->obsolete)
212     ostream_write_str (stream, "/* Flag: unmatched */\n");
213   {
214     size_t i;
215     for (i = 0; i < NFORMATS; i++)
216       if (significant_format_p (mp->is_format[i]))
217         {
218           ostream_write_str (stream, "/* Flag: ");
219           ostream_write_str (stream,
220                              make_format_description_string (mp->is_format[i],
221                                                              format_language[i],
222                                                              debug));
223           ostream_write_str (stream, " */\n");
224         }
225   }
226   if (has_range_p (mp->range))
227     {
228       char *string;
229
230       ostream_write_str (stream, "/* Flag: ");
231       string = make_range_description_string (mp->range);
232       ostream_write_str (stream, string);
233       free (string);
234       ostream_write_str (stream, " */\n");
235     }
236
237   /* Now write the untranslated string and the translated string.  */
238   write_escaped_string (stream, mp->msgid);
239   ostream_write_str (stream, " = ");
240   if (mp->msgstr[0] != '\0')
241     {
242       if (mp->is_fuzzy)
243         {
244           /* Output the msgid as value, so that at runtime the untranslated
245              string is returned.  */
246           write_escaped_string (stream, mp->msgid);
247
248           /* Output the msgstr as a comment, so that at runtime
249              propertyListFromStringsFileFormat ignores it.  */
250           if (c_strstr (mp->msgstr, "*/") == NULL)
251             {
252               ostream_write_str (stream, " /* = ");
253               write_escaped_string (stream, mp->msgstr);
254               ostream_write_str (stream, " */");
255             }
256           else
257             {
258               ostream_write_str (stream, "; // = ");
259               write_escaped_string (stream, mp->msgstr);
260             }
261         }
262       else
263         write_escaped_string (stream, mp->msgstr);
264     }
265   else
266     {
267       /* Output the msgid as value, so that at runtime the untranslated
268          string is returned.  */
269       write_escaped_string (stream, mp->msgid);
270     }
271   ostream_write_str (stream, ";");
272
273   ostream_write_str (stream, "\n");
274 }
275
276 /* Writes an entire message list to the stream.  */
277 static void
278 write_stringtable (ostream_t stream, message_list_ty *mlp,
279                    const char *canon_encoding, size_t page_width, bool debug)
280 {
281   bool blank_line;
282   size_t j;
283
284   /* Convert the messages to Unicode.  */
285   iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
286
287   /* Output the BOM.  */
288   if (!is_ascii_message_list (mlp))
289     ostream_write_str (stream, "\xef\xbb\xbf");
290
291   /* Loop through the messages.  */
292   blank_line = false;
293   for (j = 0; j < mlp->nitems; ++j)
294     {
295       const message_ty *mp = mlp->item[j];
296
297       if (mp->msgid_plural == NULL)
298         {
299           if (blank_line)
300             ostream_write_str (stream, "\n");
301
302           write_message (stream, mp, page_width, debug);
303
304           blank_line = true;
305         }
306     }
307 }
308
309 /* Output the contents of a PO file in .strings syntax.  */
310 static void
311 msgdomain_list_print_stringtable (msgdomain_list_ty *mdlp, ostream_t stream,
312                                   size_t page_width, bool debug)
313 {
314   message_list_ty *mlp;
315
316   if (mdlp->nitems == 1)
317     mlp = mdlp->item[0]->messages;
318   else
319     mlp = message_list_alloc (false);
320   write_stringtable (stream, mlp, mdlp->encoding, page_width, debug);
321 }
322
323 /* Describes a PO file in .strings syntax.  */
324 const struct catalog_output_format output_format_stringtable =
325 {
326   msgdomain_list_print_stringtable,     /* print */
327   true,                                 /* requires_utf8 */
328   false,                                /* supports_color */
329   false,                                /* supports_multiple_domains */
330   false,                                /* supports_contexts */
331   false,                                /* supports_plurals */
332   false,                                /* sorts_obsoletes_to_end */
333   false,                                /* alternative_is_po */
334   false                                 /* alternative_is_java_class */
335 };