/*
 * Convert a GNU gettext .po file to an Apple .strings file.
 *
 * Copyright 2007-2017 by Apple Inc.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 *
 * Usage:
 *
 *   po2strings filename.strings filename.po
 *
 * Compile with:
 *
 *   gcc -o po2strings po2strings.c `cups-config --libs`
 */

#include <cups/cups-private.h>


/*
 * The .strings file format is simple:
 *
 * // comment
 * "msgid" = "msgstr";
 *
 * The GNU gettext .po format is also fairly simple:
 *
 *     #. comment
 *     msgid "some text"
 *     msgstr "localized text"
 *
 * The comment, msgid, and msgstr text can span multiple lines using the form:
 *
 *     #. comment
 *     #. more comments
 *     msgid ""
 *     "some long text"
 *     msgstr ""
 *     "localized text spanning "
 *     "multiple lines"
 *
 * Both the msgid and msgstr strings use standard C quoting for special
 * characters like newline and the double quote character.
 */

static char	*normalize_string(const char *idstr, char *buffer, size_t bufsize);


/*
 *   main() - Convert .po file to .strings.
 */

int					/* O - Exit code */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  int			i;		/* Looping var */
  const char		*pofile,	/* .po filename */
			*stringsfile;	/* .strings filename */
  cups_file_t		*po,		/* .po file */
			*strings;	/* .strings file */
  char			s[4096],	/* String buffer */
			*ptr,		/* Pointer into buffer */
			*temp,		/* New string */
			*msgid,		/* msgid string */
			*msgstr,	/* msgstr string */
			normalized[8192];/* Normalized msgid string */
  size_t		length;		/* Length of combined strings */
  int			use_msgid;	/* Use msgid strings for msgstr? */


 /*
  * Process command-line arguments...
  */

  pofile      = NULL;
  stringsfile = NULL;
  use_msgid   = 0;

  for (i = 1; i < argc; i ++)
  {
    if (!strcmp(argv[i], "-m"))
      use_msgid = 1;
    else if (argv[i][0] == '-')
    {
      puts("Usage: po2strings [-m] filename.po filename.strings");
      return (1);
    }
    else if (!pofile)
      pofile = argv[i];
    else if (!stringsfile)
      stringsfile = argv[i];
    else
    {
      puts("Usage: po2strings [-m] filename.po filename.strings");
      return (1);
    }
  }

  if (!pofile || !stringsfile)
  {
    puts("Usage: po2strings [-m] filename.po filename.strings");
    return (1);
  }

 /*
  * Read strings from the .po file and write to the .strings file...
  */

  if ((po = cupsFileOpen(pofile, "r")) == NULL)
  {
    perror(pofile);
    return (1);
  }

  if ((strings = cupsFileOpen(stringsfile, "w")) == NULL)
  {
    perror(stringsfile);
    cupsFileClose(po);
    return (1);
  }

  msgid = msgstr = NULL;

  while (cupsFileGets(po, s, sizeof(s)) != NULL)
  {
    if (s[0] == '#' && s[1] == '.')
    {
     /*
      * Copy comment string...
      */

      if (msgid && msgstr)
      {
       /*
        * First output the last localization string...
	*/

	if (*msgid)
	  cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid,
			 (use_msgid || !*msgstr) ? msgid : msgstr);

	free(msgid);
	free(msgstr);
	msgid = msgstr = NULL;
      }

      cupsFilePrintf(strings, "//%s\n", s + 2);
    }
    else if (s[0] == '#' || !s[0])
    {
     /*
      * Skip blank and file comment lines...
      */

      continue;
    }
    else
    {
     /*
      * Strip the trailing quote...
      */

      if ((ptr = strrchr(s, '\"')) == NULL)
	continue;

      *ptr = '\0';

     /*
      * Find start of value...
      */

      if ((ptr = strchr(s, '\"')) == NULL)
	continue;

      ptr ++;

     /*
      * Create or add to a message...
      */

      if (!strncmp(s, "msgid", 5))
      {
       /*
	* Output previous message as needed...
	*/

        if (msgid && msgstr)
	{
	  if (*msgid)
            cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
	}

	if (msgid)
	  free(msgid);

	if (msgstr)
	  free(msgstr);

        msgid  = strdup(ptr);
	msgstr = NULL;
      }
      else if (s[0] == '\"' && (msgid || msgstr))
      {
       /*
	* Append to current string...
	*/

        size_t ptrlen = strlen(ptr);	/* Length of string */

	length = strlen(msgstr ? msgstr : msgid);

	if ((temp = realloc(msgstr ? msgstr : msgid,
			    length + ptrlen + 1)) == NULL)
	{
	  free(msgid);
	  if (msgstr)
	    free(msgstr);
	  perror("Unable to allocate string");
	  return (1);
	}

	if (msgstr)
	{
	 /*
	  * Copy the new portion to the end of the msgstr string - safe
	  * to use strcpy because the buffer is allocated to the correct
	  * size...
	  */

	  msgstr = temp;

	  memcpy(msgstr + length, ptr, ptrlen + 1);
	}
	else
	{
	 /*
	  * Copy the new portion to the end of the msgid string - safe
	  * to use strcpy because the buffer is allocated to the correct
	  * size...
	  */

	  msgid = temp;

	  memcpy(msgid + length, ptr, ptrlen + 1);
	}
      }
      else if (!strncmp(s, "msgstr", 6) && msgid)
      {
       /*
	* Set the string...
	*/

        if (msgstr)
          free(msgstr);

	if ((msgstr = strdup(ptr)) == NULL)
	{
	  free(msgid);
	  perror("Unable to allocate msgstr");
	  return (1);
	}
      }
    }
  }

  if (msgid && msgstr)
  {
    if (*msgid)
      cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
  }

  if (msgid)
    free(msgid);

  if (msgstr)
    free(msgstr);

  cupsFileClose(po);
  cupsFileClose(strings);

  return (0);
}


/*
 * 'normalize_string()' - Normalize a msgid string.
 *
 * This function converts ASCII ellipsis and double quotes to their Unicode
 * counterparts.
 */

static char *				/* O - Normalized string */
normalize_string(const char *idstr,	/* I - msgid string */
                 char       *buffer,	/* I - Normalized string buffer */
                 size_t     bufsize)	/* I - Size of string buffer */
{
  char	*bufptr = buffer,		/* Pointer into buffer */
	*bufend = buffer + bufsize - 3;	/* End of buffer */
  int	quote = 0,			/* Quote direction */
	html = 0;			/* HTML text */


  while (*idstr && bufptr < bufend)
  {
    if (!strncmp(idstr, "<A ", 3))
      html = 1;
    else if (html && *idstr == '>')
      html = 0;

    if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.')
    {
     /*
      * Convert ... to Unicode ellipsis...
      */

      *bufptr++ = (char)0xE2;
      *bufptr++ = (char)0x80;
      *bufptr++ = (char)0xA6;
      idstr += 2;
    }
    else if (!html && *idstr == '\\' && idstr[1] == '\"')
    {
      if (quote)
      {
       /*
        * Convert second \" to Unicode right (curley) double quote.
        */

	*bufptr++ = (char)0xE2;
	*bufptr++ = (char)0x80;
	*bufptr++ = (char)0x9D;
	quote     = 0;
      }
      else if (strchr(idstr + 2, '\"') != NULL)
      {
       /*
        * Convert first \" to Unicode left (curley) double quote.
        */

	*bufptr++ = (char)0xE2;
	*bufptr++ = (char)0x80;
	*bufptr++ = (char)0x9C;
	quote     = 1;
      }
      else
      {
       /*
        * Convert lone \" to Unicode double prime.
        */

        *bufptr++ = (char)0xE2;
        *bufptr++ = (char)0x80;
        *bufptr++ = (char)0xB3;
      }

      idstr ++;
    }
    else if (*idstr == '\'')
    {
      if (strchr(idstr + 1, '\'') == NULL || quote)
      {
       /*
        * Convert second ' (or ' used for a contraction) to Unicode right
        * (curley) single quote.
        */

	*bufptr++ = (char)0xE2;
	*bufptr++ = (char)0x80;
	*bufptr++ = (char)0x99;
	quote     = 0;
      }
      else
      {
       /*
        * Convert first ' to Unicode left (curley) single quote.
        */

	*bufptr++ = (char)0xE2;
	*bufptr++ = (char)0x80;
	*bufptr++ = (char)0x98;
	quote     = 1;
      }
    }
    else
      *bufptr++ = *idstr;

    idstr ++;
  }

  *bufptr = '\0';

  return (buffer);
}
