1/* Public API for GNU gettext PO files. 2 Copyright (C) 2003-2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 18 19#ifdef HAVE_CONFIG_H 20# include <config.h> 21#endif 22 23/* Specification. */ 24#include "gettext-po.h" 25 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-po.h" 35#include "write-po.h" 36#include "error.h" 37#include "xerror.h" 38#include "po-error.h" 39#include "vasprintf.h" 40#include "format.h" 41#include "gettext.h" 42 43#define _(str) gettext(str) 44 45 46struct po_file 47{ 48 msgdomain_list_ty *mdlp; 49 const char *real_filename; 50 const char *logical_filename; 51 const char **domains; 52}; 53 54struct po_message_iterator 55{ 56 po_file_t file; 57 char *domain; 58 message_list_ty *mlp; 59 size_t index; 60}; 61 62/* A po_message_t is actually a 'struct message_ty *'. */ 63 64/* A po_filepos_t is actually a 'lex_pos_ty *'. */ 65 66 67/* Version number: (major<<16) + (minor<<8) + subminor */ 68int libgettextpo_version = LIBGETTEXTPO_VERSION; 69 70 71/* Create an empty PO file representation in memory. */ 72 73po_file_t 74po_file_create (void) 75{ 76 po_file_t file; 77 78 file = (struct po_file *) xmalloc (sizeof (struct po_file)); 79 file->mdlp = msgdomain_list_alloc (false); 80 file->real_filename = _("<unnamed>"); 81 file->logical_filename = file->real_filename; 82 file->domains = NULL; 83 return file; 84} 85 86 87/* Read a PO file into memory. 88 Return its contents. Upon failure, return NULL and set errno. */ 89 90po_file_t 91po_file_read (const char *filename, po_error_handler_t handler) 92{ 93 FILE *fp; 94 po_file_t file; 95 96 if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0) 97 { 98 filename = _("<stdin>"); 99 fp = stdin; 100 } 101 else 102 { 103 fp = fopen (filename, "r"); 104 if (fp == NULL) 105 return NULL; 106 } 107 108 /* Establish error handler around read_po(). */ 109 po_error = handler->error; 110 po_error_at_line = handler->error_at_line; 111 po_multiline_warning = handler->multiline_warning; 112 po_multiline_error = handler->multiline_error; 113 114 file = (struct po_file *) xmalloc (sizeof (struct po_file)); 115 file->real_filename = filename; 116 file->logical_filename = filename; 117 file->mdlp = read_po (fp, file->real_filename, file->logical_filename); 118 file->domains = NULL; 119 120 /* Restore error handler. */ 121 po_error = error; 122 po_error_at_line = error_at_line; 123 po_multiline_warning = multiline_warning; 124 po_multiline_error = multiline_error; 125 126 if (fp != stdin) 127 fclose (fp); 128 return file; 129} 130#undef po_file_read 131 132/* Older version for binary backward compatibility. */ 133po_file_t 134po_file_read (const char *filename) 135{ 136 FILE *fp; 137 po_file_t file; 138 139 if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0) 140 { 141 filename = _("<stdin>"); 142 fp = stdin; 143 } 144 else 145 { 146 fp = fopen (filename, "r"); 147 if (fp == NULL) 148 return NULL; 149 } 150 151 file = (struct po_file *) xmalloc (sizeof (struct po_file)); 152 file->real_filename = filename; 153 file->logical_filename = filename; 154 file->mdlp = read_po (fp, file->real_filename, file->logical_filename); 155 file->domains = NULL; 156 157 if (fp != stdin) 158 fclose (fp); 159 return file; 160} 161 162 163/* Write an in-memory PO file to a file. 164 Upon failure, return NULL and set errno. */ 165 166po_file_t 167po_file_write (po_file_t file, const char *filename, po_error_handler_t handler) 168{ 169 /* Establish error handler around msgdomain_list_print(). */ 170 po_error = handler->error; 171 po_error_at_line = handler->error_at_line; 172 po_multiline_warning = handler->multiline_warning; 173 po_multiline_error = handler->multiline_error; 174 175 msgdomain_list_print (file->mdlp, filename, true, false); 176 177 /* Restore error handler. */ 178 po_error = error; 179 po_error_at_line = error_at_line; 180 po_multiline_warning = multiline_warning; 181 po_multiline_error = multiline_error; 182 183 return file; 184} 185 186 187/* Free a PO file from memory. */ 188 189void 190po_file_free (po_file_t file) 191{ 192 msgdomain_list_free (file->mdlp); 193 if (file->domains != NULL) 194 free (file->domains); 195 free (file); 196} 197 198 199/* Return the names of the domains covered by a PO file in memory. */ 200 201const char * const * 202po_file_domains (po_file_t file) 203{ 204 if (file->domains == NULL) 205 { 206 size_t n = file->mdlp->nitems; 207 const char **domains = 208 (const char **) xmalloc ((n + 1) * sizeof (const char *)); 209 size_t j; 210 211 for (j = 0; j < n; j++) 212 domains[j] = file->mdlp->item[j]->domain; 213 domains[n] = NULL; 214 215 file->domains = domains; 216 } 217 218 return file->domains; 219} 220 221 222/* Return the header entry of a domain of a PO file in memory. 223 The domain NULL denotes the default domain. 224 Return NULL if there is no header entry. */ 225 226const char * 227po_file_domain_header (po_file_t file, const char *domain) 228{ 229 message_list_ty *mlp; 230 size_t j; 231 232 if (domain == NULL) 233 domain = MESSAGE_DOMAIN_DEFAULT; 234 mlp = msgdomain_list_sublist (file->mdlp, domain, false); 235 if (mlp != NULL) 236 for (j = 0; j < mlp->nitems; j++) 237 if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete) 238 { 239 const char *header = mlp->item[j]->msgstr; 240 241 if (header != NULL) 242 return xstrdup (header); 243 else 244 return NULL; 245 } 246 return NULL; 247} 248 249 250/* Return the value of a field in a header entry. 251 The return value is either a freshly allocated string, to be freed by the 252 caller, or NULL. */ 253 254char * 255po_header_field (const char *header, const char *field) 256{ 257 size_t field_len = strlen (field); 258 const char *line; 259 260 for (line = header;;) 261 { 262 if (strncmp (line, field, field_len) == 0 263 && line[field_len] == ':' && line[field_len + 1] == ' ') 264 { 265 const char *value_start; 266 const char *value_end; 267 char *value; 268 269 value_start = line + field_len + 2; 270 value_end = strchr (value_start, '\n'); 271 if (value_end == NULL) 272 value_end = value_start + strlen (value_start); 273 274 value = (char *) xmalloc (value_end - value_start + 1); 275 memcpy (value, value_start, value_end - value_start); 276 value[value_end - value_start] = '\0'; 277 278 return value; 279 } 280 281 line = strchr (line, '\n'); 282 if (line != NULL) 283 line++; 284 else 285 break; 286 } 287 288 return NULL; 289} 290 291 292/* Return the header entry with a given field set to a given value. The field 293 is added if necessary. 294 The return value is a freshly allocated string. */ 295 296char * 297po_header_set_field (const char *header, const char *field, const char *value) 298{ 299 size_t header_len = strlen (header); 300 size_t field_len = strlen (field); 301 size_t value_len = strlen (value); 302 303 { 304 const char *line; 305 306 for (line = header;;) 307 { 308 if (strncmp (line, field, field_len) == 0 309 && line[field_len] == ':' && line[field_len + 1] == ' ') 310 { 311 const char *oldvalue_start; 312 const char *oldvalue_end; 313 size_t oldvalue_len; 314 size_t header_part1_len; 315 size_t header_part3_len; 316 size_t result_len; 317 char *result; 318 319 oldvalue_start = line + field_len + 2; 320 oldvalue_end = strchr (oldvalue_start, '\n'); 321 if (oldvalue_end == NULL) 322 oldvalue_end = oldvalue_start + strlen (oldvalue_start); 323 oldvalue_len = oldvalue_end - oldvalue_start; 324 325 header_part1_len = oldvalue_start - header; 326 header_part3_len = header + header_len - oldvalue_end; 327 result_len = header_part1_len + value_len + header_part3_len; 328 /* = header_len - oldvalue_len + value_len */ 329 result = (char *) xmalloc (result_len + 1); 330 memcpy (result, header, header_part1_len); 331 memcpy (result + header_part1_len, value, value_len); 332 memcpy (result + header_part1_len + value_len, oldvalue_end, 333 header_part3_len); 334 *(result + result_len) = '\0'; 335 336 return result; 337 } 338 339 line = strchr (line, '\n'); 340 if (line != NULL) 341 line++; 342 else 343 break; 344 } 345 } 346 { 347 size_t newline; 348 size_t result_len; 349 char *result; 350 351 newline = (header_len > 0 && header[header_len - 1] != '\n' ? 1 : 0); 352 result_len = header_len + newline + field_len + 2 + value_len + 1; 353 result = (char *) xmalloc (result_len + 1); 354 memcpy (result, header, header_len); 355 if (newline) 356 *(result + header_len) = '\n'; 357 memcpy (result + header_len + newline, field, field_len); 358 *(result + header_len + newline + field_len) = ':'; 359 *(result + header_len + newline + field_len + 1) = ' '; 360 memcpy (result + header_len + newline + field_len + 2, value, value_len); 361 *(result + header_len + newline + field_len + 2 + value_len) = '\n'; 362 *(result + result_len) = '\0'; 363 364 return result; 365 } 366} 367 368 369/* Create an iterator for traversing a domain of a PO file in memory. 370 The domain NULL denotes the default domain. */ 371 372po_message_iterator_t 373po_message_iterator (po_file_t file, const char *domain) 374{ 375 po_message_iterator_t iterator; 376 377 if (domain == NULL) 378 domain = MESSAGE_DOMAIN_DEFAULT; 379 380 iterator = 381 (struct po_message_iterator *) 382 xmalloc (sizeof (struct po_message_iterator)); 383 iterator->file = file; 384 iterator->domain = xstrdup (domain); 385 iterator->mlp = msgdomain_list_sublist (file->mdlp, domain, false); 386 iterator->index = 0; 387 388 return iterator; 389} 390 391 392/* Free an iterator. */ 393 394void 395po_message_iterator_free (po_message_iterator_t iterator) 396{ 397 free (iterator->domain); 398 free (iterator); 399} 400 401 402/* Return the next message, and advance the iterator. 403 Return NULL at the end of the message list. */ 404 405po_message_t 406po_next_message (po_message_iterator_t iterator) 407{ 408 if (iterator->mlp != NULL && iterator->index < iterator->mlp->nitems) 409 return (po_message_t) iterator->mlp->item[iterator->index++]; 410 else 411 return NULL; 412} 413 414 415/* Insert a message in a PO file in memory, in the domain and at the position 416 indicated by the iterator. The iterator thereby advances past the freshly 417 inserted message. */ 418 419void 420po_message_insert (po_message_iterator_t iterator, po_message_t message) 421{ 422 message_ty *mp = (message_ty *) message; 423 424 if (iterator->mlp == NULL) 425 /* Now we need to allocate a sublist corresponding to the iterator. */ 426 iterator->mlp = 427 msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, true); 428 /* Insert the message. */ 429 message_list_insert_at (iterator->mlp, iterator->index, mp); 430 /* Advance the iterator. */ 431 iterator->index++; 432} 433 434 435/* Return a freshly constructed message. 436 To finish initializing the message, you must set the msgid and msgstr. */ 437 438po_message_t 439po_message_create (void) 440{ 441 lex_pos_ty pos = { NULL, 0 }; 442 443 return (po_message_t) message_alloc (NULL, NULL, NULL, 0, &pos); 444} 445 446 447/* Return the msgid (untranslated English string) of a message. */ 448 449const char * 450po_message_msgid (po_message_t message) 451{ 452 message_ty *mp = (message_ty *) message; 453 454 return mp->msgid; 455} 456 457 458/* Change the msgid (untranslated English string) of a message. */ 459 460void 461po_message_set_msgid (po_message_t message, const char *msgid) 462{ 463 message_ty *mp = (message_ty *) message; 464 465 if (msgid != mp->msgid) 466 { 467 char *old_msgid = (char *) mp->msgid; 468 469 mp->msgid = xstrdup (msgid); 470 if (old_msgid != NULL) 471 free (old_msgid); 472 } 473} 474 475 476/* Return the msgid_plural (untranslated English plural string) of a message, 477 or NULL for a message without plural. */ 478 479const char * 480po_message_msgid_plural (po_message_t message) 481{ 482 message_ty *mp = (message_ty *) message; 483 484 return mp->msgid_plural; 485} 486 487 488/* Change the msgid_plural (untranslated English plural string) of a message. 489 NULL means a message without plural. */ 490 491void 492po_message_set_msgid_plural (po_message_t message, const char *msgid_plural) 493{ 494 message_ty *mp = (message_ty *) message; 495 496 if (msgid_plural != mp->msgid_plural) 497 { 498 char *old_msgid_plural = (char *) mp->msgid_plural; 499 500 mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL); 501 if (old_msgid_plural != NULL) 502 free (old_msgid_plural); 503 } 504} 505 506 507/* Return the msgstr (translation) of a message. 508 Return the empty string for an untranslated message. */ 509 510const char * 511po_message_msgstr (po_message_t message) 512{ 513 message_ty *mp = (message_ty *) message; 514 515 return mp->msgstr; 516} 517 518 519/* Change the msgstr (translation) of a message. 520 Use an empty string to denote an untranslated message. */ 521 522void 523po_message_set_msgstr (po_message_t message, const char *msgstr) 524{ 525 message_ty *mp = (message_ty *) message; 526 527 if (msgstr != mp->msgstr) 528 { 529 char *old_msgstr = (char *) mp->msgstr; 530 531 mp->msgstr = xstrdup (msgstr); 532 mp->msgstr_len = strlen (mp->msgstr) + 1; 533 if (old_msgstr != NULL) 534 free (old_msgstr); 535 } 536} 537 538 539/* Return the msgstr[index] for a message with plural handling, or 540 NULL when the index is out of range or for a message without plural. */ 541 542const char * 543po_message_msgstr_plural (po_message_t message, int index) 544{ 545 message_ty *mp = (message_ty *) message; 546 547 if (mp->msgid_plural != NULL && index >= 0) 548 { 549 const char *p; 550 const char *p_end = mp->msgstr + mp->msgstr_len; 551 552 for (p = mp->msgstr; ; p += strlen (p) + 1, index--) 553 { 554 if (p >= p_end) 555 return NULL; 556 if (index == 0) 557 break; 558 } 559 return p; 560 } 561 else 562 return NULL; 563} 564 565 566/* Change the msgstr[index] for a message with plural handling. 567 Use a NULL value at the end to reduce the number of plural forms. */ 568 569void 570po_message_set_msgstr_plural (po_message_t message, int index, const char *msgstr) 571{ 572 message_ty *mp = (message_ty *) message; 573 574 if (mp->msgid_plural != NULL && index >= 0) 575 { 576 char *p = (char *) mp->msgstr; 577 char *p_end = (char *) mp->msgstr + mp->msgstr_len; 578 char *copied_msgstr; 579 580 /* Special care must be taken of the case that msgstr points into the 581 mp->msgstr string list, because mp->msgstr may be relocated before we 582 are done with msgstr. */ 583 if (msgstr >= p && msgstr < p_end) 584 msgstr = copied_msgstr = xstrdup (msgstr); 585 else 586 copied_msgstr = NULL; 587 588 for (; ; p += strlen (p) + 1, index--) 589 { 590 if (p >= p_end) 591 { 592 /* Append at the end. */ 593 if (msgstr != NULL) 594 { 595 size_t new_msgstr_len = mp->msgstr_len + index + strlen (msgstr) + 1; 596 597 mp->msgstr = 598 (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len); 599 p = (char *) mp->msgstr + mp->msgstr_len; 600 for (; index > 0; index--) 601 *p++ = '\0'; 602 memcpy (p, msgstr, strlen (msgstr) + 1); 603 mp->msgstr_len = new_msgstr_len; 604 } 605 if (copied_msgstr != NULL) 606 free (copied_msgstr); 607 return; 608 } 609 if (index == 0) 610 break; 611 } 612 if (msgstr == NULL) 613 { 614 if (p + strlen (p) + 1 >= p_end) 615 { 616 /* Remove the string that starts at p. */ 617 mp->msgstr_len = p - mp->msgstr; 618 return; 619 } 620 /* It is not possible to remove an element of the string list 621 except the last one. So just replace it with the empty string. 622 That's the best we can do here. */ 623 msgstr = ""; 624 } 625 { 626 /* Replace the string that starts at p. */ 627 size_t i1 = p - mp->msgstr; 628 size_t i2before = i1 + strlen (p); 629 size_t i2after = i1 + strlen (msgstr); 630 size_t new_msgstr_len = mp->msgstr_len - i2before + i2after; 631 632 if (i2after > i2before) 633 mp->msgstr = (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len); 634 memmove ((char *) mp->msgstr + i2after, mp->msgstr + i2before, 635 mp->msgstr_len - i2before); 636 memcpy ((char *) mp->msgstr + i1, msgstr, i2after - i1); 637 mp->msgstr_len = new_msgstr_len; 638 } 639 if (copied_msgstr != NULL) 640 free (copied_msgstr); 641 } 642} 643 644 645/* Return the comments for a message. */ 646 647const char * 648po_message_comments (po_message_t message) 649{ 650 /* FIXME: memory leak. */ 651 message_ty *mp = (message_ty *) message; 652 653 if (mp->comment == NULL || mp->comment->nitems == 0) 654 return ""; 655 else 656 return string_list_join (mp->comment, '\n', '\n', true); 657} 658 659 660/* Change the comments for a message. 661 comments should be a multiline string, ending in a newline, or empty. */ 662 663void 664po_message_set_comments (po_message_t message, const char *comments) 665{ 666 message_ty *mp = (message_ty *) message; 667 string_list_ty *slp = string_list_alloc (); 668 669 { 670 char *copy = xstrdup (comments); 671 char *rest; 672 673 rest = copy; 674 while (*rest != '\0') 675 { 676 char *newline = strchr (rest, '\n'); 677 678 if (newline != NULL) 679 { 680 *newline = '\0'; 681 string_list_append (slp, rest); 682 rest = newline + 1; 683 } 684 else 685 { 686 string_list_append (slp, rest); 687 break; 688 } 689 } 690 free (copy); 691 } 692 693 if (mp->comment != NULL) 694 string_list_free (mp->comment); 695 696 mp->comment = slp; 697} 698 699 700/* Return the extracted comments for a message. */ 701 702const char * 703po_message_extracted_comments (po_message_t message) 704{ 705 /* FIXME: memory leak. */ 706 message_ty *mp = (message_ty *) message; 707 708 if (mp->comment_dot == NULL || mp->comment_dot->nitems == 0) 709 return ""; 710 else 711 return string_list_join (mp->comment_dot, '\n', '\n', true); 712} 713 714 715/* Change the extracted comments for a message. 716 comments should be a multiline string, ending in a newline, or empty. */ 717 718void 719po_message_set_extracted_comments (po_message_t message, const char *comments) 720{ 721 message_ty *mp = (message_ty *) message; 722 string_list_ty *slp = string_list_alloc (); 723 724 { 725 char *copy = xstrdup (comments); 726 char *rest; 727 728 rest = copy; 729 while (*rest != '\0') 730 { 731 char *newline = strchr (rest, '\n'); 732 733 if (newline != NULL) 734 { 735 *newline = '\0'; 736 string_list_append (slp, rest); 737 rest = newline + 1; 738 } 739 else 740 { 741 string_list_append (slp, rest); 742 break; 743 } 744 } 745 free (copy); 746 } 747 748 if (mp->comment_dot != NULL) 749 string_list_free (mp->comment_dot); 750 751 mp->comment_dot = slp; 752} 753 754 755/* Return the i-th file position for a message, or NULL if i is out of 756 range. */ 757 758po_filepos_t 759po_message_filepos (po_message_t message, int i) 760{ 761 message_ty *mp = (message_ty *) message; 762 763 if (i >= 0 && (size_t)i < mp->filepos_count) 764 return (po_filepos_t) &mp->filepos[i]; 765 else 766 return NULL; 767} 768 769 770/* Remove the i-th file position from a message. 771 The indices of all following file positions for the message are decremented 772 by one. */ 773 774void 775po_message_remove_filepos (po_message_t message, int i) 776{ 777 message_ty *mp = (message_ty *) message; 778 779 if (i >= 0) 780 { 781 size_t j = (size_t)i; 782 size_t n = mp->filepos_count; 783 784 if (j < n) 785 { 786 mp->filepos_count = n = n - 1; 787 free ((char *) mp->filepos[j].file_name); 788 for (; j < n; j++) 789 mp->filepos[j] = mp->filepos[j + 1]; 790 } 791 } 792} 793 794 795/* Add a file position to a message, if it is not already present for the 796 message. 797 file is the file name. 798 start_line is the line number where the string starts, or (size_t)(-1) if no 799 line number is available. */ 800 801void 802po_message_add_filepos (po_message_t message, const char *file, size_t start_line) 803{ 804 message_ty *mp = (message_ty *) message; 805 806 message_comment_filepos (mp, file, start_line); 807} 808 809 810/* Return true if the message is marked obsolete. */ 811 812int 813po_message_is_obsolete (po_message_t message) 814{ 815 message_ty *mp = (message_ty *) message; 816 817 return (mp->obsolete ? 1 : 0); 818} 819 820 821/* Change the obsolete mark of a message. */ 822 823void 824po_message_set_obsolete (po_message_t message, int obsolete) 825{ 826 message_ty *mp = (message_ty *) message; 827 828 mp->obsolete = obsolete; 829} 830 831 832/* Return true if the message is marked fuzzy. */ 833 834int 835po_message_is_fuzzy (po_message_t message) 836{ 837 message_ty *mp = (message_ty *) message; 838 839 return (mp->is_fuzzy ? 1 : 0); 840} 841 842 843/* Change the fuzzy mark of a message. */ 844 845void 846po_message_set_fuzzy (po_message_t message, int fuzzy) 847{ 848 message_ty *mp = (message_ty *) message; 849 850 mp->is_fuzzy = fuzzy; 851} 852 853 854/* Return true if the message is marked as being a format string of the given 855 type (e.g. "c-format"). */ 856 857int 858po_message_is_format (po_message_t message, const char *format_type) 859{ 860 message_ty *mp = (message_ty *) message; 861 size_t len = strlen (format_type); 862 size_t i; 863 864 if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0) 865 for (i = 0; i < NFORMATS; i++) 866 if (strlen (format_language[i]) == len - 7 867 && memcmp (format_language[i], format_type, len - 7) == 0) 868 /* The given format_type corresponds to (enum format_type) i. */ 869 return (possible_format_p (mp->is_format[i]) ? 1 : 0); 870 return 0; 871} 872 873 874/* Change the format string mark for a given type of a message. */ 875 876void 877po_message_set_format (po_message_t message, const char *format_type, /*bool*/int value) 878{ 879 message_ty *mp = (message_ty *) message; 880 size_t len = strlen (format_type); 881 size_t i; 882 883 if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0) 884 for (i = 0; i < NFORMATS; i++) 885 if (strlen (format_language[i]) == len - 7 886 && memcmp (format_language[i], format_type, len - 7) == 0) 887 /* The given format_type corresponds to (enum format_type) i. */ 888 mp->is_format[i] = (value ? yes : no); 889} 890 891 892/* An error logger based on the po_error function pointer. */ 893static void 894po_error_logger (const char *format, ...) 895{ 896 va_list args; 897 char *error_message; 898 899 va_start (args, format); 900 if (vasprintf (&error_message, format, args) < 0) 901 error (EXIT_FAILURE, 0, _("memory exhausted")); 902 va_end (args); 903 po_error (0, 0, "%s", error_message); 904 free (error_message); 905} 906 907/* Test whether the message translation is a valid format string if the message 908 is marked as being a format string. If it is invalid, pass the reasons to 909 the handler. */ 910void 911po_message_check_format (po_message_t message, po_error_handler_t handler) 912{ 913 message_ty *mp = (message_ty *) message; 914 915 /* Establish error handler for po_error_logger(). */ 916 po_error = handler->error; 917 918 check_msgid_msgstr_format (mp->msgid, mp->msgid_plural, 919 mp->msgstr, mp->msgstr_len, 920 mp->is_format, po_error_logger); 921 922 /* Restore error handler. */ 923 po_error = error; 924} 925 926 927/* Return the file name. */ 928 929const char * 930po_filepos_file (po_filepos_t filepos) 931{ 932 lex_pos_ty *pp = (lex_pos_ty *) filepos; 933 934 return pp->file_name; 935} 936 937 938/* Return the line number where the string starts, or (size_t)(-1) if no line 939 number is available. */ 940 941size_t 942po_filepos_start_line (po_filepos_t filepos) 943{ 944 lex_pos_ty *pp = (lex_pos_ty *) filepos; 945 946 return pp->line_number; 947} 948