Blame src/plural.c

Packit 8f70b4
/*
Packit 8f70b4
 * plural word form chooser for i18n
Packit 8f70b4
 *
Packit 8f70b4
 * Copyright (c) 1998,2016 by Alexander V. Lukyanov (lav@yars.free.net)
Packit 8f70b4
 *
Packit 8f70b4
 * This file is in public domain.
Packit 8f70b4
 */
Packit 8f70b4
Packit 8f70b4
/*
Packit 8f70b4
 * This file provides a function to transform a string with all plural forms
Packit 8f70b4
 * of a word to a string with concrete form depending on a number.
Packit 8f70b4
 * It uses a rule encoded in special string.
Packit 8f70b4
 */
Packit 8f70b4
Packit 8f70b4
/* TODO:
Packit 8f70b4
 *   allow to select number of argument
Packit 8f70b4
 */
Packit 8f70b4
Packit 8f70b4
#include <config.h>
Packit 8f70b4
#include <string.h>
Packit 8f70b4
#include <stdio.h>
Packit 8f70b4
#include <stdarg.h>
Packit 8f70b4
#include <stdlib.h>
Packit 8f70b4
#include "plural.h"
Packit 8f70b4
Packit 8f70b4
static int choose_plural_form(const char *rule,int num)
Packit 8f70b4
{
Packit 8f70b4
   int res=0;
Packit 8f70b4
   int match=1;
Packit 8f70b4
   int n=num;
Packit 8f70b4
   char c;
Packit 8f70b4
   int arg,arg_len;
Packit 8f70b4
Packit 8f70b4
   for(;;)
Packit 8f70b4
   {
Packit 8f70b4
      switch(c=*rule)
Packit 8f70b4
      {
Packit 8f70b4
      case '=':
Packit 8f70b4
      case '!':
Packit 8f70b4
      case '>':
Packit 8f70b4
      case '<':
Packit 8f70b4
      case '%':
Packit 8f70b4
	 if(sscanf(rule+1,"%d%n",&arg,&arg_len)<1)
Packit 8f70b4
	    return -1;
Packit 8f70b4
	 rule+=arg_len;
Packit 8f70b4
	 if(c=='%')
Packit 8f70b4
	    n%=arg;
Packit 8f70b4
	 if((c=='=' && !(n==arg))
Packit 8f70b4
	 || (c=='!' && !(n!=arg))
Packit 8f70b4
	 || (c=='>' && !(n> arg))
Packit 8f70b4
	 || (c=='<' && !(n< arg)))
Packit 8f70b4
	    match=0;
Packit 8f70b4
	 break;
Packit 8f70b4
Packit 8f70b4
      case '|':
Packit 8f70b4
      case ' ':
Packit 8f70b4
      case '\0':
Packit 8f70b4
	 if(match)
Packit 8f70b4
	    return res;
Packit 8f70b4
	 if(c=='\0')
Packit 8f70b4
	    return res+1;
Packit 8f70b4
	 if(c=='|')
Packit 8f70b4
	    match=1;
Packit 8f70b4
	 if(c==' ') /* next rule */
Packit 8f70b4
	 {
Packit 8f70b4
	    n=num;
Packit 8f70b4
	    res++;
Packit 8f70b4
	    match=1;
Packit 8f70b4
	 }
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      rule++;
Packit 8f70b4
   }
Packit 8f70b4
/*   return res;*/
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/*
Packit 8f70b4
 * The function takes _untranslated_ string with $form1|form2|form3...$
Packit 8f70b4
 * inserts, and a list of integer numbers. It uses gettext on the string.
Packit 8f70b4
 * Using "translated" rule and the list of numbers it choose appropriate
Packit 8f70b4
 * plural forms.
Packit 8f70b4
 *
Packit 8f70b4
 * If the string or the rule cannot be translated, it uses untranslated
Packit 8f70b4
 * string and default (english) rule.
Packit 8f70b4
 *
Packit 8f70b4
 * Returns pointer to static storage, copy if needed.
Packit 8f70b4
 */
Packit 8f70b4
Packit 8f70b4
const char *plural(const char *format,...)
Packit 8f70b4
{
Packit 8f70b4
   static char *res=0;
Packit 8f70b4
   static size_t res_size=0;
Packit 8f70b4
Packit 8f70b4
   /* This is the rule for plural form choice (last condition can be omitted) */
Packit 8f70b4
   /* Operators: = > < ! | % */
Packit 8f70b4
   const char *rule=N_("=1 =0|>1");
Packit 8f70b4
   const char *s;
Packit 8f70b4
   char *store;
Packit 8f70b4
   int index,plural_index;
Packit 8f70b4
Packit 8f70b4
   const char *new_format=gettext(format);
Packit 8f70b4
   const char *new_rule=gettext(rule);
Packit 8f70b4
Packit 8f70b4
   va_list va;
Packit 8f70b4
   long arg;
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
   va_start(va,format);
Packit 8f70b4
Packit 8f70b4
   if(new_format!=format && new_rule!=rule)  /* there is translation */
Packit 8f70b4
   {
Packit 8f70b4
      rule=new_rule;	   /* use "translated" rule */
Packit 8f70b4
      format=new_format;   /* and translated format */
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(res==0)
Packit 8f70b4
      res=malloc(res_size=256);
Packit 8f70b4
   if(res==0)
Packit 8f70b4
      goto va_end_out;
Packit 8f70b4
Packit 8f70b4
   store=res;
Packit 8f70b4
Packit 8f70b4
   for(s=format; *s; s++)
Packit 8f70b4
   {
Packit 8f70b4
#define ALLOCATE \
Packit 8f70b4
      if(store-res+1>=res_size)		  \
Packit 8f70b4
      {					  \
Packit 8f70b4
	 int dif=store-res;		  \
Packit 8f70b4
	 res=realloc(res,res_size*=2);	  \
Packit 8f70b4
	 if(res==0)			  \
Packit 8f70b4
	    goto va_end_out;		  \
Packit 8f70b4
	 store=res+dif;			  \
Packit 8f70b4
      } /* end ALLOCATE */
Packit 8f70b4
Packit 8f70b4
      if(*s=='$' && s[1])
Packit 8f70b4
      {
Packit 8f70b4
	 s++;
Packit 8f70b4
	 if(*s=='$')
Packit 8f70b4
	    goto plain;
Packit 8f70b4
Packit 8f70b4
	 /* check options */
Packit 8f70b4
	 if(*s=='#')
Packit 8f70b4
	 {
Packit 8f70b4
	    s++;
Packit 8f70b4
	    if(*s=='l')	/* long */
Packit 8f70b4
	    {
Packit 8f70b4
	       s++;
Packit 8f70b4
	       if(*s=='l') /* long long */
Packit 8f70b4
	       {
Packit 8f70b4
		  s++;
Packit 8f70b4
		  arg=va_arg(va,long long)%1000000;
Packit 8f70b4
	       }
Packit 8f70b4
	       else
Packit 8f70b4
		  arg=va_arg(va,long);
Packit 8f70b4
	    }
Packit 8f70b4
	    else
Packit 8f70b4
	    {
Packit 8f70b4
	       arg=va_arg(va,int);
Packit 8f70b4
	    }
Packit 8f70b4
	    if(*s=='#') /* end of options */
Packit 8f70b4
	       s++;
Packit 8f70b4
	 }
Packit 8f70b4
	 else
Packit 8f70b4
	 {
Packit 8f70b4
	    arg=va_arg(va,int);
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
	 if(arg<0)
Packit 8f70b4
	    arg=-arg;
Packit 8f70b4
Packit 8f70b4
	 plural_index=choose_plural_form(rule,arg);
Packit 8f70b4
Packit 8f70b4
	 index=0;
Packit 8f70b4
	 while(index!=plural_index)
Packit 8f70b4
	 {
Packit 8f70b4
	    /* skip plural form */
Packit 8f70b4
	    while(*s!='$' && *s!='|' && *s)
Packit 8f70b4
	       s++;
Packit 8f70b4
	    if(*s==0)
Packit 8f70b4
	       goto out;
Packit 8f70b4
	    if(*s=='$')
Packit 8f70b4
	       break;
Packit 8f70b4
	    s++;
Packit 8f70b4
	    index++;
Packit 8f70b4
	 }
Packit 8f70b4
	 if(index==plural_index)
Packit 8f70b4
	 {
Packit 8f70b4
	    /* insert the form */
Packit 8f70b4
	    while(*s!='$' && *s!='|' && *s)
Packit 8f70b4
	    {
Packit 8f70b4
	       ALLOCATE;
Packit 8f70b4
	       *store++=*s++;
Packit 8f70b4
	    }
Packit 8f70b4
	    while(*s!='$' && *s)
Packit 8f70b4
	       s++;
Packit 8f70b4
	    if(*s==0)
Packit 8f70b4
	       goto out;
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
      {
Packit 8f70b4
      plain:
Packit 8f70b4
	 ALLOCATE;
Packit 8f70b4
	 *store++=*s;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
out:
Packit 8f70b4
   ALLOCATE;
Packit 8f70b4
   *store=0;
Packit 8f70b4
va_end_out:
Packit 8f70b4
   va_end(va);
Packit 8f70b4
   return res;
Packit 8f70b4
}