1 /* Public API for GNU gettext PO files. 2 Copyright (C) 2003-2006 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 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 "gettext-po.h" 25 26 #include <limits.h> 27 #include <stdbool.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <stdarg.h> 31 #include <string.h> 32 33 #include "message.h" 34 #include "xalloc.h" 35 #include "read-catalog.h" 36 #include "read-po.h" 37 #include "write-catalog.h" 38 #include "write-po.h" 39 #include "error.h" 40 #include "xerror.h" 41 #include "po-error.h" 42 #include "po-xerror.h" 43 #include "vasprintf.h" 44 #include "format.h" 45 #include "msgl-check.h" 46 #include "gettext.h" 47 48 #define _(str) gettext(str) 49 50 51 struct po_file 52 { 53 msgdomain_list_ty *mdlp; 54 const char *real_filename; 55 const char *logical_filename; 56 const char **domains; 57 }; 58 59 struct po_message_iterator 60 { 61 po_file_t file; 62 char *domain; 63 message_list_ty *mlp; 64 size_t index; 65 }; 66 67 /* A po_message_t is actually a 'struct message_ty *'. */ 68 69 /* A po_filepos_t is actually a 'lex_pos_ty *'. */ 70 71 72 /* Version number: (major<<16) + (minor<<8) + subminor */ 73 int libgettextpo_version = LIBGETTEXTPO_VERSION; 74 75 76 /* Create an empty PO file representation in memory. */ 77 78 po_file_t 79 po_file_create (void) 80 { 81 po_file_t file; 82 83 file = (struct po_file *) xmalloc (sizeof (struct po_file)); 84 file->mdlp = msgdomain_list_alloc (false); 85 file->real_filename = _("<unnamed>"); 86 file->logical_filename = file->real_filename; 87 file->domains = NULL; 88 return file; 89 } 90 91 92 /* Read a PO file into memory. 93 Return its contents. Upon failure, return NULL and set errno. */ 94 95 po_file_t 96 po_file_read (const char *filename, po_xerror_handler_t handler) 97 { 98 FILE *fp; 99 po_file_t file; 100 101 if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0) 102 { 103 filename = _("<stdin>"); 104 fp = stdin; 105 } 106 else 107 { 108 fp = fopen (filename, "r"); 109 if (fp == NULL) 110 return NULL; 111 } 112 113 /* Establish error handler around read_catalog_stream(). */ 114 po_xerror = 115 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *)) 116 handler->xerror; 117 po_xerror2 = 118 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *)) 119 handler->xerror2; 120 gram_max_allowed_errors = UINT_MAX; 121 122 file = (struct po_file *) xmalloc (sizeof (struct po_file)); 123 file->real_filename = filename; 124 file->logical_filename = filename; 125 file->mdlp = read_catalog_stream (fp, file->real_filename, 126 file->logical_filename, &input_format_po); 127 file->domains = NULL; 128 129 /* Restore error handler. */ 130 po_xerror = textmode_xerror; 131 po_xerror2 = textmode_xerror2; 132 gram_max_allowed_errors = 20; 133 134 if (fp != stdin) 135 fclose (fp); 136 return file; 137 } 138 #undef po_file_read 139 140 po_file_t 141 po_file_read_v2 (const char *filename, po_error_handler_t handler) 142 { 143 FILE *fp; 144 po_file_t file; 145 146 if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0) 147 { 148 filename = _("<stdin>"); 149 fp = stdin; 150 } 151 else 152 { 153 fp = fopen (filename, "r"); 154 if (fp == NULL) 155 return NULL; 156 } 157 158 /* Establish error handler around read_catalog_stream(). */ 159 po_error = handler->error; 160 po_error_at_line = handler->error_at_line; 161 po_multiline_warning = handler->multiline_warning; 162 po_multiline_error = handler->multiline_error; 163 gram_max_allowed_errors = UINT_MAX; 164 165 file = (struct po_file *) xmalloc (sizeof (struct po_file)); 166 file->real_filename = filename; 167 file->logical_filename = filename; 168 file->mdlp = read_catalog_stream (fp, file->real_filename, 169 file->logical_filename, &input_format_po); 170 file->domains = NULL; 171 172 /* Restore error handler. */ 173 po_error = error; 174 po_error_at_line = error_at_line; 175 po_multiline_warning = multiline_warning; 176 po_multiline_error = multiline_error; 177 gram_max_allowed_errors = 20; 178 179 if (fp != stdin) 180 fclose (fp); 181 return file; 182 } 183 184 /* Older version for binary backward compatibility. */ 185 po_file_t 186 po_file_read (const char *filename) 187 { 188 FILE *fp; 189 po_file_t file; 190 191 if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0) 192 { 193 filename = _("<stdin>"); 194 fp = stdin; 195 } 196 else 197 { 198 fp = fopen (filename, "r"); 199 if (fp == NULL) 200 return NULL; 201 } 202 203 file = (struct po_file *) xmalloc (sizeof (struct po_file)); 204 file->real_filename = filename; 205 file->logical_filename = filename; 206 file->mdlp = read_catalog_stream (fp, file->real_filename, 207 file->logical_filename, &input_format_po); 208 file->domains = NULL; 209 210 if (fp != stdin) 211 fclose (fp); 212 return file; 213 } 214 215 216 /* Write an in-memory PO file to a file. 217 Upon failure, return NULL and set errno. */ 218 219 po_file_t 220 po_file_write (po_file_t file, const char *filename, po_xerror_handler_t handler) 221 { 222 /* Establish error handler around msgdomain_list_print(). */ 223 po_xerror = 224 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *)) 225 handler->xerror; 226 po_xerror2 = 227 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *)) 228 handler->xerror2; 229 230 msgdomain_list_print (file->mdlp, filename, &output_format_po, true, false); 231 232 /* Restore error handler. */ 233 po_xerror = textmode_xerror; 234 po_xerror2 = textmode_xerror2; 235 236 return file; 237 } 238 #undef po_file_write 239 240 /* Older version for binary backward compatibility. */ 241 po_file_t 242 po_file_write (po_file_t file, const char *filename, po_error_handler_t handler) 243 { 244 /* Establish error handler around msgdomain_list_print(). */ 245 po_error = handler->error; 246 po_error_at_line = handler->error_at_line; 247 po_multiline_warning = handler->multiline_warning; 248 po_multiline_error = handler->multiline_error; 249 250 msgdomain_list_print (file->mdlp, filename, &output_format_po, true, false); 251 252 /* Restore error handler. */ 253 po_error = error; 254 po_error_at_line = error_at_line; 255 po_multiline_warning = multiline_warning; 256 po_multiline_error = multiline_error; 257 258 return file; 259 } 260 261 262 /* Free a PO file from memory. */ 263 264 void 265 po_file_free (po_file_t file) 266 { 267 msgdomain_list_free (file->mdlp); 268 if (file->domains != NULL) 269 free (file->domains); 270 free (file); 271 } 272 273 274 /* Return the names of the domains covered by a PO file in memory. */ 275 276 const char * const * 277 po_file_domains (po_file_t file) 278 { 279 if (file->domains == NULL) 280 { 281 size_t n = file->mdlp->nitems; 282 const char **domains = 283 (const char **) xmalloc ((n + 1) * sizeof (const char *)); 284 size_t j; 285 286 for (j = 0; j < n; j++) 287 domains[j] = file->mdlp->item[j]->domain; 288 domains[n] = NULL; 289 290 file->domains = domains; 291 } 292 293 return file->domains; 294 } 295 296 297 /* Return the header entry of a domain of a PO file in memory. 298 The domain NULL denotes the default domain. 299 Return NULL if there is no header entry. */ 300 301 const char * 302 po_file_domain_header (po_file_t file, const char *domain) 303 { 304 message_list_ty *mlp; 305 size_t j; 306 307 if (domain == NULL) 308 domain = MESSAGE_DOMAIN_DEFAULT; 309 mlp = msgdomain_list_sublist (file->mdlp, domain, false); 310 if (mlp != NULL) 311 for (j = 0; j < mlp->nitems; j++) 312 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) 313 { 314 const char *header = mlp->item[j]->msgstr; 315 316 if (header != NULL) 317 return xstrdup (header); 318 else 319 return NULL; 320 } 321 return NULL; 322 } 323 324 325 /* Return the value of a field in a header entry. 326 The return value is either a freshly allocated string, to be freed by the 327 caller, or NULL. */ 328 329 char * 330 po_header_field (const char *header, const char *field) 331 { 332 size_t field_len = strlen (field); 333 const char *line; 334 335 for (line = header;;) 336 { 337 if (strncmp (line, field, field_len) == 0 338 && line[field_len] == ':' && line[field_len + 1] == ' ') 339 { 340 const char *value_start; 341 const char *value_end; 342 char *value; 343 344 value_start = line + field_len + 2; 345 value_end = strchr (value_start, '\n'); 346 if (value_end == NULL) 347 value_end = value_start + strlen (value_start); 348 349 value = (char *) xmalloc (value_end - value_start + 1); 350 memcpy (value, value_start, value_end - value_start); 351 value[value_end - value_start] = '\0'; 352 353 return value; 354 } 355 356 line = strchr (line, '\n'); 357 if (line != NULL) 358 line++; 359 else 360 break; 361 } 362 363 return NULL; 364 } 365 366 367 /* Return the header entry with a given field set to a given value. The field 368 is added if necessary. 369 The return value is a freshly allocated string. */ 370 371 char * 372 po_header_set_field (const char *header, const char *field, const char *value) 373 { 374 size_t header_len = strlen (header); 375 size_t field_len = strlen (field); 376 size_t value_len = strlen (value); 377 378 { 379 const char *line; 380 381 for (line = header;;) 382 { 383 if (strncmp (line, field, field_len) == 0 384 && line[field_len] == ':' && line[field_len + 1] == ' ') 385 { 386 const char *oldvalue_start; 387 const char *oldvalue_end; 388 size_t oldvalue_len; 389 size_t header_part1_len; 390 size_t header_part3_len; 391 size_t result_len; 392 char *result; 393 394 oldvalue_start = line + field_len + 2; 395 oldvalue_end = strchr (oldvalue_start, '\n'); 396 if (oldvalue_end == NULL) 397 oldvalue_end = oldvalue_start + strlen (oldvalue_start); 398 oldvalue_len = oldvalue_end - oldvalue_start; 399 400 header_part1_len = oldvalue_start - header; 401 header_part3_len = header + header_len - oldvalue_end; 402 result_len = header_part1_len + value_len + header_part3_len; 403 /* = header_len - oldvalue_len + value_len */ 404 result = (char *) xmalloc (result_len + 1); 405 memcpy (result, header, header_part1_len); 406 memcpy (result + header_part1_len, value, value_len); 407 memcpy (result + header_part1_len + value_len, oldvalue_end, 408 header_part3_len); 409 *(result + result_len) = '\0'; 410 411 return result; 412 } 413 414 line = strchr (line, '\n'); 415 if (line != NULL) 416 line++; 417 else 418 break; 419 } 420 } 421 { 422 size_t newline; 423 size_t result_len; 424 char *result; 425 426 newline = (header_len > 0 && header[header_len - 1] != '\n' ? 1 : 0); 427 result_len = header_len + newline + field_len + 2 + value_len + 1; 428 result = (char *) xmalloc (result_len + 1); 429 memcpy (result, header, header_len); 430 if (newline) 431 *(result + header_len) = '\n'; 432 memcpy (result + header_len + newline, field, field_len); 433 *(result + header_len + newline + field_len) = ':'; 434 *(result + header_len + newline + field_len + 1) = ' '; 435 memcpy (result + header_len + newline + field_len + 2, value, value_len); 436 *(result + header_len + newline + field_len + 2 + value_len) = '\n'; 437 *(result + result_len) = '\0'; 438 439 return result; 440 } 441 } 442 443 444 /* Create an iterator for traversing a domain of a PO file in memory. 445 The domain NULL denotes the default domain. */ 446 447 po_message_iterator_t 448 po_message_iterator (po_file_t file, const char *domain) 449 { 450 po_message_iterator_t iterator; 451 452 if (domain == NULL) 453 domain = MESSAGE_DOMAIN_DEFAULT; 454 455 iterator = 456 (struct po_message_iterator *) 457 xmalloc (sizeof (struct po_message_iterator)); 458 iterator->file = file; 459 iterator->domain = xstrdup (domain); 460 iterator->mlp = msgdomain_list_sublist (file->mdlp, domain, false); 461 iterator->index = 0; 462 463 return iterator; 464 } 465 466 467 /* Free an iterator. */ 468 469 void 470 po_message_iterator_free (po_message_iterator_t iterator) 471 { 472 free (iterator->domain); 473 free (iterator); 474 } 475 476 477 /* Return the next message, and advance the iterator. 478 Return NULL at the end of the message list. */ 479 480 po_message_t 481 po_next_message (po_message_iterator_t iterator) 482 { 483 if (iterator->mlp != NULL && iterator->index < iterator->mlp->nitems) 484 return (po_message_t) iterator->mlp->item[iterator->index++]; 485 else 486 return NULL; 487 } 488 489 490 /* Insert a message in a PO file in memory, in the domain and at the position 491 indicated by the iterator. The iterator thereby advances past the freshly 492 inserted message. */ 493 494 void 495 po_message_insert (po_message_iterator_t iterator, po_message_t message) 496 { 497 message_ty *mp = (message_ty *) message; 498 499 if (iterator->mlp == NULL) 500 /* Now we need to allocate a sublist corresponding to the iterator. */ 501 iterator->mlp = 502 msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, true); 503 /* Insert the message. */ 504 message_list_insert_at (iterator->mlp, iterator->index, mp); 505 /* Advance the iterator. */ 506 iterator->index++; 507 } 508 509 510 /* Return a freshly constructed message. 511 To finish initializing the message, you must set the msgid and msgstr. */ 512 513 po_message_t 514 po_message_create (void) 515 { 516 lex_pos_ty pos = { NULL, 0 }; 517 518 return (po_message_t) message_alloc (NULL, NULL, NULL, NULL, 0, &pos); 519 } 520 521 522 /* Return the context of a message, or NULL for a message not restricted to a 523 context. */ 524 const char * 525 po_message_msgctxt (po_message_t message) 526 { 527 message_ty *mp = (message_ty *) message; 528 529 return mp->msgctxt; 530 } 531 532 533 /* Change the context of a message. NULL means a message not restricted to a 534 context. */ 535 void 536 po_message_set_msgctxt (po_message_t message, const char *msgctxt) 537 { 538 message_ty *mp = (message_ty *) message; 539 540 if (msgctxt != mp->msgctxt) 541 { 542 char *old_msgctxt = (char *) mp->msgctxt; 543 544 mp->msgctxt = (msgctxt != NULL ? xstrdup (msgctxt) : NULL); 545 if (old_msgctxt != NULL) 546 free (old_msgctxt); 547 } 548 } 549 550 551 /* Return the msgid (untranslated English string) of a message. */ 552 553 const char * 554 po_message_msgid (po_message_t message) 555 { 556 message_ty *mp = (message_ty *) message; 557 558 return mp->msgid; 559 } 560 561 562 /* Change the msgid (untranslated English string) of a message. */ 563 564 void 565 po_message_set_msgid (po_message_t message, const char *msgid) 566 { 567 message_ty *mp = (message_ty *) message; 568 569 if (msgid != mp->msgid) 570 { 571 char *old_msgid = (char *) mp->msgid; 572 573 mp->msgid = xstrdup (msgid); 574 if (old_msgid != NULL) 575 free (old_msgid); 576 } 577 } 578 579 580 /* Return the msgid_plural (untranslated English plural string) of a message, 581 or NULL for a message without plural. */ 582 583 const char * 584 po_message_msgid_plural (po_message_t message) 585 { 586 message_ty *mp = (message_ty *) message; 587 588 return mp->msgid_plural; 589 } 590 591 592 /* Change the msgid_plural (untranslated English plural string) of a message. 593 NULL means a message without plural. */ 594 595 void 596 po_message_set_msgid_plural (po_message_t message, const char *msgid_plural) 597 { 598 message_ty *mp = (message_ty *) message; 599 600 if (msgid_plural != mp->msgid_plural) 601 { 602 char *old_msgid_plural = (char *) mp->msgid_plural; 603 604 mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL); 605 if (old_msgid_plural != NULL) 606 free (old_msgid_plural); 607 } 608 } 609 610 611 /* Return the msgstr (translation) of a message. 612 Return the empty string for an untranslated message. */ 613 614 const char * 615 po_message_msgstr (po_message_t message) 616 { 617 message_ty *mp = (message_ty *) message; 618 619 return mp->msgstr; 620 } 621 622 623 /* Change the msgstr (translation) of a message. 624 Use an empty string to denote an untranslated message. */ 625 626 void 627 po_message_set_msgstr (po_message_t message, const char *msgstr) 628 { 629 message_ty *mp = (message_ty *) message; 630 631 if (msgstr != mp->msgstr) 632 { 633 char *old_msgstr = (char *) mp->msgstr; 634 635 mp->msgstr = xstrdup (msgstr); 636 mp->msgstr_len = strlen (mp->msgstr) + 1; 637 if (old_msgstr != NULL) 638 free (old_msgstr); 639 } 640 } 641 642 643 /* Return the msgstr[index] for a message with plural handling, or 644 NULL when the index is out of range or for a message without plural. */ 645 646 const char * 647 po_message_msgstr_plural (po_message_t message, int index) 648 { 649 message_ty *mp = (message_ty *) message; 650 651 if (mp->msgid_plural != NULL && index >= 0) 652 { 653 const char *p; 654 const char *p_end = mp->msgstr + mp->msgstr_len; 655 656 for (p = mp->msgstr; ; p += strlen (p) + 1, index--) 657 { 658 if (p >= p_end) 659 return NULL; 660 if (index == 0) 661 break; 662 } 663 return p; 664 } 665 else 666 return NULL; 667 } 668 669 670 /* Change the msgstr[index] for a message with plural handling. 671 Use a NULL value at the end to reduce the number of plural forms. */ 672 673 void 674 po_message_set_msgstr_plural (po_message_t message, int index, const char *msgstr) 675 { 676 message_ty *mp = (message_ty *) message; 677 678 if (mp->msgid_plural != NULL && index >= 0) 679 { 680 char *p = (char *) mp->msgstr; 681 char *p_end = (char *) mp->msgstr + mp->msgstr_len; 682 char *copied_msgstr; 683 684 /* Special care must be taken of the case that msgstr points into the 685 mp->msgstr string list, because mp->msgstr may be relocated before we 686 are done with msgstr. */ 687 if (msgstr >= p && msgstr < p_end) 688 msgstr = copied_msgstr = xstrdup (msgstr); 689 else 690 copied_msgstr = NULL; 691 692 for (; ; p += strlen (p) + 1, index--) 693 { 694 if (p >= p_end) 695 { 696 /* Append at the end. */ 697 if (msgstr != NULL) 698 { 699 size_t new_msgstr_len = mp->msgstr_len + index + strlen (msgstr) + 1; 700 701 mp->msgstr = 702 (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len); 703 p = (char *) mp->msgstr + mp->msgstr_len; 704 for (; index > 0; index--) 705 *p++ = '\0'; 706 memcpy (p, msgstr, strlen (msgstr) + 1); 707 mp->msgstr_len = new_msgstr_len; 708 } 709 if (copied_msgstr != NULL) 710 free (copied_msgstr); 711 return; 712 } 713 if (index == 0) 714 break; 715 } 716 if (msgstr == NULL) 717 { 718 if (p + strlen (p) + 1 >= p_end) 719 { 720 /* Remove the string that starts at p. */ 721 mp->msgstr_len = p - mp->msgstr; 722 return; 723 } 724 /* It is not possible to remove an element of the string list 725 except the last one. So just replace it with the empty string. 726 That's the best we can do here. */ 727 msgstr = ""; 728 } 729 { 730 /* Replace the string that starts at p. */ 731 size_t i1 = p - mp->msgstr; 732 size_t i2before = i1 + strlen (p); 733 size_t i2after = i1 + strlen (msgstr); 734 size_t new_msgstr_len = mp->msgstr_len - i2before + i2after; 735 736 if (i2after > i2before) 737 mp->msgstr = (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len); 738 memmove ((char *) mp->msgstr + i2after, mp->msgstr + i2before, 739 mp->msgstr_len - i2before); 740 memcpy ((char *) mp->msgstr + i1, msgstr, i2after - i1); 741 mp->msgstr_len = new_msgstr_len; 742 } 743 if (copied_msgstr != NULL) 744 free (copied_msgstr); 745 } 746 } 747 748 749 /* Return the comments for a message. */ 750 751 const char * 752 po_message_comments (po_message_t message) 753 { 754 /* FIXME: memory leak. */ 755 message_ty *mp = (message_ty *) message; 756 757 if (mp->comment == NULL || mp->comment->nitems == 0) 758 return ""; 759 else 760 return string_list_join (mp->comment, '\n', '\n', true); 761 } 762 763 764 /* Change the comments for a message. 765 comments should be a multiline string, ending in a newline, or empty. */ 766 767 void 768 po_message_set_comments (po_message_t message, const char *comments) 769 { 770 message_ty *mp = (message_ty *) message; 771 string_list_ty *slp = string_list_alloc (); 772 773 { 774 char *copy = xstrdup (comments); 775 char *rest; 776 777 rest = copy; 778 while (*rest != '\0') 779 { 780 char *newline = strchr (rest, '\n'); 781 782 if (newline != NULL) 783 { 784 *newline = '\0'; 785 string_list_append (slp, rest); 786 rest = newline + 1; 787 } 788 else 789 { 790 string_list_append (slp, rest); 791 break; 792 } 793 } 794 free (copy); 795 } 796 797 if (mp->comment != NULL) 798 string_list_free (mp->comment); 799 800 mp->comment = slp; 801 } 802 803 804 /* Return the extracted comments for a message. */ 805 806 const char * 807 po_message_extracted_comments (po_message_t message) 808 { 809 /* FIXME: memory leak. */ 810 message_ty *mp = (message_ty *) message; 811 812 if (mp->comment_dot == NULL || mp->comment_dot->nitems == 0) 813 return ""; 814 else 815 return string_list_join (mp->comment_dot, '\n', '\n', true); 816 } 817 818 819 /* Change the extracted comments for a message. 820 comments should be a multiline string, ending in a newline, or empty. */ 821 822 void 823 po_message_set_extracted_comments (po_message_t message, const char *comments) 824 { 825 message_ty *mp = (message_ty *) message; 826 string_list_ty *slp = string_list_alloc (); 827 828 { 829 char *copy = xstrdup (comments); 830 char *rest; 831 832 rest = copy; 833 while (*rest != '\0') 834 { 835 char *newline = strchr (rest, '\n'); 836 837 if (newline != NULL) 838 { 839 *newline = '\0'; 840 string_list_append (slp, rest); 841 rest = newline + 1; 842 } 843 else 844 { 845 string_list_append (slp, rest); 846 break; 847 } 848 } 849 free (copy); 850 } 851 852 if (mp->comment_dot != NULL) 853 string_list_free (mp->comment_dot); 854 855 mp->comment_dot = slp; 856 } 857 858 859 /* Return the i-th file position for a message, or NULL if i is out of 860 range. */ 861 862 po_filepos_t 863 po_message_filepos (po_message_t message, int i) 864 { 865 message_ty *mp = (message_ty *) message; 866 867 if (i >= 0 && (size_t)i < mp->filepos_count) 868 return (po_filepos_t) &mp->filepos[i]; 869 else 870 return NULL; 871 } 872 873 874 /* Remove the i-th file position from a message. 875 The indices of all following file positions for the message are decremented 876 by one. */ 877 878 void 879 po_message_remove_filepos (po_message_t message, int i) 880 { 881 message_ty *mp = (message_ty *) message; 882 883 if (i >= 0) 884 { 885 size_t j = (size_t)i; 886 size_t n = mp->filepos_count; 887 888 if (j < n) 889 { 890 mp->filepos_count = n = n - 1; 891 free ((char *) mp->filepos[j].file_name); 892 for (; j < n; j++) 893 mp->filepos[j] = mp->filepos[j + 1]; 894 } 895 } 896 } 897 898 899 /* Add a file position to a message, if it is not already present for the 900 message. 901 file is the file name. 902 start_line is the line number where the string starts, or (size_t)(-1) if no 903 line number is available. */ 904 905 void 906 po_message_add_filepos (po_message_t message, const char *file, size_t start_line) 907 { 908 message_ty *mp = (message_ty *) message; 909 910 message_comment_filepos (mp, file, start_line); 911 } 912 913 914 /* Return the previous context of a message, or NULL for none. */ 915 916 const char * 917 po_message_prev_msgctxt (po_message_t message) 918 { 919 message_ty *mp = (message_ty *) message; 920 921 return mp->prev_msgctxt; 922 } 923 924 925 /* Change the previous context of a message. NULL is allowed. */ 926 927 void 928 po_message_set_prev_msgctxt (po_message_t message, const char *prev_msgctxt) 929 { 930 message_ty *mp = (message_ty *) message; 931 932 if (prev_msgctxt != mp->prev_msgctxt) 933 { 934 char *old_prev_msgctxt = (char *) mp->prev_msgctxt; 935 936 mp->prev_msgctxt = (prev_msgctxt != NULL ? xstrdup (prev_msgctxt) : NULL); 937 if (old_prev_msgctxt != NULL) 938 free (old_prev_msgctxt); 939 } 940 } 941 942 943 /* Return the previous msgid (untranslated English string) of a message, or 944 NULL for none. */ 945 946 const char * 947 po_message_prev_msgid (po_message_t message) 948 { 949 message_ty *mp = (message_ty *) message; 950 951 return mp->prev_msgid; 952 } 953 954 955 /* Change the previous msgid (untranslated English string) of a message. 956 NULL is allowed. */ 957 958 void 959 po_message_set_prev_msgid (po_message_t message, const char *prev_msgid) 960 { 961 message_ty *mp = (message_ty *) message; 962 963 if (prev_msgid != mp->prev_msgid) 964 { 965 char *old_prev_msgid = (char *) mp->prev_msgid; 966 967 mp->prev_msgid = (prev_msgid != NULL ? xstrdup (prev_msgid) : NULL); 968 if (old_prev_msgid != NULL) 969 free (old_prev_msgid); 970 } 971 } 972 973 974 /* Return the previous msgid_plural (untranslated English plural string) of a 975 message, or NULL for none. */ 976 977 const char * 978 po_message_prev_msgid_plural (po_message_t message) 979 { 980 message_ty *mp = (message_ty *) message; 981 982 return mp->prev_msgid_plural; 983 } 984 985 986 /* Change the previous msgid_plural (untranslated English plural string) of a 987 message. NULL is allowed. */ 988 989 void 990 po_message_set_prev_msgid_plural (po_message_t message, const char *prev_msgid_plural) 991 { 992 message_ty *mp = (message_ty *) message; 993 994 if (prev_msgid_plural != mp->prev_msgid_plural) 995 { 996 char *old_prev_msgid_plural = (char *) mp->prev_msgid_plural; 997 998 mp->prev_msgid_plural = 999 (prev_msgid_plural != NULL ? xstrdup (prev_msgid_plural) : NULL); 1000 if (old_prev_msgid_plural != NULL) 1001 free (old_prev_msgid_plural); 1002 } 1003 } 1004 1005 1006 /* Return true if the message is marked obsolete. */ 1007 1008 int 1009 po_message_is_obsolete (po_message_t message) 1010 { 1011 message_ty *mp = (message_ty *) message; 1012 1013 return (mp->obsolete ? 1 : 0); 1014 } 1015 1016 1017 /* Change the obsolete mark of a message. */ 1018 1019 void 1020 po_message_set_obsolete (po_message_t message, int obsolete) 1021 { 1022 message_ty *mp = (message_ty *) message; 1023 1024 mp->obsolete = obsolete; 1025 } 1026 1027 1028 /* Return true if the message is marked fuzzy. */ 1029 1030 int 1031 po_message_is_fuzzy (po_message_t message) 1032 { 1033 message_ty *mp = (message_ty *) message; 1034 1035 return (mp->is_fuzzy ? 1 : 0); 1036 } 1037 1038 1039 /* Change the fuzzy mark of a message. */ 1040 1041 void 1042 po_message_set_fuzzy (po_message_t message, int fuzzy) 1043 { 1044 message_ty *mp = (message_ty *) message; 1045 1046 mp->is_fuzzy = fuzzy; 1047 } 1048 1049 1050 /* Return true if the message is marked as being a format string of the given 1051 type (e.g. "c-format"). */ 1052 1053 int 1054 po_message_is_format (po_message_t message, const char *format_type) 1055 { 1056 message_ty *mp = (message_ty *) message; 1057 size_t len = strlen (format_type); 1058 size_t i; 1059 1060 if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0) 1061 for (i = 0; i < NFORMATS; i++) 1062 if (strlen (format_language[i]) == len - 7 1063 && memcmp (format_language[i], format_type, len - 7) == 0) 1064 /* The given format_type corresponds to (enum format_type) i. */ 1065 return (possible_format_p (mp->is_format[i]) ? 1 : 0); 1066 return 0; 1067 } 1068 1069 1070 /* Change the format string mark for a given type of a message. */ 1071 1072 void 1073 po_message_set_format (po_message_t message, const char *format_type, /*bool*/int value) 1074 { 1075 message_ty *mp = (message_ty *) message; 1076 size_t len = strlen (format_type); 1077 size_t i; 1078 1079 if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0) 1080 for (i = 0; i < NFORMATS; i++) 1081 if (strlen (format_language[i]) == len - 7 1082 && memcmp (format_language[i], format_type, len - 7) == 0) 1083 /* The given format_type corresponds to (enum format_type) i. */ 1084 mp->is_format[i] = (value ? yes : no); 1085 } 1086 1087 1088 /* Return the file name. */ 1089 1090 const char * 1091 po_filepos_file (po_filepos_t filepos) 1092 { 1093 lex_pos_ty *pp = (lex_pos_ty *) filepos; 1094 1095 return pp->file_name; 1096 } 1097 1098 1099 /* Return the line number where the string starts, or (size_t)(-1) if no line 1100 number is available. */ 1101 1102 size_t 1103 po_filepos_start_line (po_filepos_t filepos) 1104 { 1105 lex_pos_ty *pp = (lex_pos_ty *) filepos; 1106 1107 return pp->line_number; 1108 } 1109 1110 1111 /* Test whether an entire file PO file is valid, like msgfmt does it. 1112 If it is invalid, pass the reasons to the handler. */ 1113 1114 void 1115 po_file_check_all (po_file_t file, po_xerror_handler_t handler) 1116 { 1117 msgdomain_list_ty *mdlp; 1118 size_t k; 1119 1120 /* Establish error handler. */ 1121 po_xerror = 1122 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *)) 1123 handler->xerror; 1124 po_xerror2 = 1125 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *)) 1126 handler->xerror2; 1127 1128 mdlp = file->mdlp; 1129 for (k = 0; k < mdlp->nitems; k++) 1130 check_message_list (mdlp->item[k]->messages, 1, 1, 1, 0, 0, 0); 1131 1132 /* Restore error handler. */ 1133 po_xerror = textmode_xerror; 1134 po_xerror2 = textmode_xerror2; 1135 } 1136 1137 1138 /* Test a single message, to be inserted in a PO file in memory, like msgfmt 1139 does it. If it is invalid, pass the reasons to the handler. The iterator 1140 is not modified by this call; it only specifies the file and the domain. */ 1141 1142 void 1143 po_message_check_all (po_message_t message, po_message_iterator_t iterator, 1144 po_xerror_handler_t handler) 1145 { 1146 message_ty *mp = (message_ty *) message; 1147 1148 /* Establish error handler. */ 1149 po_xerror = 1150 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *)) 1151 handler->xerror; 1152 po_xerror2 = 1153 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *)) 1154 handler->xerror2; 1155 1156 /* For plural checking, combine the message and its header into a small, 1157 two-element message list. */ 1158 { 1159 message_ty *header; 1160 1161 /* Find the header. */ 1162 { 1163 message_list_ty *mlp; 1164 size_t j; 1165 1166 header = NULL; 1167 mlp = 1168 msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, false); 1169 if (mlp != NULL) 1170 for (j = 0; j < mlp->nitems; j++) 1171 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) 1172 { 1173 header = mlp->item[j]; 1174 break; 1175 } 1176 } 1177 1178 { 1179 message_ty *items[2]; 1180 struct message_list_ty ml; 1181 ml.item = items; 1182 ml.nitems = 0; 1183 ml.nitems_max = 2; 1184 ml.use_hashtable = false; 1185 1186 if (header != NULL) 1187 message_list_append (&ml, header); 1188 if (mp != header) 1189 message_list_append (&ml, mp); 1190 1191 check_message_list (&ml, 1, 1, 1, 0, 0, 0); 1192 } 1193 } 1194 1195 /* Restore error handler. */ 1196 po_xerror = textmode_xerror; 1197 po_xerror2 = textmode_xerror2; 1198 } 1199 1200 1201 /* Test whether the message translation is a valid format string if the message 1202 is marked as being a format string. If it is invalid, pass the reasons to 1203 the handler. */ 1204 void 1205 po_message_check_format (po_message_t message, po_xerror_handler_t handler) 1206 { 1207 message_ty *mp = (message_ty *) message; 1208 1209 /* Establish error handler. */ 1210 po_xerror = 1211 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *)) 1212 handler->xerror; 1213 po_xerror2 = 1214 (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *)) 1215 handler->xerror2; 1216 1217 if (!mp->obsolete) 1218 check_message (mp, &mp->pos, 0, 1, NULL, 0, 0, 0, 0); 1219 1220 /* Restore error handler. */ 1221 po_xerror = textmode_xerror; 1222 po_xerror2 = textmode_xerror2; 1223 } 1224 #undef po_message_check_format 1225 1226 /* Older version for binary backward compatibility. */ 1227 1228 /* An error logger based on the po_error function pointer. */ 1229 static void 1230 po_error_logger (const char *format, ...) 1231 __attribute__ ((__format__ (__printf__, 1, 2))); 1232 static void 1233 po_error_logger (const char *format, ...) 1234 { 1235 va_list args; 1236 char *error_message; 1237 1238 va_start (args, format); 1239 if (vasprintf (&error_message, format, args) < 0) 1240 error (EXIT_FAILURE, 0, _("memory exhausted")); 1241 va_end (args); 1242 po_error (0, 0, "%s", error_message); 1243 free (error_message); 1244 } 1245 1246 /* Test whether the message translation is a valid format string if the message 1247 is marked as being a format string. If it is invalid, pass the reasons to 1248 the handler. */ 1249 void 1250 po_message_check_format (po_message_t message, po_error_handler_t handler) 1251 { 1252 message_ty *mp = (message_ty *) message; 1253 1254 /* Establish error handler for po_error_logger(). */ 1255 po_error = handler->error; 1256 1257 check_msgid_msgstr_format (mp->msgid, mp->msgid_plural, 1258 mp->msgstr, mp->msgstr_len, 1259 mp->is_format, NULL, po_error_logger); 1260 1261 /* Restore error handler. */ 1262 po_error = error; 1263 } 1264