1 /* Reading PO files. 2 Copyright (C) 1995-1998, 2000-2003, 2005-2006 Free Software Foundation, Inc. 3 This file was written by Peter Miller <millerp@canb.auug.org.au> 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ 18 19 #ifdef HAVE_CONFIG_H 20 # include <config.h> 21 #endif 22 23 /* Specification. */ 24 #include "read-catalog.h" 25 26 #include <stdbool.h> 27 #include <stdlib.h> 28 #include <string.h> 29 30 #include "open-catalog.h" 31 #include "po-charset.h" 32 #include "po-xerror.h" 33 #include "xalloc.h" 34 #include "gettext.h" 35 36 #define _(str) gettext (str) 37 38 39 /* ========================================================================= */ 40 /* Inline functions to invoke the methods. */ 41 42 static inline void 43 call_set_domain (struct default_catalog_reader_ty *this, char *name) 44 { 45 default_catalog_reader_class_ty *methods = 46 (default_catalog_reader_class_ty *) this->methods; 47 48 if (methods->set_domain) 49 methods->set_domain (this, name); 50 } 51 52 static inline void 53 call_add_message (struct default_catalog_reader_ty *this, 54 char *msgctxt, 55 char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural, 56 char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos, 57 char *prev_msgctxt, char *prev_msgid, char *prev_msgid_plural, 58 bool force_fuzzy, bool obsolete) 59 { 60 default_catalog_reader_class_ty *methods = 61 (default_catalog_reader_class_ty *) this->methods; 62 63 if (methods->add_message) 64 methods->add_message (this, msgctxt, 65 msgid, msgid_pos, msgid_plural, 66 msgstr, msgstr_len, msgstr_pos, 67 prev_msgctxt, prev_msgid, prev_msgid_plural, 68 force_fuzzy, obsolete); 69 } 70 71 static inline void 72 call_frob_new_message (struct default_catalog_reader_ty *this, message_ty *mp, 73 const lex_pos_ty *msgid_pos, 74 const lex_pos_ty *msgstr_pos) 75 { 76 default_catalog_reader_class_ty *methods = 77 (default_catalog_reader_class_ty *) this->methods; 78 79 if (methods->frob_new_message) 80 methods->frob_new_message (this, mp, msgid_pos, msgstr_pos); 81 } 82 83 84 /* ========================================================================= */ 85 /* Implementation of default_catalog_reader_ty's methods. */ 86 87 88 /* Implementation of methods declared in the superclass. */ 89 90 91 /* Prepare for first message. */ 92 void 93 default_constructor (abstract_catalog_reader_ty *that) 94 { 95 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 96 size_t i; 97 98 this->domain = MESSAGE_DOMAIN_DEFAULT; 99 this->comment = NULL; 100 this->comment_dot = NULL; 101 this->filepos_count = 0; 102 this->filepos = NULL; 103 this->is_fuzzy = false; 104 for (i = 0; i < NFORMATS; i++) 105 this->is_format[i] = undecided; 106 this->do_wrap = undecided; 107 } 108 109 110 void 111 default_destructor (abstract_catalog_reader_ty *that) 112 { 113 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 114 115 /* Do not free this->mdlp and this->mlp. */ 116 if (this->handle_comments) 117 { 118 if (this->comment != NULL) 119 string_list_free (this->comment); 120 if (this->comment_dot != NULL) 121 string_list_free (this->comment_dot); 122 } 123 if (this->handle_filepos_comments) 124 { 125 size_t j; 126 127 for (j = 0; j < this->filepos_count; ++j) 128 free (this->filepos[j].file_name); 129 if (this->filepos != NULL) 130 free (this->filepos); 131 } 132 } 133 134 135 void 136 default_parse_brief (abstract_catalog_reader_ty *that) 137 { 138 /* We need to parse comments, because even if this->handle_comments and 139 this->handle_filepos_comments are false, we need to know which messages 140 are fuzzy. */ 141 po_lex_pass_comments (true); 142 } 143 144 145 void 146 default_parse_debrief (abstract_catalog_reader_ty *that) 147 { 148 } 149 150 151 /* Add the accumulated comments to the message. */ 152 static void 153 default_copy_comment_state (default_catalog_reader_ty *this, message_ty *mp) 154 { 155 size_t j, i; 156 157 if (this->handle_comments) 158 { 159 if (this->comment != NULL) 160 for (j = 0; j < this->comment->nitems; ++j) 161 message_comment_append (mp, this->comment->item[j]); 162 if (this->comment_dot != NULL) 163 for (j = 0; j < this->comment_dot->nitems; ++j) 164 message_comment_dot_append (mp, this->comment_dot->item[j]); 165 } 166 if (this->handle_filepos_comments) 167 { 168 for (j = 0; j < this->filepos_count; ++j) 169 { 170 lex_pos_ty *pp; 171 172 pp = &this->filepos[j]; 173 message_comment_filepos (mp, pp->file_name, pp->line_number); 174 } 175 } 176 mp->is_fuzzy = this->is_fuzzy; 177 for (i = 0; i < NFORMATS; i++) 178 mp->is_format[i] = this->is_format[i]; 179 mp->do_wrap = this->do_wrap; 180 } 181 182 183 static void 184 default_reset_comment_state (default_catalog_reader_ty *this) 185 { 186 size_t j, i; 187 188 if (this->handle_comments) 189 { 190 if (this->comment != NULL) 191 { 192 string_list_free (this->comment); 193 this->comment = NULL; 194 } 195 if (this->comment_dot != NULL) 196 { 197 string_list_free (this->comment_dot); 198 this->comment_dot = NULL; 199 } 200 } 201 if (this->handle_filepos_comments) 202 { 203 for (j = 0; j < this->filepos_count; ++j) 204 free (this->filepos[j].file_name); 205 if (this->filepos != NULL) 206 free (this->filepos); 207 this->filepos_count = 0; 208 this->filepos = NULL; 209 } 210 this->is_fuzzy = false; 211 for (i = 0; i < NFORMATS; i++) 212 this->is_format[i] = undecided; 213 this->do_wrap = undecided; 214 } 215 216 217 /* Process 'domain' directive from .po file. */ 218 void 219 default_directive_domain (abstract_catalog_reader_ty *that, char *name) 220 { 221 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 222 223 call_set_domain (this, name); 224 225 /* If there are accumulated comments, throw them away, they are 226 probably part of the file header, or about the domain directive, 227 and will be unrelated to the next message. */ 228 default_reset_comment_state (this); 229 } 230 231 232 /* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file. */ 233 void 234 default_directive_message (abstract_catalog_reader_ty *that, 235 char *msgctxt, 236 char *msgid, 237 lex_pos_ty *msgid_pos, 238 char *msgid_plural, 239 char *msgstr, size_t msgstr_len, 240 lex_pos_ty *msgstr_pos, 241 char *prev_msgctxt, 242 char *prev_msgid, char *prev_msgid_plural, 243 bool force_fuzzy, bool obsolete) 244 { 245 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 246 247 call_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural, 248 msgstr, msgstr_len, msgstr_pos, 249 prev_msgctxt, prev_msgid, prev_msgid_plural, 250 force_fuzzy, obsolete); 251 252 /* Prepare for next message. */ 253 default_reset_comment_state (this); 254 } 255 256 257 void 258 default_comment (abstract_catalog_reader_ty *that, const char *s) 259 { 260 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 261 262 if (this->handle_comments) 263 { 264 if (this->comment == NULL) 265 this->comment = string_list_alloc (); 266 string_list_append (this->comment, s); 267 } 268 } 269 270 271 void 272 default_comment_dot (abstract_catalog_reader_ty *that, const char *s) 273 { 274 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 275 276 if (this->handle_comments) 277 { 278 if (this->comment_dot == NULL) 279 this->comment_dot = string_list_alloc (); 280 string_list_append (this->comment_dot, s); 281 } 282 } 283 284 285 void 286 default_comment_filepos (abstract_catalog_reader_ty *that, 287 const char *name, size_t line) 288 { 289 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 290 291 if (this->handle_filepos_comments) 292 { 293 size_t nbytes; 294 lex_pos_ty *pp; 295 296 nbytes = (this->filepos_count + 1) * sizeof (this->filepos[0]); 297 this->filepos = xrealloc (this->filepos, nbytes); 298 pp = &this->filepos[this->filepos_count++]; 299 pp->file_name = xstrdup (name); 300 pp->line_number = line; 301 } 302 } 303 304 305 /* Test for '#, fuzzy' comments and warn. */ 306 void 307 default_comment_special (abstract_catalog_reader_ty *that, const char *s) 308 { 309 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; 310 311 po_parse_comment_special (s, &this->is_fuzzy, this->is_format, 312 &this->do_wrap); 313 } 314 315 316 /* Default implementation of methods not inherited from the superclass. */ 317 318 319 void 320 default_set_domain (default_catalog_reader_ty *this, char *name) 321 { 322 if (this->allow_domain_directives) 323 /* Override current domain name. Don't free memory. */ 324 this->domain = name; 325 else 326 { 327 po_gram_error_at_line (&gram_pos, 328 _("this file may not contain domain directives")); 329 330 /* NAME was allocated in po-gram-gen.y but is not used anywhere. */ 331 free (name); 332 } 333 } 334 335 void 336 default_add_message (default_catalog_reader_ty *this, 337 char *msgctxt, 338 char *msgid, 339 lex_pos_ty *msgid_pos, 340 char *msgid_plural, 341 char *msgstr, size_t msgstr_len, 342 lex_pos_ty *msgstr_pos, 343 char *prev_msgctxt, 344 char *prev_msgid, 345 char *prev_msgid_plural, 346 bool force_fuzzy, bool obsolete) 347 { 348 message_ty *mp; 349 350 if (this->mdlp != NULL) 351 /* Select the appropriate sublist of this->mdlp. */ 352 this->mlp = msgdomain_list_sublist (this->mdlp, this->domain, true); 353 354 if (this->allow_duplicates && msgid[0] != '\0') 355 /* Doesn't matter if this message ID has been seen before. */ 356 mp = NULL; 357 else 358 /* See if this message ID has been seen before. */ 359 mp = message_list_search (this->mlp, msgctxt, msgid); 360 361 if (mp) 362 { 363 if (!(this->allow_duplicates_if_same_msgstr 364 && msgstr_len == mp->msgstr_len 365 && memcmp (msgstr, mp->msgstr, msgstr_len) == 0)) 366 { 367 /* We give a fatal error about this, regardless whether the 368 translations are equal or different. This is for consistency 369 with msgmerge, msgcat and others. The user can use the 370 msguniq program to get rid of duplicates. */ 371 po_xerror2 (PO_SEVERITY_ERROR, 372 NULL, msgid_pos->file_name, msgid_pos->line_number, 373 (size_t)(-1), false, _("duplicate message definition"), 374 mp, NULL, 0, 0, false, 375 _("this is the location of the first definition")); 376 } 377 /* We don't need the just constructed entries' parameter string 378 (allocated in po-gram-gen.y). */ 379 free (msgid); 380 if (msgid_plural != NULL) 381 free (msgid_plural); 382 free (msgstr); 383 if (msgctxt != NULL) 384 free (msgctxt); 385 if (prev_msgctxt != NULL) 386 free (prev_msgctxt); 387 if (prev_msgid != NULL) 388 free (prev_msgid); 389 if (prev_msgid_plural != NULL) 390 free (prev_msgid_plural); 391 392 /* Add the accumulated comments to the message. */ 393 default_copy_comment_state (this, mp); 394 } 395 else 396 { 397 /* Construct message to add to the list. 398 Obsolete message go into the list at least for duplicate checking. 399 It's the caller's responsibility to ignore obsolete messages when 400 appropriate. */ 401 mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len, 402 msgstr_pos); 403 mp->prev_msgctxt = prev_msgctxt; 404 mp->prev_msgid = prev_msgid; 405 mp->prev_msgid_plural = prev_msgid_plural; 406 mp->obsolete = obsolete; 407 default_copy_comment_state (this, mp); 408 if (force_fuzzy) 409 mp->is_fuzzy = true; 410 411 call_frob_new_message (this, mp, msgid_pos, msgstr_pos); 412 413 message_list_append (this->mlp, mp); 414 } 415 } 416 417 418 /* So that the one parser can be used for multiple programs, and also 419 use good data hiding and encapsulation practices, an object 420 oriented approach has been taken. An object instance is allocated, 421 and all actions resulting from the parse will be through 422 invocations of method functions of that object. */ 423 424 static default_catalog_reader_class_ty default_methods = 425 { 426 { 427 sizeof (default_catalog_reader_ty), 428 default_constructor, 429 default_destructor, 430 default_parse_brief, 431 default_parse_debrief, 432 default_directive_domain, 433 default_directive_message, 434 default_comment, 435 default_comment_dot, 436 default_comment_filepos, 437 default_comment_special 438 }, 439 default_set_domain, /* set_domain */ 440 default_add_message, /* add_message */ 441 NULL /* frob_new_message */ 442 }; 443 444 445 default_catalog_reader_ty * 446 default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table) 447 { 448 return 449 (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super); 450 } 451 452 453 /* ========================================================================= */ 454 /* Exported functions. */ 455 456 457 /* If nonzero, remember comments for file name and line number for each 458 msgid, if present in the reference input. Defaults to true. */ 459 int line_comment = 1; 460 461 /* If false, duplicate msgids in the same domain and file generate an error. 462 If true, such msgids are allowed; the caller should treat them 463 appropriately. Defaults to false. */ 464 bool allow_duplicates = false; 465 466 467 msgdomain_list_ty * 468 read_catalog_stream (FILE *fp, const char *real_filename, 469 const char *logical_filename, 470 catalog_input_format_ty input_syntax) 471 { 472 default_catalog_reader_ty *pop; 473 msgdomain_list_ty *mdlp; 474 475 pop = default_catalog_reader_alloc (&default_methods); 476 pop->handle_comments = true; 477 pop->handle_filepos_comments = (line_comment != 0); 478 pop->allow_domain_directives = true; 479 pop->allow_duplicates = allow_duplicates; 480 pop->allow_duplicates_if_same_msgstr = false; 481 pop->mdlp = msgdomain_list_alloc (!pop->allow_duplicates); 482 pop->mlp = msgdomain_list_sublist (pop->mdlp, pop->domain, true); 483 if (input_syntax->produces_utf8) 484 /* We know a priori that input_syntax->parse convert strings to UTF-8. */ 485 pop->mdlp->encoding = po_charset_utf8; 486 po_lex_pass_obsolete_entries (true); 487 catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename, 488 logical_filename, input_syntax); 489 mdlp = pop->mdlp; 490 catalog_reader_free ((abstract_catalog_reader_ty *) pop); 491 return mdlp; 492 } 493 494 495 msgdomain_list_ty * 496 read_catalog_file (const char *filename, catalog_input_format_ty input_syntax) 497 { 498 char *real_filename; 499 FILE *fp = open_catalog_file (filename, &real_filename, true); 500 msgdomain_list_ty *result; 501 502 result = read_catalog_stream (fp, real_filename, filename, input_syntax); 503 504 if (fp != stdin) 505 fclose (fp); 506 507 return result; 508 } 509