1/* Writing Java .properties files. 2 Copyright (C) 2003, 2005-2007 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). */ 54static const char * 55conv_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 unsigned int 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 unsigned int 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 unsigned int uc1 = 0xd800 + ((uc - 0x10000) >> 10); 108 unsigned int 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. */ 126static void 127write_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 unsigned int 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 unsigned int uc1 = 0xd800 + ((uc - 0x10000) >> 10); 186 unsigned int 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. */ 208static void 209write_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. */ 240static void 241write_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. */ 280static void 281msgdomain_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. */ 294const 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 true, /* alternative_is_po */ 303 true /* alternative_is_java_class */ 304}; 305