]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - locale/checkpo.c
License change: Apache License, Version 2.0.
[thirdparty/cups.git] / locale / checkpo.c
index fb2b34256555cdab0c95fb6aab03d35c16a9354e..f46f4cef0f9bb3a01d9cdbcb75d13241dd3b88e1 100644 (file)
@@ -1,38 +1,22 @@
 /*
- * "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $"
- *
  * Verify that translations in the .po file have the same number and type of
  * printf-style format strings.
  *
- * Copyright 2007-2009 by Apple Inc.
+ * Copyright 2007-2017 by Apple Inc.
  * Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
- * These coded instructions, statements, and computer programs are the
- * property of Apple Inc. and are protected by Federal copyright
- * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- * which should have been included with this file.  If this file is
- * file is missing or damaged, see the license at "http://www.cups.org/".
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  *
  * Usage:
  *
- *   checkpo filename.po [... filenameN.po]
+ *   checkpo filename.{po,strings} [... filenameN.{po,strings}]
  *
  * Compile with:
  *
  *   gcc -o checkpo checkpo.c `cups-config --libs`
- *
- * Contents:
- *
- *   main()            - Validate .po files.
- *   abbreviate()      - Abbreviate a message string as needed.
- *   collect_formats() - Collect all of the format strings in the msgid.
- *   free_formats()    - Free all of the format strings.
  */
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <cups/string.h>
-#include <cups/i18n.h>
+#include <cups/cups-private.h>
 
 
 /*
 
 static char            *abbreviate(const char *s, char *buf, int bufsize);
 static cups_array_t    *collect_formats(const char *id);
+static cups_array_t     *cups_load_strings(const char *filename);
+static int             cups_read_strings(cups_file_t *fp, char *buffer, size_t bufsize, char **id, char **str);
+static char            *cups_scan_strings(char *buffer);
 static void            free_formats(cups_array_t *fmts);
 
 
 /*
- * 'main()' - Validate .po files.
+ * 'main()' - Validate .po and .strings files.
  */
 
 int                                    /* O - Exit code */
@@ -69,12 +56,12 @@ main(int  argc,                             /* I - Number of command-line args */
 
   if (argc < 2)
   {
-    puts("Usage: checkpo filename.po [... filenameN.po]");
+    puts("Usage: checkpo filename.{po,strings} [... filenameN.{po,strings}]");
     return (1);
   }
 
  /*
-  * Check every .po file on the command-line...
+  * Check every .po or .strings file on the command-line...
   */
 
   for (i = 1, status = 0; i < argc; i ++)
@@ -83,12 +70,19 @@ main(int  argc,                             /* I - Number of command-line args */
     * Use the CUPS .po loader to get the message strings...
     */
 
-    if ((po = _cupsMessageLoad(argv[i])) == NULL)
+    if (strstr(argv[i], ".strings"))
+      po = cups_load_strings(argv[i]);
+    else
+      po = _cupsMessageLoad(argv[i], 1);
+
+    if (!po)
     {
       perror(argv[i]);
       return (1);
     }
 
+    if (i > 1)
+      putchar('\n');
     printf("%s: ", argv[i]);
     fflush(stdout);
 
@@ -104,6 +98,68 @@ main(int  argc,                             /* I - Number of command-line args */
          msg;
         msg = (_cups_message_t *)cupsArrayNext(po))
     {
+     /*
+      * Make sure filter message prefixes are not translated...
+      */
+
+      if (!strncmp(msg->id, "ALERT:", 6) || !strncmp(msg->id, "CRIT:", 5) ||
+          !strncmp(msg->id, "DEBUG:", 6) || !strncmp(msg->id, "DEBUG2:", 7) ||
+          !strncmp(msg->id, "EMERG:", 6) || !strncmp(msg->id, "ERROR:", 6) ||
+          !strncmp(msg->id, "INFO:", 5) || !strncmp(msg->id, "NOTICE:", 7) ||
+          !strncmp(msg->id, "WARNING:", 8))
+      {
+        if (pass)
+       {
+         pass = 0;
+         puts("FAIL");
+       }
+
+       printf("    Bad prefix on filter message \"%s\"\n",
+              abbreviate(msg->id, idbuf, sizeof(idbuf)));
+      }
+
+      idfmt = msg->id + strlen(msg->id) - 1;
+      if (idfmt >= msg->id && *idfmt == '\n')
+      {
+        if (pass)
+       {
+         pass = 0;
+         puts("FAIL");
+       }
+
+       printf("    Trailing newline in message \"%s\"\n",
+              abbreviate(msg->id, idbuf, sizeof(idbuf)));
+      }
+
+      for (; idfmt >= msg->id; idfmt --)
+        if (!isspace(*idfmt & 255))
+         break;
+
+      if (idfmt >= msg->id && *idfmt == '!')
+      {
+        if (pass)
+       {
+         pass = 0;
+         puts("FAIL");
+       }
+
+       printf("    Exclamation in message \"%s\"\n",
+              abbreviate(msg->id, idbuf, sizeof(idbuf)));
+      }
+
+      if ((idfmt - 2) >= msg->id && !strncmp(idfmt - 2, "...", 3))
+      {
+        if (pass)
+       {
+         pass = 0;
+         puts("FAIL");
+       }
+
+       printf("    Ellipsis in message \"%s\"\n",
+              abbreviate(msg->id, idbuf, sizeof(idbuf)));
+      }
+
+
       if (!msg->str || !msg->str[0])
       {
         untranslated ++;
@@ -174,31 +230,33 @@ main(int  argc,                           /* I - Number of command-line args */
        free_formats(strfmts);
       }
 
-      if ((!strncmp(msg->id, "ALERT:", 6) && strncmp(msg->str, "ALERT:", 6)) ||
-          (!strncmp(msg->id, "CRIT:", 5) && strncmp(msg->str, "CRIT:", 5)) ||
-          (!strncmp(msg->id, "DEBUG:", 6) && strncmp(msg->str, "DEBUG:", 6)) ||
-          (!strncmp(msg->id, "DEBUG2:", 7) && strncmp(msg->str, "DEBUG2:", 7)) ||
-          (!strncmp(msg->id, "EMERG:", 6) && strncmp(msg->str, "EMERG:", 6)) ||
-          (!strncmp(msg->id, "ERROR:", 6) && strncmp(msg->str, "ERROR:", 6)) ||
-          (!strncmp(msg->id, "INFO:", 5) && strncmp(msg->str, "INFO:", 5)) ||
-          (!strncmp(msg->id, "NOTICE:", 7) && strncmp(msg->str, "NOTICE:", 7)) ||
-          (!strncmp(msg->id, "WARNING:", 8) && strncmp(msg->str, "WARNING:", 8)))
-      {
-        if (pass)
+     /*
+      * Only allow \\, \n, \r, \t, \", and \### character escapes...
+      */
+
+      for (strfmt = msg->str; *strfmt; strfmt ++)
+        if (*strfmt == '\\' &&
+           strfmt[1] != '\\' && strfmt[1] != 'n' && strfmt[1] != 'r' &&
+           strfmt[1] != 't' && strfmt[1] != '\"' && !isdigit(strfmt[1] & 255))
        {
-         pass = 0;
-         puts("FAIL");
-       }
+         if (pass)
+         {
+           pass = 0;
+           puts("FAIL");
+         }
 
-       printf("    Bad prefix on filter message \"%s\"\n      for \"%s\"\n\n",
-              abbreviate(msg->str, strbuf, sizeof(strbuf)),
-              abbreviate(msg->id, idbuf, sizeof(idbuf)));
-      }
+         printf("    Bad escape \\%c in filter message \"%s\"\n"
+                "      for \"%s\"\n", strfmt[1],
+                abbreviate(msg->str, strbuf, sizeof(strbuf)),
+                abbreviate(msg->id, idbuf, sizeof(idbuf)));
+          break;
+        }
     }
 
     if (pass)
     {
-      if ((untranslated * 10) >= cupsArrayCount(po))
+      if ((untranslated * 10) >= cupsArrayCount(po) &&
+          strcmp(argv[i], "cups.pot"))
       {
        /*
         * Only allow 10% of messages to be untranslated before we fail...
@@ -206,14 +264,14 @@ main(int  argc,                           /* I - Number of command-line args */
 
         pass = 0;
         puts("FAIL");
-       printf("    Too many untranslated messages (%d of %d)\n\n",
+       printf("    Too many untranslated messages (%d of %d)\n",
               untranslated, cupsArrayCount(po));
       }
       else if (untranslated > 0)
-        printf("PASS (%d of %d untranslated)\n\n", untranslated,
+        printf("PASS (%d of %d untranslated)\n", untranslated,
               cupsArrayCount(po));
       else
-        puts("PASS\n");
+        puts("PASS");
     }
 
     if (!pass)
@@ -275,7 +333,7 @@ abbreviate(const char *s,           /* I - String to abbreviate */
   }
 
   if (*s)
-    strcpy(bufptr, "...");
+    memcpy(bufptr, "...", 4);
   else
     *bufptr = '\0';
 
@@ -328,6 +386,152 @@ collect_formats(const char *id)           /* I - msgid string */
 }
 
 
+/*
+ * 'cups_load_strings()' - Load a .strings file into a _cups_msg_t array.
+ */
+
+static cups_array_t *                   /* O - CUPS array of _cups_msg_t values */
+cups_load_strings(const char *filename) /* I - File to load */
+{
+  cups_file_t     *fp;                  /* .strings file */
+  cups_array_t    *po;                  /* Localization array */
+  _cups_message_t *m;                   /* Localization message */
+  char           buffer[8192],         /* Message buffer */
+                  *id,                 /* ID string */
+                  *str;                        /* Translated message */
+
+
+  if ((fp = cupsFileOpen(filename, "r")) == NULL)
+    return (NULL);
+
+  po = _cupsMessageNew(NULL);
+
+  while (cups_read_strings(fp, buffer, sizeof(buffer), &id, &str))
+  {
+    if ((m = malloc(sizeof(_cups_message_t))) == NULL)
+      break;
+
+    m->id  = strdup(id);
+    m->str = strdup(str);
+
+    if (m->id && m->str)
+      cupsArrayAdd(po, m);
+    else
+    {
+      if (m->id)
+        free(m->id);
+
+      if (m->str)
+        free(m->str);
+
+      free(m);
+
+      cupsArrayDelete(po);
+      po = NULL;
+      break;
+    }
+  }
+
+  cupsFileClose(fp);
+
+  return (po);
+}
+
+
+/*
+ * 'cups_read_strings()' - Read a pair of strings from a .strings file.
+ */
+
+static int                             /* O - 1 on success, 0 on failure */
+cups_read_strings(cups_file_t *strings,        /* I - .strings file */
+                  char        *buffer, /* I - Line buffer */
+                  size_t      bufsize, /* I - Size of line buffer */
+                 char        **id,     /* O - Pointer to ID string */
+                 char        **str)    /* O - Pointer to translation string */
+{
+  char *bufptr;                        /* Pointer into buffer */
+
+
+  while (cupsFileGets(strings, buffer, bufsize))
+  {
+    if (buffer[0] != '\"')
+      continue;
+
+    *id    = buffer + 1;
+    bufptr = cups_scan_strings(buffer);
+
+    if (*bufptr != '\"')
+      continue;
+
+    *bufptr++ = '\0';
+
+    while (*bufptr && *bufptr != '\"')
+      bufptr ++;
+
+    if (!*bufptr)
+      continue;
+
+    *str   = bufptr + 1;
+    bufptr = cups_scan_strings(bufptr);
+
+    if (*bufptr != '\"')
+      continue;
+
+    *bufptr = '\0';
+
+    return (1);
+  }
+
+  return (0);
+}
+
+
+/*
+ * 'cups_scan_strings()' - Scan a quoted string.
+ */
+
+static char *                          /* O - End of string */
+cups_scan_strings(char *buffer)                /* I - Start of string */
+{
+  char *bufptr;                        /* Pointer into string */
+
+
+  for (bufptr = buffer + 1; *bufptr && *bufptr != '\"'; bufptr ++)
+  {
+    if (*bufptr == '\\')
+    {
+      if (bufptr[1] >= '0' && bufptr[1] <= '3' &&
+         bufptr[2] >= '0' && bufptr[2] <= '7' &&
+         bufptr[3] >= '0' && bufptr[3] <= '7')
+      {
+       /*
+       * Decode \nnn octal escape...
+       */
+
+       *bufptr = (char)(((((bufptr[1] - '0') << 3) | (bufptr[2] - '0')) << 3) | (bufptr[3] - '0'));
+       _cups_strcpy(bufptr + 1, bufptr + 4);
+      }
+      else
+      {
+       /*
+       * Decode \C escape...
+       */
+
+       _cups_strcpy(bufptr, bufptr + 1);
+       if (*bufptr == 'n')
+         *bufptr = '\n';
+       else if (*bufptr == 'r')
+         *bufptr = '\r';
+       else if (*bufptr == 't')
+         *bufptr = '\t';
+      }
+    }
+  }
+
+  return (bufptr);
+}
+
+
 /*
  * 'free_formats()' - Free all of the format strings.
  */
@@ -343,8 +547,3 @@ free_formats(cups_array_t *fmts)    /* I - Array of format strings */
 
   cupsArrayDelete(fmts);
 }
-
-
-/*
- * End of "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $".
- */