Blame src/plural.c

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