#! /bin/sh /usr/share/dpatch/dpatch-run ## say_he.dpatch by Tzafrir Cohen ## ## All lines beginning with `## DP:' are a description of the patch. ## DP: Hebrew support for SayNumber @DPATCH@ diff -urNad asterisk-1.0.7/say.c /tmp/dpep.I09gkz/asterisk-1.0.7/say.c --- asterisk-1.0.7/say.c 2005-06-12 00:26:42.418655556 +0300 +++ /tmp/dpep.I09gkz/asterisk-1.0.7/say.c 2005-06-12 00:30:06.334203512 +0300 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -408,6 +409,7 @@ en - English es - Spanish, Mexican fr - French + he - Hebrew it - Italian nl - Dutch pl - Polish @@ -416,7 +418,7 @@ tw - Taiwanese Gender: - For Portuguese, French & Spanish, we're using m & f options to saynumber() to indicate if the gender is masculine or feminine. + For Portuguese, French, Hebrew & Spanish, we're using m & f options to saynumber() to indicate if the gender is masculine or feminine. For Danish, we're using c & n options to saynumber() to indicate if the gender is commune or neutrum. This still needs to be implemented for German (although the option is passed to the function, it currently does nothing with it). @@ -448,6 +450,7 @@ static int ast_say_number_full_de(struct ast_channel *chan, int num, char *ints, char *language, char *options, int audiofd, int ctrlfd); static int ast_say_number_full_es(struct ast_channel *chan, int num, char *ints, char *language, char *options, int audiofd, int ctrlfd); static int ast_say_number_full_fr(struct ast_channel *chan, int num, char *ints, char *language, char *options, int audiofd, int ctrlfd); +static int ast_say_number_full_he(struct ast_channel *chan, int num, char *ints, char *language, char *options, int audiofd, int ctrlfd); static int ast_say_number_full_it(struct ast_channel *chan, int num, char *ints, char *language, int audiofd, int ctrlfd); static int ast_say_number_full_nl(struct ast_channel *chan, int num, char *ints, char *language, int audiofd, int ctrlfd); static int ast_say_number_full_pl(struct ast_channel *chan, int num, char *ints, char *language, char *options, int audiofd, int ctrlfd); @@ -464,6 +467,7 @@ static int ast_say_date_with_format_en(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone); static int ast_say_date_with_format_de(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone); static int ast_say_date_with_format_es(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone); +static int ast_say_date_with_format_he(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone); static int ast_say_date_with_format_nl(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone); static int ast_say_date_with_format_pt(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone); static int ast_say_date_with_format_tw(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone); @@ -505,6 +509,8 @@ return(ast_say_number_full_es(chan, num, ints, language, options, audiofd, ctrlfd)); } else if (!strcasecmp(language, "fr") ) { /* French syntax */ return(ast_say_number_full_fr(chan, num, ints, language, options, audiofd, ctrlfd)); + } else if (!strcasecmp(language, "he") ) { /* Hebrew syntax */ + return(ast_say_number_full_he(chan, num, ints, language, options, audiofd, ctrlfd)); } else if (!strcasecmp(language, "it") ) { /* Italian syntax */ return(ast_say_number_full_it(chan, num, ints, language, audiofd, ctrlfd)); } else if (!strcasecmp(language, "nl") ) { /* Dutch syntax */ @@ -978,6 +984,150 @@ return res; } + + +/*--- ast_say_number_full_he: Hebrew syntax */ +/* Extra sounds needed: + 1F: feminin 'one' + ve: 'and' + 2hundred: 2 hundred + 2thousands: 2 thousand + thousands: plural of 'thousand' + 3sF 'Smichut forms (female) + 4sF + 5sF + 6sF + 7sF + 8sF + 9sF + 3s 'Smichut' forms (male) + 4s + 5s + 6s + 7s + 9s + 10s + 11s + 12s + 13s + 14s + 15s + 16s + 17s + 18s + 19s + +TODO: 've' should sometimed be 'hu': +* before 'shtaym' (2, F) +* before 'shnaym' (2, M) +* before 'shlosha' (3, M) +* before 'shmone' (8, M) +* before 'shlosim' (30) +* before 'shmonim' (80) + +What about: +'sheva' (7, F)? +'tesha' (9, F)? +*/ +#define SAY_NUM_BUF_SIZE 256 +static int ast_say_number_full_he(struct ast_channel *chan, int num, char *ints, char *language, char *options, int audiofd, int ctrlfd) +{ + int res = 0; + int state = 0; /* no need to save anything */ + int mf = 1; /* +1 = Masculin; -1 = Feminin */ + char fn[SAY_NUM_BUF_SIZE] = ""; + ast_verbose(VERBOSE_PREFIX_3 "ast_say_digits_full: started. " + "num: %d, options=\"%s\"\n", + num, options + ); + if (!num) + return ast_say_digits_full(chan, 0,ints, language, audiofd, ctrlfd); + + if (options && !strncasecmp(options, "f",1)) + mf = -1; + + /* Do we have work to do? */ + while(!res && (num || (state>0) )) { + /* first type of work: play a second sound. In this loop + * we can only play one sound file at a time. Thus playing + * a second one requires repeating the loop just for the + * second file. The variable 'state' remembers where we were. + * state==0 is the normal mode and it means that we continue + * to check if the number num has yet anything left. + */ + ast_verbose(VERBOSE_PREFIX_3 "ast_say_digits_full: num: %d, " + "state=%d, options=\"%s\", mf=%d\n", + num, state, options, mf + ); + if (state==1) { + snprintf(fn, sizeof(fn), "digits/hundred"); + state = 0; + } else if (state==2) { + snprintf(fn, sizeof(fn), "digits/ve"); + state = 0; + } else if (state==3) { + snprintf(fn, sizeof(fn), "digits/thousands"); + state=0; + } else if (num <21) { + if (mf < 0) + snprintf(fn, sizeof(fn), "digits/%dF", num); + else + snprintf(fn, sizeof(fn), "digits/%d", num); + num = 0; + } else if (num < 100) { + snprintf(fn, sizeof(fn), "digits/%d", (num/10)*10); + num = num % 10; + if (num>0) state=2; + } else if (num < 200) { + snprintf(fn, sizeof(fn), "digits/hundred"); + num = num - 100; + } else if (num < 300) { + snprintf(fn, sizeof(fn), "digits/hundred"); + num = num - 100; + } else if (num < 1000) { + snprintf(fn, sizeof(fn), "digits/%d", (num/100)); + state=1; + num = num % 100; + } else if (num < 2000) { + snprintf(fn, sizeof(fn), "digits/thousand"); + num = num - 1000; + } else if (num < 3000) { + snprintf(fn, sizeof(fn), "digits/2thousand"); + num = num - 2000; + if (num>0) state=2; + } else if (num < 20000) { + snprintf(fn, sizeof(fn), "digits/%ds",(num/1000)); + num = num % 1000; + state=3; + } else if (num < 1000000) { + res = ast_say_number_full_he(chan, num / 1000, ints, language, options, audiofd, ctrlfd); + if (res) + return res; + snprintf(fn, sizeof(fn), "digits/thousand"); + num = num % 1000; + } else if (num < 1000000000) { + res = ast_say_number_full_he(chan, num / 1000000, ints, language, options, audiofd, ctrlfd); + if (res) + return res; + snprintf(fn, sizeof(fn), "digits/million"); + num = num % 1000000; + } else { + ast_log(LOG_DEBUG, "Number '%d' is too big for me\n", num); + res = -1; + } + if (!res) { + if(!ast_streamfile(chan, fn, language)) { + if ((audiofd > -1) && (ctrlfd > -1)) + res = ast_waitstream_full(chan, ints, audiofd, ctrlfd); + else + res = ast_waitstream(chan, ints); + } + ast_stopstream(chan); + } + } + return res; +} + /*--- ast_say_number_full_it: Italian */ static int ast_say_number_full_it(struct ast_channel *chan, int num, char *ints, char *language, int audiofd, int ctrlfd) { @@ -1925,6 +2075,8 @@ return(ast_say_date_with_format_de(chan, time, ints, lang, format, timezone)); } else if (!strcasecmp(lang, "es") || !strcasecmp(lang, "mx")) { /* Spanish syntax */ return(ast_say_date_with_format_es(chan, time, ints, lang, format, timezone)); + } else if (!strcasecmp(lang, "he")) { /* Hebrew syntax */ + return(ast_say_date_with_format_he(chan, time, ints, lang, format, timezone)); } else if (!strcasecmp(lang, "nl") ) { /* Dutch syntax */ return(ast_say_date_with_format_nl(chan, time, ints, lang, format, timezone)); } else if (!strcasecmp(lang, "pt") ) { /* Portuguese syntax */ @@ -2498,6 +2650,209 @@ return res; } +/* TODO: this probably is not the correct format for doxygen remarks */ + +/** ast_say_date_with_format_he Say formmated date in Hebrew + * + * @seealso ast_say_date_with_format_en for the details of the options + * + * Changes from the English version: + * + * * don't replicate in here the logic of ast_say_number_full_he + * + * * year is always 4-digit (because it's simpler) + * + * * added c, x, and X. Mainly for my tests + * + * * The standard "long" format used in Hebrew is AdBY, rather than ABdY + * + * TODO: + * * A "ha" is missing in the standard date format, before the 'd'. + * * The numbers of 3000--19000 are not handled well + **/ +#define IL_DATE_STR "AdBY" +#define IL_TIME_STR "IMp" +#define IL_DATE_STR_FULL IL_DATE_STR " 'digits/at' " IL_TIME_STR +int ast_say_date_with_format_he(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone) +{ + /* TODO: This whole function is cut&paste from + * ast_say_date_with_format_en . Is that considered acceptable? + **/ + struct tm tm; + int res=0, offset, sndoffset; + char sndfile[256], nextmsg[256]; + + ast_localtime(&time,&tm,timezone); + + for (offset=0 ; format[offset] != '\0' ; offset++) { + ast_log(LOG_DEBUG, "Parsing %c (offset %d) in %s\n", format[offset], offset, format); + switch (format[offset]) { + /* NOTE: if you add more options here, please try to be consistent with strftime(3) */ + case '\'': + /* Literal name of a sound file */ + sndoffset=0; + for (sndoffset=0 ; (format[++offset] != '\'') && (sndoffset < 256) ; sndoffset++) + sndfile[sndoffset] = format[offset]; + sndfile[sndoffset] = '\0'; + res = wait_file(chan,ints,sndfile,lang); + break; + case 'A': + case 'a': + /* Sunday - Saturday */ + snprintf(nextmsg,sizeof(nextmsg), "digits/day-%d", tm.tm_wday); + res = wait_file(chan,ints,nextmsg,lang); + break; + case 'B': + case 'b': + case 'h': + /* January - December */ + snprintf(nextmsg,sizeof(nextmsg), "digits/mon-%d", tm.tm_mon); + res = wait_file(chan,ints,nextmsg,lang); + break; + case 'd': + case 'e': /* Day of the month */ + /* I'm not sure exactly what the parameters + * audiofd and ctrlfd to + * ast_say_number_full_he mean, but it seems + * safe to pass -1 there. + * + * At least in one of the pathes :-( + */ + res = ast_say_number_full_he(chan, tm.tm_mday, + ints, lang, "m", -1, -1 + ); + break; + case 'Y': /* Year */ + res = ast_say_number_full_he(chan, tm.tm_year+1900, + ints, lang, "f", -1, -1 + ); + break; + case 'I': + case 'l': /* 12-Hour */ + { + int hour = tm.tm_hour; + hour = hour%12; + if (hour == 0) hour=12; + + res = ast_say_number_full_he(chan, hour, + ints, lang, "f", -1, -1 + ); + } + break; + case 'H': + case 'k': /* 24-Hour */ + /* With 'H' there is an 'oh' after a single- + * digit hour */ + if ((format[offset] == 'H') && + (tm.tm_hour <10)&&(tm.tm_hour>0) + ) { /* e.g. oh-eight */ + res = wait_file(chan,ints, "digits/oh",lang); + } + + res = ast_say_number_full_he(chan, tm.tm_hour, + ints, lang, "f", -1, -1 + ); + break; + case 'M': /* Minute */ + res = ast_say_number_full_he(chan, tm.tm_min, + ints, lang,"f", -1, -1 + ); + break; + case 'P': + case 'p': + /* AM/PM */ + if (tm.tm_hour > 11) + snprintf(nextmsg,sizeof(nextmsg), "digits/p-m"); + else + snprintf(nextmsg,sizeof(nextmsg), "digits/a-m"); + res = wait_file(chan,ints,nextmsg,lang); + break; + case 'Q': + /* Shorthand for "Today", "Yesterday", or "date" */ + case 'q': + /* Shorthand for "" (today), "Yesterday", A + * (weekday), or "date" */ + { + char todo='q';/* The letter to format*/ + if (format[offset] == 'Q') { + todo='Q'; + } + struct timeval now; + struct tm tmnow; + time_t beg_today; + + gettimeofday(&now,NULL); + ast_localtime(&now.tv_sec,&tmnow,timezone); + /* This might be slightly off, if we transcend a leap second, but never more off than 1 second */ + /* In any case, it saves not having to do ast_mktime() */ + beg_today = now.tv_sec - (tmnow.tm_hour * 3600) - (tmnow.tm_min * 60) - (tmnow.tm_sec); + if (beg_today < time) { + /* Today */ + if (todo == 'Q') { + res = wait_file(chan, + ints, + "digits/today", + lang); + } + } else if (beg_today - 86400 < time) { + /* Yesterday */ + res = wait_file(chan,ints, "digits/yesterday",lang); + } else if ((todo != 'Q') && + (beg_today - 86400 * 6 < time)) + { + /* Within the last week */ + res = ast_say_date_with_format_he( + chan, time, ints, lang, + "A", timezone); + } else { + res = ast_say_date_with_format_he( + chan, time, ints, lang, + IL_DATE_STR, timezone); + } + } + break; + case 'R': + res = ast_say_date_with_format(chan, time, ints, lang, "HM", timezone); + break; + case 'S': /* Seconds */ + res = ast_say_number_full_he(chan, tm.tm_sec, + ints, lang, "f", -1, -1 + ); + break; + case 'T': + res = ast_say_date_with_format(chan, time, ints, lang, "HMS", timezone); + break; + /* c, x, and X seem useful for testing. Not sure + * if thiey're good for the general public */ + case 'c': + res = ast_say_date_with_format_he(chan, time, + ints, lang, IL_DATE_STR_FULL, timezone); + break; + case 'x': + res = ast_say_date_with_format_he(chan, time, + ints, lang, IL_DATE_STR, timezone); + break; + case 'X': /* Currently not locale-dependent...*/ + res = ast_say_date_with_format_he(chan, time, + ints, lang, IL_TIME_STR, timezone); + break; + case ' ': + case ' ': + /* Just ignore spaces and tabs */ + break; + default: + /* Unknown character */ + ast_log(LOG_WARNING, "Unknown character in datetime format %s: %c at pos %d\n", format, format[offset], offset); + } + /* Jump out on DTMF */ + if (res) { + break; + } + } + return res; +} + + /* Spanish syntax */ int ast_say_date_with_format_es(struct ast_channel *chan, time_t time, char *ints, char *lang, char *format, char *timezone) {