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