1/* Writing NeXTstep/GNUstep .strings files.
2   Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19#ifdef HAVE_CONFIG_H
20# include <config.h>
21#endif
22
23/* Specification.  */
24#include "write-stringtable.h"
25
26#include <stdbool.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30
31#include "message.h"
32#include "msgl-ascii.h"
33#include "msgl-iconv.h"
34#include "po-charset.h"
35#include "strstr.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 file, without newline.  */
59static void
60write_escaped_string (FILE *fp, const char *str)
61{
62  const char *str_limit = str + strlen (str);
63
64  putc ('"', fp);
65  while (str < str_limit)
66    {
67      unsigned char c = (unsigned char) *str++;
68
69      if (c == '\t')
70	{
71	  putc ('\\', fp);
72	  putc ('t', fp);
73	}
74      else if (c == '\n')
75	{
76	  putc ('\\', fp);
77	  putc ('n', fp);
78	}
79      else if (c == '\r')
80	{
81	  putc ('\\', fp);
82	  putc ('r', fp);
83	}
84      else if (c == '\f')
85	{
86	  putc ('\\', fp);
87	  putc ('f', fp);
88	}
89      else if (c == '\\' || c == '"')
90	{
91	  putc ('\\', fp);
92	  putc (c, fp);
93	}
94      else
95	putc (c, fp);
96    }
97  putc ('"', fp);
98}
99
100/* Writes a message to the file.  */
101static void
102write_message (FILE *fp, const message_ty *mp, size_t page_width, bool debug)
103{
104  /* Print translator comment if available.  */
105  if (mp->comment != NULL)
106    {
107      size_t j;
108
109      for (j = 0; j < mp->comment->nitems; ++j)
110	{
111	  const char *s = mp->comment->item[j];
112
113	  /* Test whether it is safe to output the comment in C style, or
114	     whether we need C++ style for it.  */
115	  if (strstr (s, "*/") == NULL)
116	    {
117	      fputs ("/*", fp);
118	      if (*s != '\0' && *s != '\n' && *s != ' ')
119		putc (' ', fp);
120	      fputs (s, fp);
121	      fputs (" */\n", fp);
122	    }
123	  else
124	    do
125	      {
126		const char *e;
127		fputs ("//", fp);
128		if (*s != '\0' && *s != '\n' && *s != ' ')
129		  putc (' ', fp);
130		e = strchr (s, '\n');
131		if (e == NULL)
132		  {
133		    fputs (s, fp);
134		    s = NULL;
135		  }
136		else
137		  {
138		    fwrite (s, 1, e - s, fp);
139		    s = e + 1;
140		  }
141		putc ('\n', fp);
142	      }
143	    while (s != NULL);
144	}
145    }
146
147  /* Print xgettext extracted comments.  */
148  if (mp->comment_dot != NULL)
149    {
150      size_t j;
151
152      for (j = 0; j < mp->comment_dot->nitems; ++j)
153	{
154	  const char *s = mp->comment_dot->item[j];
155
156	  /* Test whether it is safe to output the comment in C style, or
157	     whether we need C++ style for it.  */
158	  if (strstr (s, "*/") == NULL)
159	    {
160	      fputs ("/* Comment: ", fp);
161	      fputs (s, fp);
162	      fputs (" */\n", fp);
163	    }
164	  else
165	    {
166	      bool first = true;
167	      do
168		{
169		  const char *e;
170		  fputs ("//", fp);
171		  if (first || (*s != '\0' && *s != '\n' && *s != ' '))
172		    putc (' ', fp);
173		  if (first)
174		    fputs ("Comment: ", fp);
175		  e = strchr (s, '\n');
176		  if (e == NULL)
177		    {
178		      fputs (s, fp);
179		      s = NULL;
180		    }
181		  else
182		    {
183		      fwrite (s, 1, e - s, fp);
184		      s = e + 1;
185		    }
186		  putc ('\n', fp);
187		  first = false;
188		}
189	      while (s != NULL);
190	    }
191	}
192    }
193
194  /* Print the file position comments.  */
195  if (mp->filepos_count != 0)
196    {
197      size_t j;
198
199      for (j = 0; j < mp->filepos_count; ++j)
200	{
201	  lex_pos_ty *pp = &mp->filepos[j];
202	  char *cp = pp->file_name;
203	  while (cp[0] == '.' && cp[1] == '/')
204	    cp += 2;
205	  fprintf (fp, "/* File: %s:%ld */\n", cp, (long) pp->line_number);
206	}
207    }
208
209  /* Print flag information in special comment.  */
210  if (mp->is_fuzzy || mp->msgstr[0] == '\0')
211    fputs ("/* Flag: untranslated */\n", fp);
212  if (mp->obsolete)
213    fputs ("/* Flag: unmatched */\n", fp);
214  {
215    size_t i;
216    for (i = 0; i < NFORMATS; i++)
217      if (significant_format_p (mp->is_format[i]))
218	{
219	  fputs ("/* Flag:", fp);
220	  fputs (make_format_description_string (mp->is_format[i],
221						 format_language[i], debug),
222		 fp);
223	  fputs (" */\n", fp);
224	}
225  }
226
227  /* Now write the untranslated string and the translated string.  */
228  write_escaped_string (fp, mp->msgid);
229  fputs (" = ", fp);
230  if (mp->msgstr[0] != '\0')
231    {
232      if (mp->is_fuzzy)
233	{
234	  /* Output the msgid as value, so that at runtime the untranslated
235	     string is returned.  */
236	  write_escaped_string (fp, mp->msgid);
237
238	  /* Output the msgstr as a comment, so that at runtime
239	     propertyListFromStringsFileFormat ignores it.  */
240	  if (strstr (mp->msgstr, "*/") == NULL)
241	    {
242	      fputs (" /* = ", fp);
243	      write_escaped_string (fp, mp->msgstr);
244	      fputs (" */", fp);
245	    }
246	  else
247	    {
248	      fputs ("; // = ", fp);
249	      write_escaped_string (fp, mp->msgstr);
250	    }
251	}
252      else
253	write_escaped_string (fp, mp->msgstr);
254    }
255  else
256    {
257      /* Output the msgid as value, so that at runtime the untranslated
258	 string is returned.  */
259      write_escaped_string (fp, mp->msgid);
260    }
261  putc (';', fp);
262
263  putc ('\n', fp);
264}
265
266/* Writes an entire message list to the file.  */
267static void
268write_stringtable (FILE *fp, message_list_ty *mlp, const char *canon_encoding,
269		   size_t page_width, bool debug)
270{
271  bool blank_line;
272  size_t j;
273
274  /* Convert the messages to Unicode.  */
275  iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
276
277  /* Output the BOM.  */
278  if (!is_ascii_message_list (mlp))
279    fputs ("\xef\xbb\xbf", fp);
280
281  /* Loop through the messages.  */
282  blank_line = false;
283  for (j = 0; j < mlp->nitems; ++j)
284    {
285      const message_ty *mp = mlp->item[j];
286
287      if (mp->msgid_plural == NULL)
288	{
289	  if (blank_line)
290	    putc ('\n', fp);
291
292	  write_message (fp, mp, page_width, debug);
293
294	  blank_line = true;
295	}
296    }
297}
298
299/* Output the contents of a PO file in .strings syntax.  */
300void
301msgdomain_list_print_stringtable (msgdomain_list_ty *mdlp, FILE *fp,
302				  size_t page_width, bool debug)
303{
304  message_list_ty *mlp;
305
306  if (mdlp->nitems == 1)
307    mlp = mdlp->item[0]->messages;
308  else
309    mlp = message_list_alloc (false);
310  write_stringtable (fp, mlp, mdlp->encoding, page_width, debug);
311}
312