Home | History | Annotate | Line # | Download | only in src
      1 /* Reading PO files.
      2    Copyright (C) 1995-1998, 2000-2003, 2005-2006 Free Software Foundation, Inc.
      3    This file was written by Peter Miller <millerp (at) canb.auug.org.au>
      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 "read-catalog.h"
     25 
     26 #include <stdbool.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 
     30 #include "open-catalog.h"
     31 #include "po-charset.h"
     32 #include "po-xerror.h"
     33 #include "xalloc.h"
     34 #include "gettext.h"
     35 
     36 #define _(str) gettext (str)
     37 
     38 
     39 /* ========================================================================= */
     40 /* Inline functions to invoke the methods.  */
     41 
     42 static inline void
     43 call_set_domain (struct default_catalog_reader_ty *this, char *name)
     44 {
     45   default_catalog_reader_class_ty *methods =
     46     (default_catalog_reader_class_ty *) this->methods;
     47 
     48   if (methods->set_domain)
     49     methods->set_domain (this, name);
     50 }
     51 
     52 static inline void
     53 call_add_message (struct default_catalog_reader_ty *this,
     54 		  char *msgctxt,
     55 		  char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
     56 		  char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
     57 		  char *prev_msgctxt, char *prev_msgid, char *prev_msgid_plural,
     58 		  bool force_fuzzy, bool obsolete)
     59 {
     60   default_catalog_reader_class_ty *methods =
     61     (default_catalog_reader_class_ty *) this->methods;
     62 
     63   if (methods->add_message)
     64     methods->add_message (this, msgctxt,
     65 			  msgid, msgid_pos, msgid_plural,
     66 			  msgstr, msgstr_len, msgstr_pos,
     67 			  prev_msgctxt, prev_msgid, prev_msgid_plural,
     68 			  force_fuzzy, obsolete);
     69 }
     70 
     71 static inline void
     72 call_frob_new_message (struct default_catalog_reader_ty *this, message_ty *mp,
     73 		       const lex_pos_ty *msgid_pos,
     74 		       const lex_pos_ty *msgstr_pos)
     75 {
     76   default_catalog_reader_class_ty *methods =
     77     (default_catalog_reader_class_ty *) this->methods;
     78 
     79   if (methods->frob_new_message)
     80     methods->frob_new_message (this, mp, msgid_pos, msgstr_pos);
     81 }
     82 
     83 
     84 /* ========================================================================= */
     85 /* Implementation of default_catalog_reader_ty's methods.  */
     86 
     87 
     88 /* Implementation of methods declared in the superclass.  */
     89 
     90 
     91 /* Prepare for first message.  */
     92 void
     93 default_constructor (abstract_catalog_reader_ty *that)
     94 {
     95   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
     96   size_t i;
     97 
     98   this->domain = MESSAGE_DOMAIN_DEFAULT;
     99   this->comment = NULL;
    100   this->comment_dot = NULL;
    101   this->filepos_count = 0;
    102   this->filepos = NULL;
    103   this->is_fuzzy = false;
    104   for (i = 0; i < NFORMATS; i++)
    105     this->is_format[i] = undecided;
    106   this->do_wrap = undecided;
    107 }
    108 
    109 
    110 void
    111 default_destructor (abstract_catalog_reader_ty *that)
    112 {
    113   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
    114 
    115   /* Do not free this->mdlp and this->mlp.  */
    116   if (this->handle_comments)
    117     {
    118       if (this->comment != NULL)
    119 	string_list_free (this->comment);
    120       if (this->comment_dot != NULL)
    121 	string_list_free (this->comment_dot);
    122     }
    123   if (this->handle_filepos_comments)
    124     {
    125       size_t j;
    126 
    127       for (j = 0; j < this->filepos_count; ++j)
    128 	free (this->filepos[j].file_name);
    129       if (this->filepos != NULL)
    130 	free (this->filepos);
    131     }
    132 }
    133 
    134 
    135 void
    136 default_parse_brief (abstract_catalog_reader_ty *that)
    137 {
    138   /* We need to parse comments, because even if this->handle_comments and
    139      this->handle_filepos_comments are false, we need to know which messages
    140      are fuzzy.  */
    141   po_lex_pass_comments (true);
    142 }
    143 
    144 
    145 void
    146 default_parse_debrief (abstract_catalog_reader_ty *that)
    147 {
    148 }
    149 
    150 
    151 /* Add the accumulated comments to the message.  */
    152 static void
    153 default_copy_comment_state (default_catalog_reader_ty *this, message_ty *mp)
    154 {
    155   size_t j, i;
    156 
    157   if (this->handle_comments)
    158     {
    159       if (this->comment != NULL)
    160 	for (j = 0; j < this->comment->nitems; ++j)
    161 	  message_comment_append (mp, this->comment->item[j]);
    162       if (this->comment_dot != NULL)
    163 	for (j = 0; j < this->comment_dot->nitems; ++j)
    164 	  message_comment_dot_append (mp, this->comment_dot->item[j]);
    165     }
    166   if (this->handle_filepos_comments)
    167     {
    168       for (j = 0; j < this->filepos_count; ++j)
    169 	{
    170 	  lex_pos_ty *pp;
    171 
    172 	  pp = &this->filepos[j];
    173 	  message_comment_filepos (mp, pp->file_name, pp->line_number);
    174 	}
    175     }
    176   mp->is_fuzzy = this->is_fuzzy;
    177   for (i = 0; i < NFORMATS; i++)
    178     mp->is_format[i] = this->is_format[i];
    179   mp->do_wrap = this->do_wrap;
    180 }
    181 
    182 
    183 static void
    184 default_reset_comment_state (default_catalog_reader_ty *this)
    185 {
    186   size_t j, i;
    187 
    188   if (this->handle_comments)
    189     {
    190       if (this->comment != NULL)
    191 	{
    192 	  string_list_free (this->comment);
    193 	  this->comment = NULL;
    194 	}
    195       if (this->comment_dot != NULL)
    196 	{
    197 	  string_list_free (this->comment_dot);
    198 	  this->comment_dot = NULL;
    199 	}
    200     }
    201   if (this->handle_filepos_comments)
    202     {
    203       for (j = 0; j < this->filepos_count; ++j)
    204 	free (this->filepos[j].file_name);
    205       if (this->filepos != NULL)
    206 	free (this->filepos);
    207       this->filepos_count = 0;
    208       this->filepos = NULL;
    209     }
    210   this->is_fuzzy = false;
    211   for (i = 0; i < NFORMATS; i++)
    212     this->is_format[i] = undecided;
    213   this->do_wrap = undecided;
    214 }
    215 
    216 
    217 /* Process 'domain' directive from .po file.  */
    218 void
    219 default_directive_domain (abstract_catalog_reader_ty *that, char *name)
    220 {
    221   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
    222 
    223   call_set_domain (this, name);
    224 
    225   /* If there are accumulated comments, throw them away, they are
    226      probably part of the file header, or about the domain directive,
    227      and will be unrelated to the next message.  */
    228   default_reset_comment_state (this);
    229 }
    230 
    231 
    232 /* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file.  */
    233 void
    234 default_directive_message (abstract_catalog_reader_ty *that,
    235 			   char *msgctxt,
    236 			   char *msgid,
    237 			   lex_pos_ty *msgid_pos,
    238 			   char *msgid_plural,
    239 			   char *msgstr, size_t msgstr_len,
    240 			   lex_pos_ty *msgstr_pos,
    241 			   char *prev_msgctxt,
    242 			   char *prev_msgid, char *prev_msgid_plural,
    243 			   bool force_fuzzy, bool obsolete)
    244 {
    245   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
    246 
    247   call_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
    248 		    msgstr, msgstr_len, msgstr_pos,
    249 		    prev_msgctxt, prev_msgid, prev_msgid_plural,
    250 		    force_fuzzy, obsolete);
    251 
    252   /* Prepare for next message.  */
    253   default_reset_comment_state (this);
    254 }
    255 
    256 
    257 void
    258 default_comment (abstract_catalog_reader_ty *that, const char *s)
    259 {
    260   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
    261 
    262   if (this->handle_comments)
    263     {
    264       if (this->comment == NULL)
    265 	this->comment = string_list_alloc ();
    266       string_list_append (this->comment, s);
    267     }
    268 }
    269 
    270 
    271 void
    272 default_comment_dot (abstract_catalog_reader_ty *that, const char *s)
    273 {
    274   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
    275 
    276   if (this->handle_comments)
    277     {
    278       if (this->comment_dot == NULL)
    279 	this->comment_dot = string_list_alloc ();
    280       string_list_append (this->comment_dot, s);
    281     }
    282 }
    283 
    284 
    285 void
    286 default_comment_filepos (abstract_catalog_reader_ty *that,
    287 			 const char *name, size_t line)
    288 {
    289   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
    290 
    291   if (this->handle_filepos_comments)
    292     {
    293       size_t nbytes;
    294       lex_pos_ty *pp;
    295 
    296       nbytes = (this->filepos_count + 1) * sizeof (this->filepos[0]);
    297       this->filepos = xrealloc (this->filepos, nbytes);
    298       pp = &this->filepos[this->filepos_count++];
    299       pp->file_name = xstrdup (name);
    300       pp->line_number = line;
    301     }
    302 }
    303 
    304 
    305 /* Test for '#, fuzzy' comments and warn.  */
    306 void
    307 default_comment_special (abstract_catalog_reader_ty *that, const char *s)
    308 {
    309   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
    310 
    311   po_parse_comment_special (s, &this->is_fuzzy, this->is_format,
    312 			    &this->do_wrap);
    313 }
    314 
    315 
    316 /* Default implementation of methods not inherited from the superclass.  */
    317 
    318 
    319 void
    320 default_set_domain (default_catalog_reader_ty *this, char *name)
    321 {
    322   if (this->allow_domain_directives)
    323     /* Override current domain name.  Don't free memory.  */
    324     this->domain = name;
    325   else
    326     {
    327       po_gram_error_at_line (&gram_pos,
    328 			     _("this file may not contain domain directives"));
    329 
    330       /* NAME was allocated in po-gram-gen.y but is not used anywhere.  */
    331       free (name);
    332     }
    333 }
    334 
    335 void
    336 default_add_message (default_catalog_reader_ty *this,
    337 		     char *msgctxt,
    338 		     char *msgid,
    339 		     lex_pos_ty *msgid_pos,
    340 		     char *msgid_plural,
    341 		     char *msgstr, size_t msgstr_len,
    342 		     lex_pos_ty *msgstr_pos,
    343 		     char *prev_msgctxt,
    344 		     char *prev_msgid,
    345 		     char *prev_msgid_plural,
    346 		     bool force_fuzzy, bool obsolete)
    347 {
    348   message_ty *mp;
    349 
    350   if (this->mdlp != NULL)
    351     /* Select the appropriate sublist of this->mdlp.  */
    352     this->mlp = msgdomain_list_sublist (this->mdlp, this->domain, true);
    353 
    354   if (this->allow_duplicates && msgid[0] != '\0')
    355     /* Doesn't matter if this message ID has been seen before.  */
    356     mp = NULL;
    357   else
    358     /* See if this message ID has been seen before.  */
    359     mp = message_list_search (this->mlp, msgctxt, msgid);
    360 
    361   if (mp)
    362     {
    363       if (!(this->allow_duplicates_if_same_msgstr
    364 	    && msgstr_len == mp->msgstr_len
    365 	    && memcmp (msgstr, mp->msgstr, msgstr_len) == 0))
    366 	{
    367 	  /* We give a fatal error about this, regardless whether the
    368 	     translations are equal or different.  This is for consistency
    369 	     with msgmerge, msgcat and others.  The user can use the
    370 	     msguniq program to get rid of duplicates.  */
    371 	  po_xerror2 (PO_SEVERITY_ERROR,
    372 		      NULL, msgid_pos->file_name, msgid_pos->line_number,
    373 		      (size_t)(-1), false, _("duplicate message definition"),
    374 		      mp, NULL, 0, 0, false,
    375 		      _("this is the location of the first definition"));
    376 	}
    377       /* We don't need the just constructed entries' parameter string
    378 	 (allocated in po-gram-gen.y).  */
    379       free (msgid);
    380       if (msgid_plural != NULL)
    381 	free (msgid_plural);
    382       free (msgstr);
    383       if (msgctxt != NULL)
    384 	free (msgctxt);
    385       if (prev_msgctxt != NULL)
    386 	free (prev_msgctxt);
    387       if (prev_msgid != NULL)
    388 	free (prev_msgid);
    389       if (prev_msgid_plural != NULL)
    390 	free (prev_msgid_plural);
    391 
    392       /* Add the accumulated comments to the message.  */
    393       default_copy_comment_state (this, mp);
    394     }
    395   else
    396     {
    397       /* Construct message to add to the list.
    398 	 Obsolete message go into the list at least for duplicate checking.
    399 	 It's the caller's responsibility to ignore obsolete messages when
    400 	 appropriate.  */
    401       mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len,
    402 			  msgstr_pos);
    403       mp->prev_msgctxt = prev_msgctxt;
    404       mp->prev_msgid = prev_msgid;
    405       mp->prev_msgid_plural = prev_msgid_plural;
    406       mp->obsolete = obsolete;
    407       default_copy_comment_state (this, mp);
    408       if (force_fuzzy)
    409 	mp->is_fuzzy = true;
    410 
    411       call_frob_new_message (this, mp, msgid_pos, msgstr_pos);
    412 
    413       message_list_append (this->mlp, mp);
    414     }
    415 }
    416 
    417 
    418 /* So that the one parser can be used for multiple programs, and also
    419    use good data hiding and encapsulation practices, an object
    420    oriented approach has been taken.  An object instance is allocated,
    421    and all actions resulting from the parse will be through
    422    invocations of method functions of that object.  */
    423 
    424 static default_catalog_reader_class_ty default_methods =
    425 {
    426   {
    427     sizeof (default_catalog_reader_ty),
    428     default_constructor,
    429     default_destructor,
    430     default_parse_brief,
    431     default_parse_debrief,
    432     default_directive_domain,
    433     default_directive_message,
    434     default_comment,
    435     default_comment_dot,
    436     default_comment_filepos,
    437     default_comment_special
    438   },
    439   default_set_domain, /* set_domain */
    440   default_add_message, /* add_message */
    441   NULL /* frob_new_message */
    442 };
    443 
    444 
    445 default_catalog_reader_ty *
    446 default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table)
    447 {
    448   return
    449     (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super);
    450 }
    451 
    452 
    453 /* ========================================================================= */
    454 /* Exported functions.  */
    455 
    456 
    457 /* If nonzero, remember comments for file name and line number for each
    458    msgid, if present in the reference input.  Defaults to true.  */
    459 int line_comment = 1;
    460 
    461 /* If false, duplicate msgids in the same domain and file generate an error.
    462    If true, such msgids are allowed; the caller should treat them
    463    appropriately.  Defaults to false.  */
    464 bool allow_duplicates = false;
    465 
    466 
    467 msgdomain_list_ty *
    468 read_catalog_stream (FILE *fp, const char *real_filename,
    469 		     const char *logical_filename,
    470 		     catalog_input_format_ty input_syntax)
    471 {
    472   default_catalog_reader_ty *pop;
    473   msgdomain_list_ty *mdlp;
    474 
    475   pop = default_catalog_reader_alloc (&default_methods);
    476   pop->handle_comments = true;
    477   pop->handle_filepos_comments = (line_comment != 0);
    478   pop->allow_domain_directives = true;
    479   pop->allow_duplicates = allow_duplicates;
    480   pop->allow_duplicates_if_same_msgstr = false;
    481   pop->mdlp = msgdomain_list_alloc (!pop->allow_duplicates);
    482   pop->mlp = msgdomain_list_sublist (pop->mdlp, pop->domain, true);
    483   if (input_syntax->produces_utf8)
    484     /* We know a priori that input_syntax->parse convert strings to UTF-8.  */
    485     pop->mdlp->encoding = po_charset_utf8;
    486   po_lex_pass_obsolete_entries (true);
    487   catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename,
    488 			logical_filename, input_syntax);
    489   mdlp = pop->mdlp;
    490   catalog_reader_free ((abstract_catalog_reader_ty *) pop);
    491   return mdlp;
    492 }
    493 
    494 
    495 msgdomain_list_ty *
    496 read_catalog_file (const char *filename, catalog_input_format_ty input_syntax)
    497 {
    498   char *real_filename;
    499   FILE *fp = open_catalog_file (filename, &real_filename, true);
    500   msgdomain_list_ty *result;
    501 
    502   result = read_catalog_stream (fp, real_filename, filename, input_syntax);
    503 
    504   if (fp != stdin)
    505     fclose (fp);
    506 
    507   return result;
    508 }
    509