1 /* Reading PO files, abstract class. 2 Copyright (C) 1995-1996, 1998, 2000-2006 Free Software Foundation, Inc. 3 4 This file was written by Peter Miller <millerp@canb.auug.org.au> 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software Foundation, 18 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ 19 20 21 #ifdef HAVE_CONFIG_H 22 # include "config.h" 23 #endif 24 25 /* Specification. */ 26 #include "read-catalog-abstract.h" 27 28 #include <stdlib.h> 29 #include <string.h> 30 31 #include "xalloc.h" 32 #include "xvasprintf.h" 33 #include "po-xerror.h" 34 #include "gettext.h" 35 36 /* Local variables. */ 37 static abstract_catalog_reader_ty *callback_arg; 38 39 40 /* ========================================================================= */ 41 /* Allocating and freeing instances of abstract_catalog_reader_ty. */ 42 43 44 abstract_catalog_reader_ty * 45 catalog_reader_alloc (abstract_catalog_reader_class_ty *method_table) 46 { 47 abstract_catalog_reader_ty *pop; 48 49 pop = (abstract_catalog_reader_ty *) xmalloc (method_table->size); 50 pop->methods = method_table; 51 if (method_table->constructor) 52 method_table->constructor (pop); 53 return pop; 54 } 55 56 57 void 58 catalog_reader_free (abstract_catalog_reader_ty *pop) 59 { 60 if (pop->methods->destructor) 61 pop->methods->destructor (pop); 62 free (pop); 63 } 64 65 66 /* ========================================================================= */ 67 /* Inline functions to invoke the methods. */ 68 69 70 static inline void 71 call_parse_brief (abstract_catalog_reader_ty *pop) 72 { 73 if (pop->methods->parse_brief) 74 pop->methods->parse_brief (pop); 75 } 76 77 static inline void 78 call_parse_debrief (abstract_catalog_reader_ty *pop) 79 { 80 if (pop->methods->parse_debrief) 81 pop->methods->parse_debrief (pop); 82 } 83 84 static inline void 85 call_directive_domain (abstract_catalog_reader_ty *pop, char *name) 86 { 87 if (pop->methods->directive_domain) 88 pop->methods->directive_domain (pop, name); 89 } 90 91 static inline void 92 call_directive_message (abstract_catalog_reader_ty *pop, 93 char *msgctxt, 94 char *msgid, 95 lex_pos_ty *msgid_pos, 96 char *msgid_plural, 97 char *msgstr, size_t msgstr_len, 98 lex_pos_ty *msgstr_pos, 99 char *prev_msgctxt, 100 char *prev_msgid, 101 char *prev_msgid_plural, 102 bool force_fuzzy, bool obsolete) 103 { 104 if (pop->methods->directive_message) 105 pop->methods->directive_message (pop, msgctxt, 106 msgid, msgid_pos, msgid_plural, 107 msgstr, msgstr_len, msgstr_pos, 108 prev_msgctxt, 109 prev_msgid, 110 prev_msgid_plural, 111 force_fuzzy, obsolete); 112 } 113 114 static inline void 115 call_comment (abstract_catalog_reader_ty *pop, const char *s) 116 { 117 if (pop->methods->comment != NULL) 118 pop->methods->comment (pop, s); 119 } 120 121 static inline void 122 call_comment_dot (abstract_catalog_reader_ty *pop, const char *s) 123 { 124 if (pop->methods->comment_dot != NULL) 125 pop->methods->comment_dot (pop, s); 126 } 127 128 static inline void 129 call_comment_filepos (abstract_catalog_reader_ty *pop, const char *name, 130 size_t line) 131 { 132 if (pop->methods->comment_filepos) 133 pop->methods->comment_filepos (pop, name, line); 134 } 135 136 static inline void 137 call_comment_special (abstract_catalog_reader_ty *pop, const char *s) 138 { 139 if (pop->methods->comment_special != NULL) 140 pop->methods->comment_special (pop, s); 141 } 142 143 144 /* ========================================================================= */ 145 /* Exported functions. */ 146 147 148 static inline void 149 parse_start (abstract_catalog_reader_ty *pop) 150 { 151 /* The parse will call the po_callback_... functions (see below) 152 when the various directive are recognised. The callback_arg 153 variable is used to tell these functions which instance is to 154 have the relevant method invoked. */ 155 callback_arg = pop; 156 157 call_parse_brief (pop); 158 } 159 160 static inline void 161 parse_end (abstract_catalog_reader_ty *pop) 162 { 163 call_parse_debrief (pop); 164 callback_arg = NULL; 165 } 166 167 168 void 169 catalog_reader_parse (abstract_catalog_reader_ty *pop, FILE *fp, 170 const char *real_filename, const char *logical_filename, 171 catalog_input_format_ty input_syntax) 172 { 173 /* Parse the stream's content. */ 174 parse_start (pop); 175 input_syntax->parse (pop, fp, real_filename, logical_filename); 176 parse_end (pop); 177 178 if (error_message_count > 0) 179 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, 180 /*real_filename*/ NULL, (size_t)(-1), (size_t)(-1), false, 181 xasprintf (ngettext ("found %d fatal error", 182 "found %d fatal errors", 183 error_message_count), 184 error_message_count)); 185 error_message_count = 0; 186 } 187 188 189 /* ========================================================================= */ 190 /* Callbacks used by po-gram.y or po-lex.c, indirectly from 191 catalog_reader_parse. */ 192 193 194 /* This function is called by po_gram_lex() whenever a domain directive 195 has been seen. */ 196 void 197 po_callback_domain (char *name) 198 { 199 /* assert(callback_arg); */ 200 call_directive_domain (callback_arg, name); 201 } 202 203 204 /* This function is called by po_gram_lex() whenever a message has been 205 seen. */ 206 void 207 po_callback_message (char *msgctxt, 208 char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural, 209 char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos, 210 char *prev_msgctxt, 211 char *prev_msgid, 212 char *prev_msgid_plural, 213 bool force_fuzzy, bool obsolete) 214 { 215 /* assert(callback_arg); */ 216 call_directive_message (callback_arg, msgctxt, 217 msgid, msgid_pos, msgid_plural, 218 msgstr, msgstr_len, msgstr_pos, 219 prev_msgctxt, prev_msgid, prev_msgid_plural, 220 force_fuzzy, obsolete); 221 } 222 223 224 void 225 po_callback_comment (const char *s) 226 { 227 /* assert(callback_arg); */ 228 call_comment (callback_arg, s); 229 } 230 231 232 void 233 po_callback_comment_dot (const char *s) 234 { 235 /* assert(callback_arg); */ 236 call_comment_dot (callback_arg, s); 237 } 238 239 240 /* This function is called by po_parse_comment_filepos(), once for each 241 filename. */ 242 void 243 po_callback_comment_filepos (const char *name, size_t line) 244 { 245 /* assert(callback_arg); */ 246 call_comment_filepos (callback_arg, name, line); 247 } 248 249 250 void 251 po_callback_comment_special (const char *s) 252 { 253 /* assert(callback_arg); */ 254 call_comment_special (callback_arg, s); 255 } 256 257 258 /* Parse a special comment and put the result in *fuzzyp, formatp, *wrapp. */ 259 void 260 po_parse_comment_special (const char *s, 261 bool *fuzzyp, enum is_format formatp[NFORMATS], 262 enum is_wrap *wrapp) 263 { 264 size_t i; 265 266 *fuzzyp = false; 267 for (i = 0; i < NFORMATS; i++) 268 formatp[i] = undecided; 269 *wrapp = undecided; 270 271 while (*s != '\0') 272 { 273 const char *t; 274 275 /* Skip whitespace. */ 276 while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) != NULL) 277 s++; 278 279 /* Collect a token. */ 280 t = s; 281 while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) == NULL) 282 s++; 283 if (s != t) 284 { 285 size_t len = s - t; 286 287 /* Accept fuzzy flag. */ 288 if (len == 5 && memcmp (t, "fuzzy", 5) == 0) 289 { 290 *fuzzyp = true; 291 continue; 292 } 293 294 /* Accept format description. */ 295 if (len >= 7 && memcmp (t + len - 7, "-format", 7) == 0) 296 { 297 const char *p; 298 size_t n; 299 enum is_format value; 300 301 p = t; 302 n = len - 7; 303 304 if (n >= 3 && memcmp (p, "no-", 3) == 0) 305 { 306 p += 3; 307 n -= 3; 308 value = no; 309 } 310 else if (n >= 9 && memcmp (p, "possible-", 9) == 0) 311 { 312 p += 9; 313 n -= 9; 314 value = possible; 315 } 316 else if (n >= 11 && memcmp (p, "impossible-", 11) == 0) 317 { 318 p += 11; 319 n -= 11; 320 value = impossible; 321 } 322 else 323 value = yes; 324 325 for (i = 0; i < NFORMATS; i++) 326 if (strlen (format_language[i]) == n 327 && memcmp (format_language[i], p, n) == 0) 328 { 329 formatp[i] = value; 330 break; 331 } 332 if (i < NFORMATS) 333 continue; 334 } 335 336 /* Accept wrap description. */ 337 if (len == 4 && memcmp (t, "wrap", 4) == 0) 338 { 339 *wrapp = yes; 340 continue; 341 } 342 if (len == 7 && memcmp (t, "no-wrap", 7) == 0) 343 { 344 *wrapp = no; 345 continue; 346 } 347 348 /* Unknown special comment marker. It may have been generated 349 from a future xgettext version. Ignore it. */ 350 } 351 } 352 } 353 354 355 /* Parse a GNU style file comment. 356 Syntax: an arbitrary number of 357 STRING COLON NUMBER 358 or 359 STRING 360 The latter style, without line number, occurs in PO files converted e.g. 361 from Pascal .rst files or from OpenOffice resource files. 362 Call po_callback_comment_filepos for each of them. */ 363 static void 364 po_parse_comment_filepos (const char *s) 365 { 366 while (*s != '\0') 367 { 368 while (*s == ' ' || *s == '\t' || *s == '\n') 369 s++; 370 if (*s != '\0') 371 { 372 const char *string_start = s; 373 374 do 375 s++; 376 while (!(*s == '\0' || *s == ' ' || *s == '\t' || *s == '\n')); 377 378 /* See if there is a COLON and NUMBER after the STRING, separated 379 through optional spaces. */ 380 { 381 const char *p = s; 382 383 while (*p == ' ' || *p == '\t' || *p == '\n') 384 p++; 385 386 if (*p == ':') 387 { 388 p++; 389 390 while (*p == ' ' || *p == '\t' || *p == '\n') 391 p++; 392 393 if (*p >= '0' && *p <= '9') 394 { 395 /* Accumulate a number. */ 396 size_t n = 0; 397 398 do 399 { 400 n = n * 10 + (*p - '0'); 401 p++; 402 } 403 while (*p >= '0' && *p <= '9'); 404 405 if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n') 406 { 407 /* Parsed a GNU style file comment with spaces. */ 408 const char *string_end = s; 409 size_t string_length = string_end - string_start; 410 char *string = (char *) xmalloc (string_length + 1); 411 412 memcpy (string, string_start, string_length); 413 string[string_length] = '\0'; 414 415 po_callback_comment_filepos (string, n); 416 417 free (string); 418 419 s = p; 420 continue; 421 } 422 } 423 } 424 } 425 426 /* See if there is a COLON at the end of STRING and a NUMBER after 427 it, separated through optional spaces. */ 428 if (s[-1] == ':') 429 { 430 const char *p = s; 431 432 while (*p == ' ' || *p == '\t' || *p == '\n') 433 p++; 434 435 if (*p >= '0' && *p <= '9') 436 { 437 /* Accumulate a number. */ 438 size_t n = 0; 439 440 do 441 { 442 n = n * 10 + (*p - '0'); 443 p++; 444 } 445 while (*p >= '0' && *p <= '9'); 446 447 if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n') 448 { 449 /* Parsed a GNU style file comment with spaces. */ 450 const char *string_end = s - 1; 451 size_t string_length = string_end - string_start; 452 char *string = (char *) xmalloc (string_length + 1); 453 454 memcpy (string, string_start, string_length); 455 string[string_length] = '\0'; 456 457 po_callback_comment_filepos (string, n); 458 459 free (string); 460 461 s = p; 462 continue; 463 } 464 } 465 } 466 467 /* See if there is a COLON and NUMBER at the end of the STRING, 468 without separating spaces. */ 469 { 470 const char *p = s; 471 472 while (p > string_start) 473 { 474 p--; 475 if (!(*p >= '0' && *p <= '9')) 476 { 477 p++; 478 break; 479 } 480 } 481 482 /* p now points to the beginning of the trailing digits segment 483 at the end of STRING. */ 484 485 if (p < s 486 && p > string_start + 1 487 && p[-1] == ':') 488 { 489 /* Parsed a GNU style file comment without spaces. */ 490 const char *string_end = p - 1; 491 492 /* Accumulate a number. */ 493 { 494 size_t n = 0; 495 496 do 497 { 498 n = n * 10 + (*p - '0'); 499 p++; 500 } 501 while (p < s); 502 503 { 504 size_t string_length = string_end - string_start; 505 char *string = (char *) xmalloc (string_length + 1); 506 507 memcpy (string, string_start, string_length); 508 string[string_length] = '\0'; 509 510 po_callback_comment_filepos (string, n); 511 512 free (string); 513 514 continue; 515 } 516 } 517 } 518 } 519 520 /* Parsed a file comment without line number. */ 521 { 522 const char *string_end = s; 523 size_t string_length = string_end - string_start; 524 char *string = (char *) xmalloc (string_length + 1); 525 526 memcpy (string, string_start, string_length); 527 string[string_length] = '\0'; 528 529 po_callback_comment_filepos (string, (size_t)(-1)); 530 531 free (string); 532 } 533 } 534 } 535 } 536 537 538 /* Parse a SunOS or Solaris style file comment. 539 Syntax of SunOS style: 540 FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD COLON NUMBER 541 Syntax of Solaris style: 542 FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD NUMBER_KEYWORD COLON NUMBER 543 where 544 FILE_KEYWORD ::= "file" | "File" 545 COLON ::= ":" 546 COMMA ::= "," 547 LINE_KEYWORD ::= "line" 548 NUMBER_KEYWORD ::= "number" 549 NUMBER ::= [0-9]+ 550 Return true if parsed, false if not a comment of this form. */ 551 static bool 552 po_parse_comment_solaris_filepos (const char *s) 553 { 554 if (s[0] == ' ' 555 && (s[1] == 'F' || s[1] == 'f') 556 && s[2] == 'i' && s[3] == 'l' && s[4] == 'e' 557 && s[5] == ':') 558 { 559 const char *string_start; 560 const char *string_end; 561 562 { 563 const char *p = s + 6; 564 565 while (*p == ' ' || *p == '\t') 566 p++; 567 string_start = p; 568 } 569 570 for (string_end = string_start; *string_end != '\0'; string_end++) 571 { 572 const char *p = string_end; 573 574 while (*p == ' ' || *p == '\t') 575 p++; 576 577 if (*p == ',') 578 { 579 p++; 580 581 while (*p == ' ' || *p == '\t') 582 p++; 583 584 if (p[0] == 'l' && p[1] == 'i' && p[2] == 'n' && p[3] == 'e') 585 { 586 p += 4; 587 588 while (*p == ' ' || *p == '\t') 589 p++; 590 591 if (p[0] == 'n' && p[1] == 'u' && p[2] == 'm' 592 && p[3] == 'b' && p[4] == 'e' && p[5] == 'r') 593 { 594 p += 6; 595 while (*p == ' ' || *p == '\t') 596 p++; 597 } 598 599 if (*p == ':') 600 { 601 p++; 602 603 if (*p >= '0' && *p <= '9') 604 { 605 /* Accumulate a number. */ 606 size_t n = 0; 607 608 do 609 { 610 n = n * 10 + (*p - '0'); 611 p++; 612 } 613 while (*p >= '0' && *p <= '9'); 614 615 while (*p == ' ' || *p == '\t' || *p == '\n') 616 p++; 617 618 if (*p == '\0') 619 { 620 /* Parsed a Sun style file comment. */ 621 size_t string_length = string_end - string_start; 622 char *string = 623 (char *) xmalloc (string_length + 1); 624 625 memcpy (string, string_start, string_length); 626 string[string_length] = '\0'; 627 628 po_callback_comment_filepos (string, n); 629 630 free (string); 631 return true; 632 } 633 } 634 } 635 } 636 } 637 } 638 } 639 640 return false; 641 } 642 643 644 /* This function is called by po_gram_lex() whenever a comment is 645 seen. It analyzes the comment to see what sort it is, and then 646 dispatches it to the appropriate method: call_comment, call_comment_dot, 647 call_comment_filepos (via po_parse_comment_filepos), or 648 call_comment_special. */ 649 void 650 po_callback_comment_dispatcher (const char *s) 651 { 652 if (*s == '.') 653 po_callback_comment_dot (s + 1); 654 else if (*s == ':') 655 { 656 /* Parse the file location string. The appropriate callback will be 657 invoked. */ 658 po_parse_comment_filepos (s + 1); 659 } 660 else if (*s == ',' || *s == '!') 661 { 662 /* Get all entries in the special comment line. */ 663 po_callback_comment_special (s + 1); 664 } 665 else 666 { 667 /* It looks like a plain vanilla comment, but Solaris-style file 668 position lines do, too. Try to parse the lot. If the parse 669 succeeds, the appropriate callback will be invoked. */ 670 if (po_parse_comment_solaris_filepos (s)) 671 /* Do nothing, it is a Sun-style file pos line. */ ; 672 else 673 po_callback_comment (s); 674 } 675 } 676