about summary refs log tree commit diff
path: root/catgets/gencat.c
diff options
context:
space:
mode:
Diffstat (limited to 'catgets/gencat.c')
-rw-r--r--catgets/gencat.c282
1 files changed, 246 insertions, 36 deletions
diff --git a/catgets/gencat.c b/catgets/gencat.c
index de6bdf65d8..0200ca44ef 100644
--- a/catgets/gencat.c
+++ b/catgets/gencat.c
@@ -22,11 +22,14 @@
 #endif
 
 #include <argp.h>
+#include <assert.h>
 #include <ctype.h>
 #include <endian.h>
 #include <errno.h>
 #include <error.h>
 #include <fcntl.h>
+#include <iconv.h>
+#include <langinfo.h>
 #include <locale.h>
 #include <libintl.h>
 #include <limits.h>
@@ -37,6 +40,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <wchar.h>
 
 #include "version.h"
 
@@ -79,7 +83,7 @@ struct catalog
   struct set_list *all_sets;
   struct set_list *current_set;
   size_t total_messages;
-  char quote_char;
+  wint_t quote_char;
   int last_set;
 
   struct obstack mem_pool;
@@ -137,6 +141,8 @@ static struct argp argp =
 /* Wrapper functions with error checking for standard functions.  */
 extern void *xmalloc (size_t n);
 extern void *xcalloc (size_t n, size_t s);
+extern void *xrealloc (void *o, size_t n);
+extern char *xstrdup (const char *);
 
 /* Prototypes for local functions.  */
 static void error_print (void);
@@ -145,9 +151,11 @@ static struct catalog *read_input_file (struct catalog *current,
 static void write_out (struct catalog *result, const char *output_name,
 		       const char *header_name);
 static struct set_list *find_set (struct catalog *current, int number);
-static void normalize_line (const char *fname, size_t line, char *string,
-			    char quote_char);
+static void normalize_line (const char *fname, size_t line, iconv_t cd,
+			    wchar_t *string, wchar_t quote_char);
 static void read_old (struct catalog *catalog, const char *file_name);
+static int open_conversion (const char *codesetp, iconv_t *cd_towcp,
+			    iconv_t *cd_tombp);
 
 
 int
@@ -260,6 +268,11 @@ read_input_file (struct catalog *current, const char *fname)
   char *buf;
   size_t len;
   size_t line_number;
+  wchar_t *wbuf;
+  size_t wbufsize;
+  iconv_t cd_towc = (iconv_t) -1;
+  iconv_t cd_tomb = (iconv_t) -1;
+  char *codeset = NULL;
 
   if (strcmp (fname, "-") == 0 || strcmp (fname, "/dev/stdin") == 0)
     {
@@ -289,6 +302,10 @@ read_input_file (struct catalog *current, const char *fname)
   buf = NULL;
   len = 0;
   line_number = 0;
+
+  wbufsize = 1024;
+  wbuf = (wchar_t *) xmalloc (wbufsize);
+
   while (!feof (fp))
     {
       int continued;
@@ -328,7 +345,29 @@ read_input_file (struct catalog *current, const char *fname)
       if (this_line[0] == '$')
 	{
 	  if (isblank (this_line[1]))
-	    /* This is a comment line.  Do nothing.  */;
+	    {
+	      int cnt = 1;
+	      while (isblank (this_line[cnt]))
+		++cnt;
+	      if (strncmp (&this_line[cnt], "codeset=", 8) != 0)
+		/* This is a comment line. Do nothing.  */;
+	      else if (codeset != NULL)
+		/* Ignore multiple codeset. */;
+	      else
+		{
+		  int start = cnt + 8;
+		  cnt = start;
+		  while (this_line[cnt] != '\0' && !isspace (this_line[cnt]))
+		    ++cnt;
+		  if (cnt != start)
+		    {
+		      int len = cnt - start;
+		      codeset = xmalloc (len + 1);
+		      *((char *) mempcpy (codeset, &this_line[start], len))
+			= '\0';
+		    }
+		}
+	    }
 	  else if (strncmp (&this_line[1], "set", 3) == 0)
 	    {
 	      int cnt = sizeof ("set");
@@ -470,12 +509,44 @@ this is the first definition"));
 	    }
 	  else if (strncmp (&this_line[1], "quote", 5) == 0)
 	    {
-	      int cnt = sizeof ("quote");
+	      char buf[2];
+	      char *bufptr;
+	      size_t buflen;
+	      char *wbufptr;
+	      size_t wbuflen;
+	      int cnt;
+
+	      cnt = sizeof ("quote");
 	      while (isspace (this_line[cnt]))
 		++cnt;
+
+	      /* We need the conversion.  */
+	      if (cd_towc == (iconv_t) -1
+		  && open_conversion (codeset, &cd_towc, &cd_tomb) != 0)
+		/* Something is wrong.  */
+		goto out;
+
 	      /* Yes, the quote char can be '\0'; this means no quote
-		 char.  */
-	      current->quote_char = this_line[cnt];
+		 char.  The function using the information works on
+		 wide characters so we have to convert it here.  */
+	      buf[0] = this_line[cnt];
+	      buf[1] = '\0';
+	      bufptr = buf;
+	      buflen = 2;
+
+	      wbufptr = (char *) wbuf;
+	      wbuflen = wbufsize;
+
+	      /* Flush the state.  */
+	      iconv (cd_towc, NULL, NULL, NULL, NULL);
+
+	      iconv (cd_towc, &bufptr, &buflen, &wbufptr, &wbuflen);
+	      if (buflen != 0 || (wchar_t *) wbufptr != &wbuf[2])
+		error_at_line (0, 0, fname, start_line,
+			       gettext ("invalid quote character"));
+	      else
+		/* Use the converted wide character.  */
+		current->quote_char = wbuf[0];
 	    }
 	  else
 	    {
@@ -568,15 +639,92 @@ duplicated message identifier"));
 
 	  if (message_number != 0)
 	    {
+	      char *inbuf;
+	      size_t inlen;
+	      char *outbuf;
+	      size_t outlen;
 	      struct message_list *newp;
+	      size_t this_line_len = strlen (this_line) + 1;
+
+	      /* We need the conversion.  */
+	      if (cd_towc == (iconv_t) -1
+		  && open_conversion (codeset, &cd_towc, &cd_tomb) != 0)
+		/* Something is wrong.  */
+		goto out;
+
+	      /* Convert to a wide character string.  We have to
+		 interpret escape sequences which will be impossible
+		 without doing the conversion if the codeset of the
+		 message is stateful.  */
+	      while (1)
+		{
+		  inbuf = this_line;
+		  inlen = this_line_len;
+		  outbuf = (char *) wbuf;
+		  outlen = wbufsize;
+
+		  /* Flush the state.  */
+		  iconv (cd_towc, NULL, NULL, NULL, NULL);
+
+		  iconv (cd_towc, &inbuf, &inlen, &outbuf, &outlen);
+		  if (inlen == 0)
+		    {
+		      /* The string is converted.  */
+		      assert (outlen < wbufsize);
+		      assert (wbuf[(wbufsize - outlen) / sizeof (wchar_t) - 1]
+			      == L'\0');
+		      break;
+		    }
+
+		  if (outlen != 0)
+		    {
+		      /* Something is wrong with this string, we ignore it.  */
+		      error_at_line (0, 0, fname, start_line, gettext ("\
+invalid character: message ignored"));
+		      goto ignore;
+		    }
+
+		  /* The output buffer is too small.  */
+		  wbufsize *= 2;
+		  wbuf = (wchar_t *) xrealloc (wbuf, wbufsize);
+		}
 
 	      used = 1;	/* Yes, we use the line.  */
 
 	      /* Strip quote characters, change escape sequences into
 		 correct characters etc.  */
-	      normalize_line (fname, start_line, this_line,
+	      normalize_line (fname, start_line, cd_towc, wbuf,
 			      current->quote_char);
 
+	      /* Now the string is free of escape sequences.  Convert it
+		 back into a multibyte character string.  First free the
+		 memory allocated for the original string.  */
+	      obstack_free (&current->mem_pool, this_line);
+
+	      /* Now fill in the new string.  It should never happen that
+		 the replaced string is longer than the original.  */
+	      inbuf = (char *) wbuf;
+	      inlen = (wcslen (wbuf) + 1) * sizeof (wchar_t);
+
+	      outlen = obstack_room (&current->mem_pool);
+	      start_line = (char *) obstack_alloc (&current->mem_pool, outlen);
+	      outbuf = start_line;
+
+	      /* Flush the state.  */
+	      iconv (cd_tomb, NULL, NULL, NULL, NULL);
+
+	      iconv (cd_tomb, &inbuf, &inlen, &outbuf, &outlen);
+	      if (inlen != 0)
+		{
+		  error_at_line (0, 0, fname, start_line,
+				 gettext ("invalid line"));
+		  goto ignore;
+		}
+	      assert (outbuf[-1] == '\0');
+
+	      /* Free the memory in the obstack we don't use.  */
+	      obstack_free (&current->mem_pool, outbuf);
+
 	      newp = (struct message_list *) xmalloc (sizeof (*newp));
 	      newp->number = message_number;
 	      newp->message = this_line;
@@ -625,11 +773,20 @@ duplicated message identifier"));
 			   gettext ("malformed line ignored"));
 	}
 
+    ignore:
       /* We can save the memory for the line if it was not used.  */
       if (!used)
 	obstack_free (&current->mem_pool, this_line);
     }
 
+  /* Close the conversion modules.  */
+  iconv_close (cd_towc);
+  iconv_close (cd_tomb);
+  free (codeset);
+
+ out:
+  free (wbuf);
+
   if (fp != stdin)
     fclose (fp);
   return current;
@@ -895,13 +1052,14 @@ find_set (struct catalog *current, int number)
 /* Normalize given string *in*place* by processing escape sequences
    and quote characters.  */
 static void
-normalize_line (const char *fname, size_t line, char *string, char quote_char)
+normalize_line (const char *fname, size_t line, iconv_t cd, wchar_t *string,
+		wchar_t quote_char)
 {
   int is_quoted;
-  char *rp = string;
-  char *wp = string;
+  wchar_t *rp = string;
+  wchar_t *wp = string;
 
-  if (quote_char != '\0' && *rp == quote_char)
+  if (quote_char != L'\0' && *rp == quote_char)
     {
       is_quoted = 1;
       ++rp;
@@ -909,58 +1067,83 @@ normalize_line (const char *fname, size_t line, char *string, char quote_char)
   else
     is_quoted = 0;
 
-  while (*rp != '\0')
+  while (*rp != L'\0')
     if (*rp == quote_char)
       /* We simply end the string when we find the first time an
 	 not-escaped quote character.  */
 	break;
-    else if (*rp == '\\')
+    else if (*rp == L'\\')
       {
 	++rp;
-	if (quote_char != '\0' && *rp == quote_char)
+	if (quote_char != L'\0' && *rp == quote_char)
 	  /* This is an extension to XPG.  */
 	  *wp++ = *rp++;
 	else
 	  /* Recognize escape sequences.  */
 	  switch (*rp)
 	    {
-	    case 'n':
-	      *wp++ = '\n';
+	    case L'n':
+	      *wp++ = L'\n';
 	      ++rp;
 	      break;
-	    case 't':
-	      *wp++ = '\t';
+	    case L't':
+	      *wp++ = L'\t';
 	      ++rp;
 	      break;
-	    case 'v':
-	      *wp++ = '\v';
+	    case L'v':
+	      *wp++ = L'\v';
 	      ++rp;
 	      break;
-	    case 'b':
-	      *wp++ = '\b';
+	    case L'b':
+	      *wp++ = L'\b';
 	      ++rp;
 	      break;
-	    case 'r':
-	      *wp++ = '\r';
+	    case L'r':
+	      *wp++ = L'\r';
 	      ++rp;
 	      break;
-	    case 'f':
-	      *wp++ = '\f';
+	    case L'f':
+	      *wp++ = L'\f';
 	      ++rp;
 	      break;
-	    case '\\':
-	      *wp++ = '\\';
+	    case L'\\':
+	      *wp++ = L'\\';
 	      ++rp;
 	      break;
-	    case '0' ... '7':
+	    case L'0' ... L'7':
 	      {
-		int number = *rp++ - '0';
-		while (number <= (255 / 8) && *rp >= '0' && *rp <= '7')
+		int number;
+		char cbuf[2];
+		char *cbufptr;
+		size_t cbufin;
+		wchar_t wcbuf[2];
+		char *wcbufptr;
+		size_t wcbufin;
+
+		number = *rp++ - L'0';
+		while (number <= (255 / 8) && *rp >= L'0' && *rp <= L'7')
 		  {
 		    number *= 8;
-		    number += *rp++ - '0';
+		    number += *rp++ - L'0';
 		  }
-		*wp++ = (char) number;
+
+		cbuf[0] = (char) number;
+		cbuf[1] = '\0';
+		cbufptr = cbuf;
+		cbufin = 2;
+
+		wcbufptr = (char *) wcbuf;
+		wcbufin = sizeof (wcbuf);
+
+		/* Flush the state.  */
+		iconv (cd, NULL, NULL, NULL, NULL);
+
+		iconv (cd, &cbufptr, &cbufin, &wcbufptr, &wcbufin);
+		if (cbufptr != &cbuf[2] || (wchar_t *) wcbufptr != &wcbuf[2])
+		  error_at_line (0, 0, fname, line,
+				 gettext ("invalid escape sequence"));
+		else
+		  *wp++ = wcbuf[0];
 	      }
 	      break;
 	    default:
@@ -974,10 +1157,10 @@ normalize_line (const char *fname, size_t line, char *string, char quote_char)
   /* If we saw a quote character at the beginning we expect another
      one at the end.  */
   if (is_quoted && *rp != quote_char)
-    error (0, 0, fname, line, gettext ("unterminated message"));
+    error_at_line (0, 0, fname, line, gettext ("unterminated message"));
 
   /* Terminate string.  */
-  *wp = '\0';
+  *wp = L'\0';
   return;
 }
 
@@ -1069,3 +1252,30 @@ read_old (struct catalog *catalog, const char *file_name)
 	}
     }
 }
+
+
+static int
+open_conversion (const char *codeset, iconv_t *cd_towcp, iconv_t *cd_tombp)
+{
+  /* If the input file does not specify the codeset use the locale's.  */
+  if (codeset == NULL)
+    {
+      setlocale (LC_ALL, "");
+      codeset = nl_langinfo (CODESET);
+      setlocale (LC_ALL, "C");
+    }
+
+  /* Get the conversion modules.  */
+  *cd_towcp = iconv_open ("WCHAR_T", codeset);
+  *cd_tombp = iconv_open (codeset, "WCHAR_T");
+  if (*cd_towcp == (iconv_t) -1 || *cd_tombp == (iconv_t) -1)
+    {
+      error (0, 0, gettext ("conversion modules not available"));
+      if (*cd_towcp != (iconv_t) -1)
+	iconv_close (*cd_towcp);
+
+      return 1;
+    }
+
+  return 0;
+}