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.
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)
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.
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. */
24 #include "gettext-po.h"
39 #include "vasprintf.h"
43 #define _(str) gettext(str)
48 msgdomain_list_ty
*mdlp
;
49 const char *real_filename
;
50 const char *logical_filename
;
54 struct po_message_iterator
62 /* A po_message_t is actually a 'struct message_ty *'. */
64 /* A po_filepos_t is actually a 'lex_pos_ty *'. */
67 /* Version number: (major<<16) + (minor<<8) + subminor */
68 int libgettextpo_version
= LIBGETTEXTPO_VERSION
;
71 /* Create an empty PO file representation in memory. */
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
;
87 /* Read a PO file into memory.
88 Return its contents. Upon failure, return NULL and set errno. */
91 po_file_read (const char *filename
, po_error_handler_t handler
)
96 if (strcmp (filename
, "-") == 0 || strcmp (filename
, "/dev/stdin") == 0)
98 filename
= _("<stdin>");
103 fp
= fopen (filename
, "r");
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
;
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
;
120 /* Restore error handler. */
122 po_error_at_line
= error_at_line
;
123 po_multiline_warning
= multiline_warning
;
124 po_multiline_error
= multiline_error
;
132 /* Older version for binary backward compatibility. */
134 po_file_read (const char *filename
)
139 if (strcmp (filename
, "-") == 0 || strcmp (filename
, "/dev/stdin") == 0)
141 filename
= _("<stdin>");
146 fp
= fopen (filename
, "r");
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
;
163 /* Write an in-memory PO file to a file.
164 Upon failure, return NULL and set errno. */
167 po_file_write (po_file_t file
, const char *filename
, po_error_handler_t handler
)
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
;
175 msgdomain_list_print (file
->mdlp
, filename
, true, false);
177 /* Restore error handler. */
179 po_error_at_line
= error_at_line
;
180 po_multiline_warning
= multiline_warning
;
181 po_multiline_error
= multiline_error
;
187 /* Free a PO file from memory. */
190 po_file_free (po_file_t file
)
192 msgdomain_list_free (file
->mdlp
);
193 if (file
->domains
!= NULL
)
194 free (file
->domains
);
199 /* Return the names of the domains covered by a PO file in memory. */
202 po_file_domains (po_file_t file
)
204 if (file
->domains
== NULL
)
206 size_t n
= file
->mdlp
->nitems
;
207 const char **domains
=
208 (const char **) xmalloc ((n
+ 1) * sizeof (const char *));
211 for (j
= 0; j
< n
; j
++)
212 domains
[j
] = file
->mdlp
->item
[j
]->domain
;
215 file
->domains
= domains
;
218 return file
->domains
;
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. */
227 po_file_domain_header (po_file_t file
, const char *domain
)
229 message_list_ty
*mlp
;
233 domain
= MESSAGE_DOMAIN_DEFAULT
;
234 mlp
= msgdomain_list_sublist (file
->mdlp
, domain
, false);
236 for (j
= 0; j
< mlp
->nitems
; j
++)
237 if (mlp
->item
[j
]->msgid
[0] == '\0' && !mlp
->item
[j
]->obsolete
)
239 const char *header
= mlp
->item
[j
]->msgstr
;
242 return xstrdup (header
);
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
255 po_header_field (const char *header
, const char *field
)
257 size_t field_len
= strlen (field
);
260 for (line
= header
;;)
262 if (strncmp (line
, field
, field_len
) == 0
263 && line
[field_len
] == ':' && line
[field_len
+ 1] == ' ')
265 const char *value_start
;
266 const char *value_end
;
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
);
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';
281 line
= strchr (line
, '\n');
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. */
297 po_header_set_field (const char *header
, const char *field
, const char *value
)
299 size_t header_len
= strlen (header
);
300 size_t field_len
= strlen (field
);
301 size_t value_len
= strlen (value
);
306 for (line
= header
;;)
308 if (strncmp (line
, field
, field_len
) == 0
309 && line
[field_len
] == ':' && line
[field_len
+ 1] == ' ')
311 const char *oldvalue_start
;
312 const char *oldvalue_end
;
314 size_t header_part1_len
;
315 size_t header_part3_len
;
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
;
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
,
334 *(result
+ result_len
) = '\0';
339 line
= strchr (line
, '\n');
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
);
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';
369 /* Create an iterator for traversing a domain of a PO file in memory.
370 The domain NULL denotes the default domain. */
372 po_message_iterator_t
373 po_message_iterator (po_file_t file
, const char *domain
)
375 po_message_iterator_t iterator
;
378 domain
= MESSAGE_DOMAIN_DEFAULT
;
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);
392 /* Free an iterator. */
395 po_message_iterator_free (po_message_iterator_t iterator
)
397 free (iterator
->domain
);
402 /* Return the next message, and advance the iterator.
403 Return NULL at the end of the message list. */
406 po_next_message (po_message_iterator_t iterator
)
408 if (iterator
->mlp
!= NULL
&& iterator
->index
< iterator
->mlp
->nitems
)
409 return (po_message_t
) iterator
->mlp
->item
[iterator
->index
++];
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
420 po_message_insert (po_message_iterator_t iterator
, po_message_t message
)
422 message_ty
*mp
= (message_ty
*) message
;
424 if (iterator
->mlp
== NULL
)
425 /* Now we need to allocate a sublist corresponding to the iterator. */
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. */
435 /* Return a freshly constructed message.
436 To finish initializing the message, you must set the msgid and msgstr. */
439 po_message_create (void)
441 lex_pos_ty pos
= { NULL
, 0 };
443 return (po_message_t
) message_alloc (NULL
, NULL
, NULL
, 0, &pos
);
447 /* Return the msgid (untranslated English string) of a message. */
450 po_message_msgid (po_message_t message
)
452 message_ty
*mp
= (message_ty
*) message
;
458 /* Change the msgid (untranslated English string) of a message. */
461 po_message_set_msgid (po_message_t message
, const char *msgid
)
463 message_ty
*mp
= (message_ty
*) message
;
465 if (msgid
!= mp
->msgid
)
467 char *old_msgid
= (char *) mp
->msgid
;
469 mp
->msgid
= xstrdup (msgid
);
470 if (old_msgid
!= NULL
)
476 /* Return the msgid_plural (untranslated English plural string) of a message,
477 or NULL for a message without plural. */
480 po_message_msgid_plural (po_message_t message
)
482 message_ty
*mp
= (message_ty
*) message
;
484 return mp
->msgid_plural
;
488 /* Change the msgid_plural (untranslated English plural string) of a message.
489 NULL means a message without plural. */
492 po_message_set_msgid_plural (po_message_t message
, const char *msgid_plural
)
494 message_ty
*mp
= (message_ty
*) message
;
496 if (msgid_plural
!= mp
->msgid_plural
)
498 char *old_msgid_plural
= (char *) mp
->msgid_plural
;
500 mp
->msgid_plural
= (msgid_plural
!= NULL
? xstrdup (msgid_plural
) : NULL
);
501 if (old_msgid_plural
!= NULL
)
502 free (old_msgid_plural
);
507 /* Return the msgstr (translation) of a message.
508 Return the empty string for an untranslated message. */
511 po_message_msgstr (po_message_t message
)
513 message_ty
*mp
= (message_ty
*) message
;
519 /* Change the msgstr (translation) of a message.
520 Use an empty string to denote an untranslated message. */
523 po_message_set_msgstr (po_message_t message
, const char *msgstr
)
525 message_ty
*mp
= (message_ty
*) message
;
527 if (msgstr
!= mp
->msgstr
)
529 char *old_msgstr
= (char *) mp
->msgstr
;
531 mp
->msgstr
= xstrdup (msgstr
);
532 mp
->msgstr_len
= strlen (mp
->msgstr
) + 1;
533 if (old_msgstr
!= NULL
)
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. */
543 po_message_msgstr_plural (po_message_t message
, int index
)
545 message_ty
*mp
= (message_ty
*) message
;
547 if (mp
->msgid_plural
!= NULL
&& index
>= 0)
550 const char *p_end
= mp
->msgstr
+ mp
->msgstr_len
;
552 for (p
= mp
->msgstr
; ; p
+= strlen (p
) + 1, index
--)
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. */
570 po_message_set_msgstr_plural (po_message_t message
, int index
, const char *msgstr
)
572 message_ty
*mp
= (message_ty
*) message
;
574 if (mp
->msgid_plural
!= NULL
&& index
>= 0)
576 char *p
= (char *) mp
->msgstr
;
577 char *p_end
= (char *) mp
->msgstr
+ mp
->msgstr_len
;
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
);
586 copied_msgstr
= NULL
;
588 for (; ; p
+= strlen (p
) + 1, index
--)
592 /* Append at the end. */
595 size_t new_msgstr_len
= mp
->msgstr_len
+ index
+ strlen (msgstr
) + 1;
598 (char *) xrealloc ((char *) mp
->msgstr
, new_msgstr_len
);
599 p
= (char *) mp
->msgstr
+ mp
->msgstr_len
;
600 for (; index
> 0; index
--)
602 memcpy (p
, msgstr
, strlen (msgstr
) + 1);
603 mp
->msgstr_len
= new_msgstr_len
;
605 if (copied_msgstr
!= NULL
)
606 free (copied_msgstr
);
614 if (p
+ strlen (p
) + 1 >= p_end
)
616 /* Remove the string that starts at p. */
617 mp
->msgstr_len
= p
- mp
->msgstr
;
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. */
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
;
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
;
639 if (copied_msgstr
!= NULL
)
640 free (copied_msgstr
);
645 /* Return the comments for a message. */
648 po_message_comments (po_message_t message
)
650 /* FIXME: memory leak. */
651 message_ty
*mp
= (message_ty
*) message
;
653 if (mp
->comment
== NULL
|| mp
->comment
->nitems
== 0)
656 return string_list_join (mp
->comment
, '\n', '\n', true);
660 /* Change the comments for a message.
661 comments should be a multiline string, ending in a newline, or empty. */
664 po_message_set_comments (po_message_t message
, const char *comments
)
666 message_ty
*mp
= (message_ty
*) message
;
667 string_list_ty
*slp
= string_list_alloc ();
670 char *copy
= xstrdup (comments
);
674 while (*rest
!= '\0')
676 char *newline
= strchr (rest
, '\n');
681 string_list_append (slp
, rest
);
686 string_list_append (slp
, rest
);
693 if (mp
->comment
!= NULL
)
694 string_list_free (mp
->comment
);
700 /* Return the extracted comments for a message. */
703 po_message_extracted_comments (po_message_t message
)
705 /* FIXME: memory leak. */
706 message_ty
*mp
= (message_ty
*) message
;
708 if (mp
->comment_dot
== NULL
|| mp
->comment_dot
->nitems
== 0)
711 return string_list_join (mp
->comment_dot
, '\n', '\n', true);
715 /* Change the extracted comments for a message.
716 comments should be a multiline string, ending in a newline, or empty. */
719 po_message_set_extracted_comments (po_message_t message
, const char *comments
)
721 message_ty
*mp
= (message_ty
*) message
;
722 string_list_ty
*slp
= string_list_alloc ();
725 char *copy
= xstrdup (comments
);
729 while (*rest
!= '\0')
731 char *newline
= strchr (rest
, '\n');
736 string_list_append (slp
, rest
);
741 string_list_append (slp
, rest
);
748 if (mp
->comment_dot
!= NULL
)
749 string_list_free (mp
->comment_dot
);
751 mp
->comment_dot
= slp
;
755 /* Return the i-th file position for a message, or NULL if i is out of
759 po_message_filepos (po_message_t message
, int i
)
761 message_ty
*mp
= (message_ty
*) message
;
763 if (i
>= 0 && (size_t)i
< mp
->filepos_count
)
764 return (po_filepos_t
) &mp
->filepos
[i
];
770 /* Remove the i-th file position from a message.
771 The indices of all following file positions for the message are decremented
775 po_message_remove_filepos (po_message_t message
, int i
)
777 message_ty
*mp
= (message_ty
*) message
;
781 size_t j
= (size_t)i
;
782 size_t n
= mp
->filepos_count
;
786 mp
->filepos_count
= n
= n
- 1;
787 free ((char *) mp
->filepos
[j
].file_name
);
789 mp
->filepos
[j
] = mp
->filepos
[j
+ 1];
795 /* Add a file position to a message, if it is not already present for the
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. */
802 po_message_add_filepos (po_message_t message
, const char *file
, size_t start_line
)
804 message_ty
*mp
= (message_ty
*) message
;
806 message_comment_filepos (mp
, file
, start_line
);
810 /* Return true if the message is marked obsolete. */
813 po_message_is_obsolete (po_message_t message
)
815 message_ty
*mp
= (message_ty
*) message
;
817 return (mp
->obsolete
? 1 : 0);
821 /* Change the obsolete mark of a message. */
824 po_message_set_obsolete (po_message_t message
, int obsolete
)
826 message_ty
*mp
= (message_ty
*) message
;
828 mp
->obsolete
= obsolete
;
832 /* Return true if the message is marked fuzzy. */
835 po_message_is_fuzzy (po_message_t message
)
837 message_ty
*mp
= (message_ty
*) message
;
839 return (mp
->is_fuzzy
? 1 : 0);
843 /* Change the fuzzy mark of a message. */
846 po_message_set_fuzzy (po_message_t message
, int fuzzy
)
848 message_ty
*mp
= (message_ty
*) message
;
850 mp
->is_fuzzy
= fuzzy
;
854 /* Return true if the message is marked as being a format string of the given
855 type (e.g. "c-format"). */
858 po_message_is_format (po_message_t message
, const char *format_type
)
860 message_ty
*mp
= (message_ty
*) message
;
861 size_t len
= strlen (format_type
);
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);
874 /* Change the format string mark for a given type of a message. */
877 po_message_set_format (po_message_t message
, const char *format_type
, /*bool*/int value
)
879 message_ty
*mp
= (message_ty
*) message
;
880 size_t len
= strlen (format_type
);
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
);
892 /* An error logger based on the po_error function pointer. */
894 po_error_logger (const char *format
, ...)
899 va_start (args
, format
);
900 if (vasprintf (&error_message
, format
, args
) < 0)
901 error (EXIT_FAILURE
, 0, _("memory exhausted"));
903 po_error (0, 0, "%s", error_message
);
904 free (error_message
);
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
911 po_message_check_format (po_message_t message
, po_error_handler_t handler
)
913 message_ty
*mp
= (message_ty
*) message
;
915 /* Establish error handler for po_error_logger(). */
916 po_error
= handler
->error
;
918 check_msgid_msgstr_format (mp
->msgid
, mp
->msgid_plural
,
919 mp
->msgstr
, mp
->msgstr_len
,
920 mp
->is_format
, po_error_logger
);
922 /* Restore error handler. */
927 /* Return the file name. */
930 po_filepos_file (po_filepos_t filepos
)
932 lex_pos_ty
*pp
= (lex_pos_ty
*) filepos
;
934 return pp
->file_name
;
938 /* Return the line number where the string starts, or (size_t)(-1) if no line
939 number is available. */
942 po_filepos_start_line (po_filepos_t filepos
)
944 lex_pos_ty
*pp
= (lex_pos_ty
*) filepos
;
946 return pp
->line_number
;