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