Imported Upstream version 0.18.1.1
[platform/upstream/gettext.git] / gettext-tools / src / write-properties.c
1 /* Writing Java .properties files.
2    Copyright (C) 2003, 2005-2009 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-properties.h"
24
25 #include <errno.h>
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "error.h"
32 #include "message.h"
33 #include "msgl-ascii.h"
34 #include "msgl-iconv.h"
35 #include "po-charset.h"
36 #include "unistr.h"
37 #include "ostream.h"
38 #include "write-po.h"
39 #include "xalloc.h"
40
41 /* The format of the Java .properties files is documented in the JDK
42    documentation for class java.util.Properties.  In the case of .properties
43    files for PropertyResourceBundle, for each message, the msgid becomes the
44    key (left-hand side) and the msgstr becomes the value (right-hand side)
45    of a "key=value" line.  Messages with plurals are not supported in this
46    format.  */
47
48 /* Handling of comments: We copy all comments from the PO file to the
49    .properties file. This is not really needed; it's a service for translators
50    who don't like PO files and prefer to maintain the .properties file.  */
51
52 /* Converts a string to JAVA encoding (with \uxxxx sequences for non-ASCII
53    characters).  */
54 static const char *
55 conv_to_java (const char *string)
56 {
57   /* We cannot use iconv to "JAVA" because not all iconv() implementations
58      know about the "JAVA" encoding.  */
59   static const char hexdigit[] = "0123456789abcdef";
60   size_t length;
61   char *result;
62
63   if (is_ascii_string (string))
64     return string;
65
66   length = 0;
67   {
68     const char *str = string;
69     const char *str_limit = str + strlen (str);
70
71     while (str < str_limit)
72       {
73         ucs4_t uc;
74         str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
75         length += (uc <= 0x007f ? 1 : uc < 0x10000 ? 6 : 12);
76       }
77   }
78
79   result = XNMALLOC (length + 1, char);
80
81   {
82     char *newstr = result;
83     const char *str = string;
84     const char *str_limit = str + strlen (str);
85
86     while (str < str_limit)
87       {
88         ucs4_t uc;
89         str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
90         if (uc <= 0x007f)
91           /* ASCII characters can be output literally.
92              We could treat non-ASCII ISO-8859-1 characters (0x0080..0x00FF)
93              the same way, but there is no point in doing this; Sun's
94              nativetoascii doesn't do it either.  */
95           *newstr++ = uc;
96         else if (uc < 0x10000)
97           {
98             /* Single UCS-2 'char'  */
99             sprintf (newstr, "\\u%c%c%c%c",
100                      hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
101                      hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
102             newstr += 6;
103           }
104         else
105           {
106             /* UTF-16 surrogate: two 'char's.  */
107             ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10);
108             ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
109             sprintf (newstr, "\\u%c%c%c%c",
110                      hexdigit[(uc1 >> 12) & 0x0f], hexdigit[(uc1 >> 8) & 0x0f],
111                      hexdigit[(uc1 >> 4) & 0x0f], hexdigit[uc1 & 0x0f]);
112             newstr += 6;
113             sprintf (newstr, "\\u%c%c%c%c",
114                      hexdigit[(uc2 >> 12) & 0x0f], hexdigit[(uc2 >> 8) & 0x0f],
115                      hexdigit[(uc2 >> 4) & 0x0f], hexdigit[uc2 & 0x0f]);
116             newstr += 6;
117           }
118       }
119     *newstr = '\0';
120   }
121
122   return result;
123 }
124
125 /* Writes a key or value to the stream, without newline.  */
126 static void
127 write_escaped_string (ostream_t stream, const char *str, bool in_key)
128 {
129   static const char hexdigit[] = "0123456789abcdef";
130   const char *str_limit = str + strlen (str);
131   bool first = true;
132
133   while (str < str_limit)
134     {
135       ucs4_t uc;
136       str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
137       /* Whitespace must be escaped.  */
138       if (uc == 0x0020 && (first || in_key))
139         ostream_write_str (stream, "\\ ");
140       else if (uc == 0x0009)
141         ostream_write_str (stream, "\\t");
142       else if (uc == 0x000a)
143         ostream_write_str (stream, "\\n");
144       else if (uc == 0x000d)
145         ostream_write_str (stream, "\\r");
146       else if (uc == 0x000c)
147         ostream_write_str (stream, "\\f");
148       else if (/* Backslash must be escaped.  */
149                uc == '\\'
150                /* Possible comment introducers must be escaped.  */
151                || uc == '#' || uc == '!'
152                /* Key terminators must be escaped.  */
153                || uc == '=' || uc == ':')
154         {
155           char seq[2];
156           seq[0] = '\\';
157           seq[1] = uc;
158           ostream_write_mem (stream, seq, 2);
159         }
160       else if (uc >= 0x0020 && uc <= 0x007e)
161         {
162           /* ASCII characters can be output literally.
163              We could treat non-ASCII ISO-8859-1 characters (0x0080..0x00FF)
164              the same way, but there is no point in doing this; Sun's
165              nativetoascii doesn't do it either.  */
166           char seq[1];
167           seq[0] = uc;
168           ostream_write_mem (stream, seq, 1);
169         }
170       else if (uc < 0x10000)
171         {
172           /* Single UCS-2 'char'  */
173           char seq[6];
174           seq[0] = '\\';
175           seq[1] = 'u';
176           seq[2] = hexdigit[(uc >> 12) & 0x0f];
177           seq[3] = hexdigit[(uc >> 8) & 0x0f];
178           seq[4] = hexdigit[(uc >> 4) & 0x0f];
179           seq[5] = hexdigit[uc & 0x0f];
180           ostream_write_mem (stream, seq, 6);
181         }
182       else
183         {
184           /* UTF-16 surrogate: two 'char's.  */
185           ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10);
186           ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
187           char seq[6];
188           seq[0] = '\\';
189           seq[1] = 'u';
190           seq[2] = hexdigit[(uc1 >> 12) & 0x0f];
191           seq[3] = hexdigit[(uc1 >> 8) & 0x0f];
192           seq[4] = hexdigit[(uc1 >> 4) & 0x0f];
193           seq[5] = hexdigit[uc1 & 0x0f];
194           ostream_write_mem (stream, seq, 6);
195           seq[0] = '\\';
196           seq[1] = 'u';
197           seq[2] = hexdigit[(uc2 >> 12) & 0x0f];
198           seq[3] = hexdigit[(uc2 >> 8) & 0x0f];
199           seq[4] = hexdigit[(uc2 >> 4) & 0x0f];
200           seq[5] = hexdigit[uc2 & 0x0f];
201           ostream_write_mem (stream, seq, 6);
202         }
203       first = false;
204     }
205 }
206
207 /* Writes a message to the stream.  */
208 static void
209 write_message (ostream_t stream, const message_ty *mp,
210                size_t page_width, bool debug)
211 {
212   /* Print translator comment if available.  */
213   message_print_comment (mp, stream);
214
215   /* Print xgettext extracted comments.  */
216   message_print_comment_dot (mp, stream);
217
218   /* Print the file position comments.  */
219   message_print_comment_filepos (mp, stream, false, page_width);
220
221   /* Print flag information in special comment.  */
222   message_print_comment_flags (mp, stream, debug);
223
224   /* Put a comment mark if the message is the header or untranslated or
225      fuzzy.  */
226   if (is_header (mp)
227       || mp->msgstr[0] == '\0'
228       || (mp->is_fuzzy && !is_header (mp)))
229     ostream_write_str (stream, "!");
230
231   /* Now write the untranslated string and the translated string.  */
232   write_escaped_string (stream, mp->msgid, true);
233   ostream_write_str (stream, "=");
234   write_escaped_string (stream, mp->msgstr, false);
235
236   ostream_write_str (stream, "\n");
237 }
238
239 /* Writes an entire message list to the stream.  */
240 static void
241 write_properties (ostream_t stream, message_list_ty *mlp,
242                   const char *canon_encoding, size_t page_width, bool debug)
243 {
244   bool blank_line;
245   size_t j, i;
246
247   /* Convert the messages to Unicode.  */
248   iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
249   for (j = 0; j < mlp->nitems; ++j)
250     {
251       message_ty *mp = mlp->item[j];
252
253       if (mp->comment != NULL)
254         for (i = 0; i < mp->comment->nitems; ++i)
255           mp->comment->item[i] = conv_to_java (mp->comment->item[i]);
256       if (mp->comment_dot != NULL)
257         for (i = 0; i < mp->comment_dot->nitems; ++i)
258           mp->comment_dot->item[i] = conv_to_java (mp->comment_dot->item[i]);
259     }
260
261   /* Loop through the messages.  */
262   blank_line = false;
263   for (j = 0; j < mlp->nitems; ++j)
264     {
265       const message_ty *mp = mlp->item[j];
266
267       if (mp->msgid_plural == NULL && !mp->obsolete)
268         {
269           if (blank_line)
270             ostream_write_str (stream, "\n");
271
272           write_message (stream, mp, page_width, debug);
273
274           blank_line = true;
275         }
276     }
277 }
278
279 /* Output the contents of a PO file in Java .properties syntax.  */
280 static void
281 msgdomain_list_print_properties (msgdomain_list_ty *mdlp, ostream_t stream,
282                                  size_t page_width, bool debug)
283 {
284   message_list_ty *mlp;
285
286   if (mdlp->nitems == 1)
287     mlp = mdlp->item[0]->messages;
288   else
289     mlp = message_list_alloc (false);
290   write_properties (stream, mlp, mdlp->encoding, page_width, debug);
291 }
292
293 /* Describes a PO file in Java .properties syntax.  */
294 const struct catalog_output_format output_format_properties =
295 {
296   msgdomain_list_print_properties,      /* print */
297   true,                                 /* requires_utf8 */
298   false,                                /* supports_color */
299   false,                                /* supports_multiple_domains */
300   false,                                /* supports_contexts */
301   false,                                /* supports_plurals */
302   false,                                /* sorts_obsoletes_to_end */
303   true,                                 /* alternative_is_po */
304   true                                  /* alternative_is_java_class */
305 };