Home | History | Annotate | Line # | Download | only in src
      1 /* Reading PO files, abstract class.
      2    Copyright (C) 1995-1996, 1998, 2000-2006 Free Software Foundation, Inc.
      3 
      4    This file was written by Peter Miller <millerp (at) canb.auug.org.au>
      5 
      6    This program is free software; you can redistribute it and/or modify
      7    it under the terms of the GNU General Public License as published by
      8    the Free Software Foundation; either version 2, or (at your option)
      9    any later version.
     10 
     11    This program is distributed in the hope that it will be useful,
     12    but WITHOUT ANY WARRANTY; without even the implied warranty of
     13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14    GNU General Public License for more details.
     15 
     16    You should have received a copy of the GNU General Public License
     17    along with this program; if not, write to the Free Software Foundation,
     18    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
     19 
     20 
     21 #ifdef HAVE_CONFIG_H
     22 # include "config.h"
     23 #endif
     24 
     25 /* Specification.  */
     26 #include "read-catalog-abstract.h"
     27 
     28 #include <stdlib.h>
     29 #include <string.h>
     30 
     31 #include "xalloc.h"
     32 #include "xvasprintf.h"
     33 #include "po-xerror.h"
     34 #include "gettext.h"
     35 
     36 /* Local variables.  */
     37 static abstract_catalog_reader_ty *callback_arg;
     38 
     39 
     40 /* ========================================================================= */
     41 /* Allocating and freeing instances of abstract_catalog_reader_ty.  */
     42 
     43 
     44 abstract_catalog_reader_ty *
     45 catalog_reader_alloc (abstract_catalog_reader_class_ty *method_table)
     46 {
     47   abstract_catalog_reader_ty *pop;
     48 
     49   pop = (abstract_catalog_reader_ty *) xmalloc (method_table->size);
     50   pop->methods = method_table;
     51   if (method_table->constructor)
     52     method_table->constructor (pop);
     53   return pop;
     54 }
     55 
     56 
     57 void
     58 catalog_reader_free (abstract_catalog_reader_ty *pop)
     59 {
     60   if (pop->methods->destructor)
     61     pop->methods->destructor (pop);
     62   free (pop);
     63 }
     64 
     65 
     66 /* ========================================================================= */
     67 /* Inline functions to invoke the methods.  */
     68 
     69 
     70 static inline void
     71 call_parse_brief (abstract_catalog_reader_ty *pop)
     72 {
     73   if (pop->methods->parse_brief)
     74     pop->methods->parse_brief (pop);
     75 }
     76 
     77 static inline void
     78 call_parse_debrief (abstract_catalog_reader_ty *pop)
     79 {
     80   if (pop->methods->parse_debrief)
     81     pop->methods->parse_debrief (pop);
     82 }
     83 
     84 static inline void
     85 call_directive_domain (abstract_catalog_reader_ty *pop, char *name)
     86 {
     87   if (pop->methods->directive_domain)
     88     pop->methods->directive_domain (pop, name);
     89 }
     90 
     91 static inline void
     92 call_directive_message (abstract_catalog_reader_ty *pop,
     93 			char *msgctxt,
     94 			char *msgid,
     95 			lex_pos_ty *msgid_pos,
     96 			char *msgid_plural,
     97 			char *msgstr, size_t msgstr_len,
     98 			lex_pos_ty *msgstr_pos,
     99 			char *prev_msgctxt,
    100 			char *prev_msgid,
    101 			char *prev_msgid_plural,
    102 			bool force_fuzzy, bool obsolete)
    103 {
    104   if (pop->methods->directive_message)
    105     pop->methods->directive_message (pop, msgctxt,
    106 				     msgid, msgid_pos, msgid_plural,
    107 				     msgstr, msgstr_len, msgstr_pos,
    108 				     prev_msgctxt,
    109 				     prev_msgid,
    110 				     prev_msgid_plural,
    111 				     force_fuzzy, obsolete);
    112 }
    113 
    114 static inline void
    115 call_comment (abstract_catalog_reader_ty *pop, const char *s)
    116 {
    117   if (pop->methods->comment != NULL)
    118     pop->methods->comment (pop, s);
    119 }
    120 
    121 static inline void
    122 call_comment_dot (abstract_catalog_reader_ty *pop, const char *s)
    123 {
    124   if (pop->methods->comment_dot != NULL)
    125     pop->methods->comment_dot (pop, s);
    126 }
    127 
    128 static inline void
    129 call_comment_filepos (abstract_catalog_reader_ty *pop, const char *name,
    130 		      size_t line)
    131 {
    132   if (pop->methods->comment_filepos)
    133     pop->methods->comment_filepos (pop, name, line);
    134 }
    135 
    136 static inline void
    137 call_comment_special (abstract_catalog_reader_ty *pop, const char *s)
    138 {
    139   if (pop->methods->comment_special != NULL)
    140     pop->methods->comment_special (pop, s);
    141 }
    142 
    143 
    144 /* ========================================================================= */
    145 /* Exported functions.  */
    146 
    147 
    148 static inline void
    149 parse_start (abstract_catalog_reader_ty *pop)
    150 {
    151   /* The parse will call the po_callback_... functions (see below)
    152      when the various directive are recognised.  The callback_arg
    153      variable is used to tell these functions which instance is to
    154      have the relevant method invoked.  */
    155   callback_arg = pop;
    156 
    157   call_parse_brief (pop);
    158 }
    159 
    160 static inline void
    161 parse_end (abstract_catalog_reader_ty *pop)
    162 {
    163   call_parse_debrief (pop);
    164   callback_arg = NULL;
    165 }
    166 
    167 
    168 void
    169 catalog_reader_parse (abstract_catalog_reader_ty *pop, FILE *fp,
    170 		      const char *real_filename, const char *logical_filename,
    171 		      catalog_input_format_ty input_syntax)
    172 {
    173   /* Parse the stream's content.  */
    174   parse_start (pop);
    175   input_syntax->parse (pop, fp, real_filename, logical_filename);
    176   parse_end (pop);
    177 
    178   if (error_message_count > 0)
    179     po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
    180 	       /*real_filename*/ NULL, (size_t)(-1), (size_t)(-1), false,
    181 	       xasprintf (ngettext ("found %d fatal error",
    182 				    "found %d fatal errors",
    183 				    error_message_count),
    184 			  error_message_count));
    185   error_message_count = 0;
    186 }
    187 
    188 
    189 /* ========================================================================= */
    190 /* Callbacks used by po-gram.y or po-lex.c, indirectly from
    191    catalog_reader_parse.  */
    192 
    193 
    194 /* This function is called by po_gram_lex() whenever a domain directive
    195    has been seen.  */
    196 void
    197 po_callback_domain (char *name)
    198 {
    199   /* assert(callback_arg); */
    200   call_directive_domain (callback_arg, name);
    201 }
    202 
    203 
    204 /* This function is called by po_gram_lex() whenever a message has been
    205    seen.  */
    206 void
    207 po_callback_message (char *msgctxt,
    208 		     char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
    209 		     char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
    210 		     char *prev_msgctxt,
    211 		     char *prev_msgid,
    212 		     char *prev_msgid_plural,
    213 		     bool force_fuzzy, bool obsolete)
    214 {
    215   /* assert(callback_arg); */
    216   call_directive_message (callback_arg, msgctxt,
    217 			  msgid, msgid_pos, msgid_plural,
    218 			  msgstr, msgstr_len, msgstr_pos,
    219 			  prev_msgctxt, prev_msgid, prev_msgid_plural,
    220 			  force_fuzzy, obsolete);
    221 }
    222 
    223 
    224 void
    225 po_callback_comment (const char *s)
    226 {
    227   /* assert(callback_arg); */
    228   call_comment (callback_arg, s);
    229 }
    230 
    231 
    232 void
    233 po_callback_comment_dot (const char *s)
    234 {
    235   /* assert(callback_arg); */
    236   call_comment_dot (callback_arg, s);
    237 }
    238 
    239 
    240 /* This function is called by po_parse_comment_filepos(), once for each
    241    filename.  */
    242 void
    243 po_callback_comment_filepos (const char *name, size_t line)
    244 {
    245   /* assert(callback_arg); */
    246   call_comment_filepos (callback_arg, name, line);
    247 }
    248 
    249 
    250 void
    251 po_callback_comment_special (const char *s)
    252 {
    253   /* assert(callback_arg); */
    254   call_comment_special (callback_arg, s);
    255 }
    256 
    257 
    258 /* Parse a special comment and put the result in *fuzzyp, formatp, *wrapp.  */
    259 void
    260 po_parse_comment_special (const char *s,
    261 			  bool *fuzzyp, enum is_format formatp[NFORMATS],
    262 			  enum is_wrap *wrapp)
    263 {
    264   size_t i;
    265 
    266   *fuzzyp = false;
    267   for (i = 0; i < NFORMATS; i++)
    268     formatp[i] = undecided;
    269   *wrapp = undecided;
    270 
    271   while (*s != '\0')
    272     {
    273       const char *t;
    274 
    275       /* Skip whitespace.  */
    276       while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) != NULL)
    277 	s++;
    278 
    279       /* Collect a token.  */
    280       t = s;
    281       while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) == NULL)
    282 	s++;
    283       if (s != t)
    284 	{
    285 	  size_t len = s - t;
    286 
    287 	  /* Accept fuzzy flag.  */
    288 	  if (len == 5 && memcmp (t, "fuzzy", 5) == 0)
    289 	    {
    290 	      *fuzzyp = true;
    291 	      continue;
    292 	    }
    293 
    294 	  /* Accept format description.  */
    295 	  if (len >= 7 && memcmp (t + len - 7, "-format", 7) == 0)
    296 	    {
    297 	      const char *p;
    298 	      size_t n;
    299 	      enum is_format value;
    300 
    301 	      p = t;
    302 	      n = len - 7;
    303 
    304 	      if (n >= 3 && memcmp (p, "no-", 3) == 0)
    305 		{
    306 		  p += 3;
    307 		  n -= 3;
    308 		  value = no;
    309 		}
    310 	      else if (n >= 9 && memcmp (p, "possible-", 9) == 0)
    311 		{
    312 		  p += 9;
    313 		  n -= 9;
    314 		  value = possible;
    315 		}
    316 	      else if (n >= 11 && memcmp (p, "impossible-", 11) == 0)
    317 		{
    318 		  p += 11;
    319 		  n -= 11;
    320 		  value = impossible;
    321 		}
    322 	      else
    323 		value = yes;
    324 
    325 	      for (i = 0; i < NFORMATS; i++)
    326 		if (strlen (format_language[i]) == n
    327 		    && memcmp (format_language[i], p, n) == 0)
    328 		  {
    329 		    formatp[i] = value;
    330 		    break;
    331 		  }
    332 	      if (i < NFORMATS)
    333 		continue;
    334 	    }
    335 
    336 	  /* Accept wrap description.  */
    337 	  if (len == 4 && memcmp (t, "wrap", 4) == 0)
    338 	    {
    339 	      *wrapp = yes;
    340 	      continue;
    341 	    }
    342 	  if (len == 7 && memcmp (t, "no-wrap", 7) == 0)
    343 	    {
    344 	      *wrapp = no;
    345 	      continue;
    346 	    }
    347 
    348 	  /* Unknown special comment marker.  It may have been generated
    349 	     from a future xgettext version.  Ignore it.  */
    350 	}
    351     }
    352 }
    353 
    354 
    355 /* Parse a GNU style file comment.
    356    Syntax: an arbitrary number of
    357              STRING COLON NUMBER
    358            or
    359              STRING
    360    The latter style, without line number, occurs in PO files converted e.g.
    361    from Pascal .rst files or from OpenOffice resource files.
    362    Call po_callback_comment_filepos for each of them.  */
    363 static void
    364 po_parse_comment_filepos (const char *s)
    365 {
    366   while (*s != '\0')
    367     {
    368       while (*s == ' ' || *s == '\t' || *s == '\n')
    369 	s++;
    370       if (*s != '\0')
    371 	{
    372 	  const char *string_start = s;
    373 
    374 	  do
    375 	    s++;
    376 	  while (!(*s == '\0' || *s == ' ' || *s == '\t' || *s == '\n'));
    377 
    378 	  /* See if there is a COLON and NUMBER after the STRING, separated
    379 	     through optional spaces.  */
    380 	  {
    381 	    const char *p = s;
    382 
    383 	    while (*p == ' ' || *p == '\t' || *p == '\n')
    384 	      p++;
    385 
    386 	    if (*p == ':')
    387 	      {
    388 		p++;
    389 
    390 		while (*p == ' ' || *p == '\t' || *p == '\n')
    391 		  p++;
    392 
    393 		if (*p >= '0' && *p <= '9')
    394 		  {
    395 		    /* Accumulate a number.  */
    396 		    size_t n = 0;
    397 
    398 		    do
    399 		      {
    400 			n = n * 10 + (*p - '0');
    401 			p++;
    402 		      }
    403 		    while (*p >= '0' && *p <= '9');
    404 
    405 		    if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n')
    406 		      {
    407 			/* Parsed a GNU style file comment with spaces.  */
    408 			const char *string_end = s;
    409 			size_t string_length = string_end - string_start;
    410 			char *string = (char *) xmalloc (string_length + 1);
    411 
    412 			memcpy (string, string_start, string_length);
    413 			string[string_length] = '\0';
    414 
    415 			po_callback_comment_filepos (string, n);
    416 
    417 			free (string);
    418 
    419 			s = p;
    420 			continue;
    421 		      }
    422 		  }
    423 	      }
    424 	  }
    425 
    426 	  /* See if there is a COLON at the end of STRING and a NUMBER after
    427 	     it, separated through optional spaces.  */
    428 	  if (s[-1] == ':')
    429 	    {
    430 	      const char *p = s;
    431 
    432 	      while (*p == ' ' || *p == '\t' || *p == '\n')
    433 		p++;
    434 
    435 	      if (*p >= '0' && *p <= '9')
    436 		{
    437 		  /* Accumulate a number.  */
    438 		  size_t n = 0;
    439 
    440 		  do
    441 		    {
    442 		      n = n * 10 + (*p - '0');
    443 		      p++;
    444 		    }
    445 		  while (*p >= '0' && *p <= '9');
    446 
    447 		  if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n')
    448 		    {
    449 		      /* Parsed a GNU style file comment with spaces.  */
    450 		      const char *string_end = s - 1;
    451 		      size_t string_length = string_end - string_start;
    452 		      char *string = (char *) xmalloc (string_length + 1);
    453 
    454 		      memcpy (string, string_start, string_length);
    455 		      string[string_length] = '\0';
    456 
    457 		      po_callback_comment_filepos (string, n);
    458 
    459 		      free (string);
    460 
    461 		      s = p;
    462 		      continue;
    463 		    }
    464 		}
    465 	    }
    466 
    467 	  /* See if there is a COLON and NUMBER at the end of the STRING,
    468 	     without separating spaces.  */
    469 	  {
    470 	    const char *p = s;
    471 
    472 	    while (p > string_start)
    473 	      {
    474 		p--;
    475 		if (!(*p >= '0' && *p <= '9'))
    476 		  {
    477 		    p++;
    478 		    break;
    479 		  }
    480 	      }
    481 
    482 	    /* p now points to the beginning of the trailing digits segment
    483 	       at the end of STRING.  */
    484 
    485 	    if (p < s
    486 		&& p > string_start + 1
    487 		&& p[-1] == ':')
    488 	      {
    489 		/* Parsed a GNU style file comment without spaces.  */
    490 		const char *string_end = p - 1;
    491 
    492 		/* Accumulate a number.  */
    493 		{
    494 		  size_t n = 0;
    495 
    496 		  do
    497 		    {
    498 		      n = n * 10 + (*p - '0');
    499 		      p++;
    500 		    }
    501 		  while (p < s);
    502 
    503 		  {
    504 		    size_t string_length = string_end - string_start;
    505 		    char *string = (char *) xmalloc (string_length + 1);
    506 
    507 		    memcpy (string, string_start, string_length);
    508 		    string[string_length] = '\0';
    509 
    510 		    po_callback_comment_filepos (string, n);
    511 
    512 		    free (string);
    513 
    514 		    continue;
    515 		  }
    516 		}
    517 	      }
    518 	  }
    519 
    520 	  /* Parsed a file comment without line number.  */
    521 	  {
    522 	    const char *string_end = s;
    523 	    size_t string_length = string_end - string_start;
    524 	    char *string = (char *) xmalloc (string_length + 1);
    525 
    526 	    memcpy (string, string_start, string_length);
    527 	    string[string_length] = '\0';
    528 
    529 	    po_callback_comment_filepos (string, (size_t)(-1));
    530 
    531 	    free (string);
    532 	  }
    533 	}
    534     }
    535 }
    536 
    537 
    538 /* Parse a SunOS or Solaris style file comment.
    539    Syntax of SunOS style:
    540      FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD COLON NUMBER
    541    Syntax of Solaris style:
    542      FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD NUMBER_KEYWORD COLON NUMBER
    543    where
    544      FILE_KEYWORD ::= "file" | "File"
    545      COLON ::= ":"
    546      COMMA ::= ","
    547      LINE_KEYWORD ::= "line"
    548      NUMBER_KEYWORD ::= "number"
    549      NUMBER ::= [0-9]+
    550    Return true if parsed, false if not a comment of this form. */
    551 static bool
    552 po_parse_comment_solaris_filepos (const char *s)
    553 {
    554   if (s[0] == ' '
    555       && (s[1] == 'F' || s[1] == 'f')
    556       && s[2] == 'i' && s[3] == 'l' && s[4] == 'e'
    557       && s[5] == ':')
    558     {
    559       const char *string_start;
    560       const char *string_end;
    561 
    562       {
    563 	const char *p = s + 6;
    564 
    565 	while (*p == ' ' || *p == '\t')
    566 	  p++;
    567 	string_start = p;
    568       }
    569 
    570       for (string_end = string_start; *string_end != '\0'; string_end++)
    571 	{
    572 	  const char *p = string_end;
    573 
    574 	  while (*p == ' ' || *p == '\t')
    575 	    p++;
    576 
    577 	  if (*p == ',')
    578 	    {
    579 	      p++;
    580 
    581 	      while (*p == ' ' || *p == '\t')
    582 		p++;
    583 
    584 	      if (p[0] == 'l' && p[1] == 'i' && p[2] == 'n' && p[3] == 'e')
    585 		{
    586 		  p += 4;
    587 
    588 		  while (*p == ' ' || *p == '\t')
    589 		    p++;
    590 
    591 		  if (p[0] == 'n' && p[1] == 'u' && p[2] == 'm'
    592 		      && p[3] == 'b' && p[4] == 'e' && p[5] == 'r')
    593 		    {
    594 		      p += 6;
    595 		      while (*p == ' ' || *p == '\t')
    596 			p++;
    597 		    }
    598 
    599 		  if (*p == ':')
    600 		    {
    601 		      p++;
    602 
    603 		      if (*p >= '0' && *p <= '9')
    604 			{
    605 			  /* Accumulate a number.  */
    606 			  size_t n = 0;
    607 
    608 			  do
    609 			    {
    610 			      n = n * 10 + (*p - '0');
    611 			      p++;
    612 			    }
    613 			  while (*p >= '0' && *p <= '9');
    614 
    615 			  while (*p == ' ' || *p == '\t' || *p == '\n')
    616 			    p++;
    617 
    618 			  if (*p == '\0')
    619 			    {
    620 			      /* Parsed a Sun style file comment.  */
    621 			      size_t string_length = string_end - string_start;
    622 			      char *string =
    623 				(char *) xmalloc (string_length + 1);
    624 
    625 			      memcpy (string, string_start, string_length);
    626 			      string[string_length] = '\0';
    627 
    628 			      po_callback_comment_filepos (string, n);
    629 
    630 			      free (string);
    631 			      return true;
    632 			    }
    633 			}
    634 		    }
    635 		}
    636 	    }
    637 	}
    638     }
    639 
    640   return false;
    641 }
    642 
    643 
    644 /* This function is called by po_gram_lex() whenever a comment is
    645    seen.  It analyzes the comment to see what sort it is, and then
    646    dispatches it to the appropriate method: call_comment, call_comment_dot,
    647    call_comment_filepos (via po_parse_comment_filepos), or
    648    call_comment_special.  */
    649 void
    650 po_callback_comment_dispatcher (const char *s)
    651 {
    652   if (*s == '.')
    653     po_callback_comment_dot (s + 1);
    654   else if (*s == ':')
    655     {
    656       /* Parse the file location string.  The appropriate callback will be
    657 	 invoked.  */
    658       po_parse_comment_filepos (s + 1);
    659     }
    660   else if (*s == ',' || *s == '!')
    661     {
    662       /* Get all entries in the special comment line.  */
    663       po_callback_comment_special (s + 1);
    664     }
    665   else
    666     {
    667       /* It looks like a plain vanilla comment, but Solaris-style file
    668 	 position lines do, too.  Try to parse the lot.  If the parse
    669 	 succeeds, the appropriate callback will be invoked.  */
    670       if (po_parse_comment_solaris_filepos (s))
    671 	/* Do nothing, it is a Sun-style file pos line.  */ ;
    672       else
    673 	po_callback_comment (s);
    674     }
    675 }
    676