us_text.c 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. /*************************************************************************/
  2. /* */
  3. /* Language Technologies Institute */
  4. /* Carnegie Mellon University */
  5. /* Copyright (c) 2001 */
  6. /* All Rights Reserved. */
  7. /* */
  8. /* Permission is hereby granted, free of charge, to use and distribute */
  9. /* this software and its documentation without restriction, including */
  10. /* without limitation the rights to use, copy, modify, merge, publish, */
  11. /* distribute, sublicense, and/or sell copies of this work, and to */
  12. /* permit persons to whom this work is furnished to do so, subject to */
  13. /* the following conditions: */
  14. /* 1. The code must retain the above copyright notice, this list of */
  15. /* conditions and the following disclaimer. */
  16. /* 2. Any modifications must be clearly marked as such. */
  17. /* 3. Original authors' names are not deleted. */
  18. /* 4. The authors' names are not used to endorse or promote products */
  19. /* derived from this software without specific prior written */
  20. /* permission. */
  21. /* */
  22. /* CARNEGIE MELLON UNIVERSITY AND THE CONTRIBUTORS TO THIS WORK */
  23. /* DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING */
  24. /* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT */
  25. /* SHALL CARNEGIE MELLON UNIVERSITY NOR THE CONTRIBUTORS BE LIABLE */
  26. /* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES */
  27. /* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN */
  28. /* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, */
  29. /* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF */
  30. /* THIS SOFTWARE. */
  31. /* */
  32. /*************************************************************************/
  33. /* Author: Alan W Black (awb@cs.cmu.edu) */
  34. /* Date: January 2001 */
  35. /*************************************************************************/
  36. /* */
  37. /* US English text analysis functions */
  38. /* */
  39. /*************************************************************************/
  40. #include <ctype.h>
  41. #include "flite.h"
  42. #include "usenglish.h"
  43. #include "us_text.h"
  44. #include "cst_regex.h"
  45. static int text_splitable(const char *s,int i);
  46. static cst_val *state_name(const char *name,cst_item *t);
  47. /* compiled us regexes */
  48. #include "us_regexes.h"
  49. /* Note you need to also update the wandm regex in make_us_regeses too */
  50. static const char * const wandm_abbrevs[99][2] =
  51. {
  52. { "LB", "pounds" },
  53. { "LBS", "pounds" },
  54. { "lb", "pounds" },
  55. { "lbs", "pounds" },
  56. { "ft", "feet" },
  57. { "FT", "feet" },
  58. { "kg", "kilograms" },
  59. { "km", "kilometers" },
  60. { "cm", "centimeters" },
  61. { "mm", "millimeters" },
  62. { "ml", "milliliters" },
  63. { "oz", "ounces" },
  64. { "hz", "hertz" },
  65. { "Hz", "hertz" },
  66. { "HZ", "hertz" },
  67. { "KHz", "kilohertz" },
  68. { "MHz", "megahertz" },
  69. { "GHz", "gigahertz" },
  70. { "KB", "kilobytes" },
  71. { "GB", "gigabytes" },
  72. { "MB", "megabytes" },
  73. { "TB", "terabytes" },
  74. { NULL, NULL },
  75. };
  76. static const char * const eedwords[] = {
  77. "to",
  78. "can",
  79. "can't",
  80. "cannot",
  81. "cant",
  82. "could",
  83. "couldn't",
  84. "couldnt",
  85. "will",
  86. "shall",
  87. NULL};
  88. static int rex_like(const cst_item *t)
  89. {
  90. /* returns 1 if this is in a king like context */
  91. char *pn = cst_downcase(ffeature_string(t,"p.name"));
  92. char *ppn = cst_downcase(ffeature_string(t,"p.p.name"));
  93. int v = 0;
  94. if (cst_streq(pn,"louis") ||
  95. cst_streq(pn,"henry") ||
  96. cst_streq(pn,"charles") ||
  97. cst_streq(pn,"philip") ||
  98. cst_streq(pn,"george") ||
  99. cst_streq(pn,"edward") ||
  100. cst_streq(pn,"pius") ||
  101. cst_streq(pn,"william") ||
  102. cst_streq(pn,"richard") ||
  103. cst_streq(pn,"ptolemy") ||
  104. cst_streq(pn,"john") ||
  105. cst_streq(pn,"paul") ||
  106. cst_streq(pn,"peter") ||
  107. cst_streq(pn,"nicholas") ||
  108. cst_streq(pn,"frederick") ||
  109. cst_streq(pn,"james") ||
  110. cst_streq(pn,"alfonso") ||
  111. cst_streq(pn,"ivan") ||
  112. cst_streq(pn,"napolean") ||
  113. cst_streq(pn,"leo") ||
  114. cst_streq(pn,"gregory") ||
  115. cst_streq(pn,"catherine") ||
  116. cst_streq(pn,"alexandria") ||
  117. cst_streq(pn,"pierre") ||
  118. cst_streq(pn,"elizabeth") ||
  119. cst_streq(pn,"mary"))
  120. v = 1;
  121. else if (cst_streq(ppn,"king") ||
  122. cst_streq(ppn,"queen") ||
  123. cst_streq(ppn,"pope") ||
  124. cst_streq(ppn,"duke") ||
  125. cst_streq(ppn,"tsar") ||
  126. cst_streq(ppn,"emperor") ||
  127. cst_streq(ppn,"shah") ||
  128. cst_streq(ppn,"ceasar") ||
  129. cst_streq(ppn,"duchess") ||
  130. cst_streq(ppn,"tsarina") ||
  131. cst_streq(ppn,"empress") ||
  132. cst_streq(ppn,"baron") ||
  133. cst_streq(ppn,"baroness") ||
  134. cst_streq(ppn,"sultan") ||
  135. cst_streq(ppn,"count") ||
  136. cst_streq(ppn,"countess"))
  137. v = 1;
  138. cst_free(pn);
  139. cst_free(ppn);
  140. return v;
  141. }
  142. static int section_like(const cst_item *t)
  143. {
  144. /* returns 1 if this is in a king like context */
  145. char *pn = cst_downcase(ffeature_string(t,"p.name"));
  146. int v = 0;
  147. if (cst_streq(pn,"section") ||
  148. cst_streq(pn,"chapter") ||
  149. cst_streq(pn,"part") ||
  150. cst_streq(pn,"phrase") ||
  151. cst_streq(pn,"verse") ||
  152. cst_streq(pn,"scene") ||
  153. cst_streq(pn,"act") ||
  154. cst_streq(pn,"book") ||
  155. cst_streq(pn,"volume") ||
  156. cst_streq(pn,"chap") ||
  157. cst_streq(pn,"war") ||
  158. cst_streq(pn,"apollo") ||
  159. cst_streq(pn,"trek") ||
  160. cst_streq(pn,"fortran"))
  161. v = 1;
  162. cst_free(pn);
  163. return v;
  164. }
  165. cst_utterance *us_textanalysis(cst_utterance *u)
  166. {
  167. if (!feat_present(u->features, "tokentowords_func"))
  168. utt_set_feat(u, "tokentowords_func", itemfunc_val(us_tokentowords));
  169. return default_textanalysis(u);
  170. }
  171. static cst_val *us_tokentowords_one(cst_item *token, const char *name);
  172. cst_val *us_tokentowords(cst_item *token)
  173. {
  174. return us_tokentowords_one(token, item_feat_string(token, "name"));
  175. }
  176. static cst_val *add_break(cst_val *l)
  177. {
  178. /* add feature (break 1) to last item in this list */
  179. const cst_val *i;
  180. cst_val *t;
  181. cst_features *f;
  182. for (i=l; val_cdr(i); i=val_cdr(i));
  183. if (i) /* might be empty list */
  184. {
  185. f = new_features();
  186. feat_set_string(f,"break","1");
  187. t = cons_val(val_car(i),features_val(f));
  188. set_car((cst_val *)i,t);
  189. }
  190. return l;
  191. }
  192. static int contains_unicode_single_quote(const char *name)
  193. {
  194. static const char *unicode_single_quote = "’";
  195. int i;
  196. for (i=0; name[i]; i++)
  197. {
  198. /* No check if name is long enough as it'll have NULL before end */
  199. if ((name[i] == unicode_single_quote[0]) &&
  200. (name[i+1] == unicode_single_quote[1]) &&
  201. (name[i+2] == unicode_single_quote[2]))
  202. return TRUE;
  203. }
  204. return FALSE;
  205. }
  206. static char *map_unicode_single_quote(const char *name)
  207. {
  208. static const char *unicode_single_quote = "’";
  209. int i,j;
  210. char *aaa = cst_strdup(name); /* it'll always get shorter */
  211. for (i=0,j=0; name[i]; i++,j++)
  212. {
  213. if ((name[i] == unicode_single_quote[0]) &&
  214. (name[i+1] == unicode_single_quote[1]) &&
  215. (name[i+2] == unicode_single_quote[2]))
  216. {
  217. aaa[j] = '\'';
  218. i+=2;
  219. }
  220. else
  221. aaa[j] = name[i];
  222. }
  223. aaa[j] = '\0';
  224. return aaa;
  225. }
  226. static cst_val *us_tokentowords_one(cst_item *token, const char *name)
  227. {
  228. /* Return list of words that expand token/name */
  229. char *p, *aaa, *bbb, *ccc;
  230. int i,j,k,l;
  231. cst_val *r, *s, *ss;
  232. const cst_val *rr;
  233. const char *nsw = "";
  234. const char *ssml_alias = "";
  235. const char *token_name = "";
  236. cst_lexicon *lex;
  237. cst_utterance *utt;
  238. /* printf("token_name %s name %s\n",item_name(token),name); */
  239. /* FIXME: For SAPI and friends, any tokens with explicit
  240. pronunciations need to be passed through as-is. This should be
  241. done in the interface code rather than here once the
  242. tokentowords hook is accessible. AWB: no, they should set the
  243. nsw feature and this function should deal with it (doesn't yet though)*/
  244. if (item_feat_present(token,"phones"))
  245. return cons_val(string_val(name),NULL);
  246. if (item_feat_present(token,"nsw"))
  247. nsw = item_feat_string(token,"nsw");
  248. token_name = item_name(token);
  249. if ((item_feat_present(token,"ssml_alias")) &&
  250. (!cst_streq(token_name,name))) /* and we are not recursing */
  251. {
  252. /* SSML has given a substitute for this (and more) tokens */
  253. /* NOTE: the alias is not put through text normalization */
  254. ssml_alias = item_feat_string(token,"ssml_alias");
  255. if (cst_streq(ssml_alias,ffeature_string(token,"p.ssml_alias")))
  256. /* The first token gets the substitution */
  257. return NULL;
  258. else
  259. {
  260. return cons_val(string_val(ssml_alias),NULL);
  261. }
  262. }
  263. utt = item_utt(token);
  264. lex = val_lexicon(feat_val(utt->features,"lexicon"));
  265. if (cst_streq("1",get_param_string(item_feats(token),"ssml_comment","0")))
  266. r = NULL;
  267. else if ((cst_streq("a",name) || cst_streq("A",name)) &&
  268. ((item_next(token) == 0) ||
  269. (!cst_streq(name,item_name(token))) ||
  270. (!cst_streq("",ffeature_string(token,"punc")))))
  271. { /* if A is a sub part of a token, then its ey not ah */
  272. r = cons_val(string_val("_a"),0);
  273. }
  274. else if (cst_strlen(name) == 0)
  275. r = NULL;
  276. else if (cst_regex_match(dottedabbrevs,name))
  277. { /* X.X.X */
  278. aaa = cst_strdup(name);
  279. for (i=j=0; aaa[i]; i++)
  280. if (aaa[i] != '.')
  281. {
  282. aaa[j] = aaa[i];
  283. j++;
  284. }
  285. aaa[j] = '\0';
  286. r = en_exp_letters(aaa);
  287. cst_free(aaa);
  288. }
  289. else if (cst_regex_match(cst_rx_commaint,name))
  290. { /* 99,999,999 */
  291. aaa = cst_strdup(name);
  292. for (j=i=0; i < cst_strlen(name); i++)
  293. if (name[i] != ',')
  294. {
  295. aaa[j] = name[i];
  296. j++;
  297. }
  298. aaa[j] = '\0';
  299. r = en_exp_real(aaa);
  300. cst_free(aaa);
  301. }
  302. else if (cst_regex_match(sevenphonenumber,name))
  303. { /* 234-3434 telephone numbers */
  304. p=strchr(name,'-');
  305. aaa = cst_strdup(name);
  306. aaa[cst_strlen(name)-cst_strlen(p)] = '\0';
  307. bbb = cst_strdup(p+1);
  308. r = val_append(add_break(en_exp_digits(aaa)),
  309. en_exp_digits(bbb));
  310. cst_free(aaa);
  311. cst_free(bbb);
  312. }
  313. else if
  314. ((cst_regex_match(threedigits,name) &&
  315. ((!cst_regex_match(cst_rx_digits,ffeature_string(token,"p.name"))
  316. && cst_regex_match(threedigits,ffeature_string(token,"n.name"))
  317. && cst_regex_match(fourdigits,ffeature_string(token,"n.n.name"))) ||
  318. (cst_regex_match(sevenphonenumber,ffeature_string(token,"n.name"))) ||
  319. (!cst_regex_match(cst_rx_digits,ffeature_string(token,"p.p.name"))
  320. && cst_regex_match(threedigits,ffeature_string(token,"p.name"))
  321. && cst_regex_match(fourdigits,ffeature_string(token,"n.name"))))) ||
  322. (cst_regex_match(fourdigits,name) &&
  323. (!cst_regex_match(cst_rx_digits,ffeature_string(token,"n.name"))
  324. && cst_regex_match(threedigits,ffeature_string(token,"p.name"))
  325. && cst_regex_match(threedigits,ffeature_string(token,"p.p.name")))))
  326. {
  327. /* part of a telephone number */
  328. if (cst_streq("",ffeature_string(token,"punc")))
  329. item_set_string(token,"punc",",");
  330. r = add_break(en_exp_digits(name));
  331. }
  332. else if (cst_regex_match(numbertime,name))
  333. {
  334. p=strchr(name,':');
  335. aaa = cst_strdup(name);
  336. aaa[cst_strlen(name)-cst_strlen(p)] = '\0';
  337. bbb = cst_strdup(p+1);
  338. r = en_exp_number(aaa);
  339. if (!cst_streq("00",bbb))
  340. r = val_append(r,en_exp_id(bbb));
  341. /* r = add_break(r); */
  342. cst_free(aaa);
  343. cst_free(bbb);
  344. }
  345. else if (cst_regex_match(numbertimexm,name))
  346. {
  347. p=strchr(name,':');
  348. if (!p) p=strchr(name,'.');
  349. aaa = cst_strdup(name);
  350. aaa[cst_strlen(name)-cst_strlen(p)] = '\0';
  351. bbb = cst_strdup(p+1);
  352. bbb[2] = '\0';
  353. ccc = cst_strdup(p+3);
  354. r = en_exp_number(aaa);
  355. if (!cst_streq("00",bbb))
  356. r = val_append(r,en_exp_id(bbb));
  357. /* r = add_break(r); */
  358. r = val_append(r,en_exp_letters(ccc));
  359. cst_free(aaa);
  360. cst_free(bbb);
  361. cst_free(ccc);
  362. }
  363. else if (cst_regex_match(digits2dash,name))
  364. { /* 999-999-999 etc */
  365. bbb = cst_strdup(name);
  366. for (ss=0,aaa=p=bbb; *p; p++)
  367. {
  368. if (*p == '-')
  369. {
  370. *p = '\0';
  371. ss = cons_val(string_val(aaa),ss);
  372. aaa = p+1;
  373. }
  374. }
  375. ss = cons_val(string_val(aaa),ss);
  376. if ((val_length(ss) == 2) &&
  377. (atoi(val_string(val_car(val_cdr(ss)))) <
  378. atoi(val_string(val_car(ss))))) /* its a number range */
  379. {
  380. /* Should get 22-23 November, or 1998-1999 right */
  381. r =
  382. val_append(us_tokentowords_one(token,val_string(val_car(val_cdr(ss)))),
  383. cons_val(string_val("to"),
  384. us_tokentowords_one(token,val_string(val_car(ss)))));
  385. }
  386. else
  387. { /* Its just a bunch of ids */
  388. r = 0;
  389. for (rr=ss; rr; rr=val_cdr(rr))
  390. {
  391. r = val_append(
  392. add_break(en_exp_digits(val_string(val_car(rr)))),r);
  393. }
  394. }
  395. delete_val(ss);
  396. cst_free(bbb);
  397. }
  398. else if (cst_regex_match(cst_rx_digits,name))
  399. { /* string of digits (use cart to disambiguate) */
  400. if (cst_streq("nide",nsw))
  401. r = en_exp_id(name);
  402. else {
  403. const cst_val *tv;
  404. const char *ts;
  405. char *rname;
  406. rname = cst_strdup(item_feat_string(token,"name"));
  407. if (cst_streq(name,rname))
  408. tv = cart_interpret(token,&us_nums_cart);
  409. else
  410. { /* in a recursive call */
  411. item_set_string(token,"name",name);
  412. tv = cart_interpret(token,&us_nums_cart);
  413. item_set_string(token,"name",rname);
  414. }
  415. cst_free(rname);
  416. ts = val_string(tv);
  417. if (cst_streq(ts,"ordinal"))
  418. r = en_exp_ordinal(name);
  419. else if (cst_streq(ts,"digits"))
  420. r = en_exp_digits(name);
  421. else if (cst_streq(ts,"year"))
  422. r = en_exp_id(name);
  423. else
  424. r = en_exp_number(name);
  425. }
  426. }
  427. else if (cst_regex_match(romannums,name))
  428. { /* Roman numerals */
  429. if (cst_streq("",ffeature_string(token,"p.punc")))
  430. { /* no preceeding punc */
  431. char n[10];
  432. cst_sprintf(n,"%d",en_exp_roman(name));
  433. if (rex_like(token))
  434. r = cons_val(string_val("the"),
  435. en_exp_ordinal(n));
  436. else if (section_like(token))
  437. r = en_exp_number(n);
  438. else
  439. r = en_exp_letters(name);
  440. }
  441. else
  442. r = en_exp_letters(name);
  443. }
  444. else if (cst_regex_match(drst,name))
  445. { /* St Andrew's St, Dr King Dr */
  446. const char *street;
  447. const char *saint;
  448. if ((name[0] == 's') || (name[0] == 'S'))
  449. {
  450. street = "street";
  451. saint = "saint";
  452. }
  453. else
  454. {
  455. street = "drive";
  456. saint = "doctor";
  457. }
  458. if ((item_next(token) == 0) ||
  459. strchr(item_feat_string(token,"punc"),','))
  460. r = cons_val(string_val(street),NULL);
  461. else if (strchr(ffeature_string(token,"punc"),','))
  462. r = cons_val(string_val(saint),NULL);
  463. else
  464. {
  465. const char *pname = ffeature_string(token,"p.name");
  466. const char *nname = ffeature_string(token,"n.name");
  467. if ((pname[0] >= 'A') && (pname[0] <= 'Z') &&
  468. (nname[0] >= 'a') && (nname[0] <= 'z'))
  469. r = cons_val(string_val(street),NULL);
  470. else if ((pname[0] >= '0') && (pname[0] <= '9') &&
  471. (nname[0] >= 'a') && (nname[0] <= 'z'))
  472. r = cons_val(string_val(street),NULL);
  473. else if ((pname[0] >= 'a') && (pname[0] <= 'z') &&
  474. (nname[0] >= 'A') && (nname[0] <= 'Z'))
  475. r = cons_val(string_val(saint),NULL);
  476. else if (cst_streq(ffeature_string(token,"n.whitespace")," "))
  477. r = cons_val(string_val(saint),NULL);
  478. else
  479. r = cons_val(string_val(street),NULL);
  480. }
  481. if (cst_streq(item_feat_string(token,"punc"),"."))
  482. item_set_string(token,"punc","");
  483. }
  484. else if (cst_streq(name,"Mr"))
  485. {
  486. item_set_string(token,"punc","");
  487. r = cons_val(string_val("mister"),NULL);
  488. }
  489. else if (cst_streq(name,"Mrs"))
  490. {
  491. item_set_string(token,"punc","");
  492. r = cons_val(string_val("missus"),NULL);
  493. }
  494. else if ((cst_streq(name,"read")) ||
  495. (cst_streq(name,"lead")))
  496. { /* checking WSJ examples, this seems a quick and easy way to */
  497. /* get many of these correct */
  498. const char *pname = ffeature_string(token,"p.name");
  499. for (i=0; eedwords[i]; i++)
  500. if (cst_streq(pname,eedwords[i]))
  501. break;
  502. if (eedwords[i])
  503. { /* reed or leed */
  504. if (name[0] == 'r')
  505. r = cons_val(string_val("reed"),NULL);
  506. else
  507. r = cons_val(string_val("leed"),NULL);
  508. }
  509. else /* red or led */
  510. {
  511. if (name[0] == 'r')
  512. r = cons_val(string_val("red"),NULL);
  513. else
  514. r = cons_val(string_val("led"),NULL);
  515. }
  516. }
  517. else if (cst_streq(name,"am") || cst_streq(name,"AM"))
  518. {
  519. if (!cst_streq(name,item_name(token)))
  520. r = en_exp_letters(name);
  521. else if (item_prev(token) &&
  522. (cst_regex_match(numbertime,ffeature_string(token,"p.name")) ||
  523. cst_regex_match(cst_rx_digits,ffeature_string(token,"p.name"))))
  524. r = en_exp_letters(name);
  525. else
  526. r = cons_val(string_val(name),NULL);
  527. }
  528. else if ((cst_strlen(name) == 1) &&
  529. (name[0] >= 'A') &&
  530. (name[0] <= 'Z') &&
  531. (cst_streq(" ",ffeature_string(token,"n.whitespace"))) &&
  532. (ffeature_string(token,"n.name")[0] >= 'A') &&
  533. (ffeature_string(token,"n.name")[0] <= 'Z'))
  534. {
  535. item_set_string(token,"punc","");
  536. aaa = cst_downcase(name);
  537. if (cst_streq(aaa,"a"))
  538. r = cons_val(string_val("_a"),0);
  539. else
  540. r = cons_val(string_val(aaa),0);
  541. cst_free(aaa);
  542. }
  543. else if (cst_regex_match(cst_rx_double,name))
  544. { /* real numbers */
  545. r = en_exp_real(name);
  546. }
  547. else if (cst_regex_match(ordinal_number,name))
  548. { /* explicit ordinals */
  549. aaa = cst_strdup(name);
  550. aaa[cst_strlen(name)-2] = '\0';
  551. r = en_exp_ordinal(aaa);
  552. cst_free(aaa);
  553. }
  554. else if ((cst_regex_match(illion,name)) &&
  555. (cst_regex_match(usmoney,ffeature_string(token,"p.name"))))
  556. {
  557. r = cons_val(string_val(name),
  558. cons_val(string_val("dollars"),NULL));
  559. }
  560. else if (cst_regex_match(usmoney,name))
  561. {
  562. /* US money */
  563. /* printf("money, money, money %s\n", name); */
  564. p = strchr(name,'.');
  565. if (cst_regex_match(illion,ffeature_string(token,"n.name")))
  566. { /* carl sagan's billions and billions */
  567. r = en_exp_real(&name[1]);
  568. }
  569. else if (!p)
  570. {
  571. aaa = cst_strdup(&name[1]);
  572. if (cst_streq("1",aaa))
  573. r = cons_val(string_val("dollar"),NULL);
  574. else
  575. r = cons_val(string_val("dollars"),NULL);
  576. r = val_append(us_tokentowords_one(token,aaa),r);
  577. cst_free(aaa);
  578. }
  579. else if ((cst_strlen(p) == 1) || (cst_strlen(p) > 3))
  580. { /* simply read as mumble point mumble */
  581. r = val_append(en_exp_real(&name[1]),
  582. cons_val(string_val("dollars"),NULL));
  583. }
  584. else
  585. {
  586. aaa = cst_strdup(name);
  587. aaa[cst_strlen(name)-cst_strlen(p)] = '\0';
  588. for (i=j=0; aaa[i] != '\0'; i++)
  589. {
  590. if (aaa[i] != ',')
  591. {
  592. aaa[j] = aaa[i];
  593. j++;
  594. }
  595. }
  596. aaa[j] = '\0';
  597. if (cst_streq("00",p+1))
  598. r = 0;
  599. else if (cst_streq("01",p+1))
  600. r = val_append(en_exp_number(p+1),
  601. cons_val(string_val("cent"),NULL));
  602. else
  603. r = val_append(en_exp_number(p+1),
  604. cons_val(string_val("cents"),NULL));
  605. if (cst_streq("1",aaa+1))
  606. r = cons_val(string_val("dollar"),r);
  607. else
  608. r = cons_val(string_val("dollars"),r);
  609. r = val_append(en_exp_number(aaa+1),r);
  610. cst_free(aaa);
  611. }
  612. }
  613. else if (name[cst_strlen(name)-1] == '%')
  614. {
  615. aaa = cst_strdup(name);
  616. aaa[cst_strlen(aaa)-1] = '\0';
  617. r = val_append(us_tokentowords_one(token,aaa),
  618. cons_val(string_val("per"),
  619. cons_val(string_val("cent"),NULL)));
  620. cst_free(aaa);
  621. }
  622. else if (cst_regex_match(numess,name))
  623. { /* 60s and 7s and 9s */
  624. aaa = cst_strdup(name);
  625. aaa[cst_strlen(name)-1] = '\0';
  626. r = val_append(us_tokentowords_one(token,aaa),
  627. cons_val(string_val("'s"),0));
  628. cst_free(aaa);
  629. }
  630. else if (contains_unicode_single_quote(name))
  631. {
  632. /* A single quote is sometimes rendered as unicode "’" */
  633. /* so we map it back to an ascii single quote ' */
  634. aaa = map_unicode_single_quote(name);
  635. r = us_tokentowords_one(token, aaa);
  636. cst_free(aaa);
  637. return r;
  638. }
  639. else if ((p=(cst_strrchr(name,'\''))))
  640. {
  641. static const char * const pc[] = { "'s", "'ll", "'ve", "'d", NULL };
  642. bbb = cst_downcase(p);
  643. if (cst_member_string(bbb, pc))
  644. {
  645. aaa = cst_strdup(name);
  646. aaa[cst_strlen(name)-cst_strlen(p)] = '\0';
  647. r = val_append(us_tokentowords_one(token,aaa),
  648. cons_val(string_val(bbb),0));
  649. cst_free(aaa);
  650. }
  651. else if (cst_streq(p,"'tve")) /* admittedly rare and weird */
  652. {
  653. aaa = cst_strdup(name);
  654. aaa[cst_strlen(name)-cst_strlen(p)+2] = '\0';
  655. r = val_append(us_tokentowords_one(token,aaa),
  656. cons_val(string_val("'ve"),0));
  657. cst_free(aaa);
  658. }
  659. else
  660. {
  661. aaa = cst_strdup(name);
  662. strcpy(&aaa[cst_strlen(name)-cst_strlen(p)],p+1);
  663. r = us_tokentowords_one(token,aaa);
  664. cst_free(aaa);
  665. }
  666. cst_free(bbb);
  667. }
  668. else if ((cst_regex_match(digitsslashdigits,name)) &&
  669. (cst_streq(name,item_name(token))))
  670. { /* might be fraction, or not */
  671. p=strchr(name,'/');
  672. aaa = cst_strdup(name);
  673. aaa[cst_strlen(name)-cst_strlen(p)] = '\0';
  674. bbb = cst_strdup(p+1);
  675. if ((cst_streq("1",aaa)) && (cst_streq("2",bbb)))
  676. r = cons_val(string_val("a"),
  677. cons_val(string_val("half"),0));
  678. else if (atoi(aaa) < (atoi(bbb)))
  679. {
  680. r = val_append(en_exp_number(aaa),
  681. en_exp_ordinal(bbb));
  682. if (atoi(aaa) > 1)
  683. r = val_append(r,cons_val(string_val("'s"),0));
  684. }
  685. else
  686. r = val_append(en_exp_number(aaa),
  687. cons_val(string_val("slash"),
  688. en_exp_number(bbb)));
  689. if ((cst_regex_match(cst_rx_digits,ffeature_string(token,"p.name")))
  690. && (item_prev(token))) /* don't mistake "0" as a number */
  691. r = cons_val(string_val("and"),r);
  692. cst_free(aaa);
  693. cst_free(bbb);
  694. }
  695. else if ((p=(strchr(name,'-'))))
  696. { /* aaa-bbb */
  697. aaa = cst_strdup(name);
  698. aaa[cst_strlen(name)-cst_strlen(p)] = '\0';
  699. bbb = cst_strdup(p+1);
  700. if (cst_regex_match(cst_rx_digits,aaa) &&
  701. cst_regex_match(cst_rx_digits,bbb))
  702. {
  703. ccc = cst_strdup(name);
  704. item_set_string(token,"name",bbb);
  705. r = us_tokentowords_one(token,bbb);
  706. item_set_string(token,"name",aaa);
  707. r = val_append(us_tokentowords_one(token,aaa),
  708. cons_val(string_val("to"),r));
  709. item_set_string(token,"name",ccc);
  710. cst_free(ccc);
  711. }
  712. else
  713. r = val_append(us_tokentowords_one(token,aaa),
  714. us_tokentowords_one(token,bbb));
  715. cst_free(aaa);
  716. cst_free(bbb);
  717. }
  718. else if (cst_regex_match(wandm,name))
  719. { /* weights and measures */
  720. for (j=cst_strlen(name)-1; j > 0; j--)
  721. if (cst_strchr("0123456789",name[j]))
  722. break;
  723. j += 1;
  724. for (i=0; wandm_abbrevs[i][0]; i++)
  725. if (cst_streq(name+j,wandm_abbrevs[i][0]))
  726. break;
  727. aaa = cst_strdup(name);
  728. aaa[j] = '\0';
  729. /* remove any commas */
  730. for (k=0,l=0; aaa[l]; k++,l++)
  731. {
  732. if (aaa[l] == ',') l++;
  733. aaa[k] = aaa[l];
  734. }
  735. aaa[k] = '\0';
  736. if (!wandm_abbrevs[i][0]) /* didn't find an expansion */
  737. r = val_append(en_exp_number(aaa),
  738. us_tokentowords_one(token,name+j));
  739. else
  740. r = val_append(en_exp_number(aaa),
  741. cons_val(string_val(wandm_abbrevs[i][1]),NULL));
  742. cst_free(aaa);
  743. }
  744. else if ((cst_strlen(name) > 1) && (!cst_regex_match(cst_rx_alpha,name)))
  745. { /* its not just alphas */
  746. for (i=0; name[i] != '\0'; i++)
  747. if (text_splitable(name,i))
  748. break;
  749. aaa = cst_strdup(name);
  750. aaa[i+1] = '\0';
  751. bbb = cst_strdup(&name[i+1]);
  752. item_set_string(token,"nsw","nide");
  753. r = val_append(us_tokentowords_one(token,aaa),
  754. us_tokentowords_one(token,bbb));
  755. cst_free(aaa);
  756. cst_free(bbb);
  757. }
  758. else if ((s = state_name(name,token)))
  759. {
  760. r = s;
  761. }
  762. else if ((cst_strlen(name) > 1) &&
  763. (cst_regex_match(cst_rx_alpha,name)) &&
  764. (!in_lex(lex,name,NULL,NULL)) && // AUP: Added 4th argument (voice feats) as NULL, needs to be revisited later.
  765. (!us_aswd(name)))
  766. /* Still not quiet right, if there is a user_lex we need to check */
  767. /* it too -- but user_lex isn't user setable yet */
  768. /* Need common exception list */
  769. /* unpronouncable list of alphas */
  770. r = en_exp_letters(name);
  771. /* buckets of other stuff missing */
  772. else /* just a word */
  773. {
  774. aaa = cst_downcase(name);
  775. r = cons_val(string_val(aaa),0);
  776. cst_free(aaa);
  777. }
  778. return r;
  779. }
  780. static int text_splitable(const char *s,int i)
  781. {
  782. /* should token be split after this */
  783. if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",s[i]) &&
  784. strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",s[i+1]))
  785. return FALSE;
  786. else if (strchr("0123456789",s[i]) &&
  787. strchr("0123456789",s[i+1]))
  788. return FALSE;
  789. else
  790. return TRUE;
  791. }
  792. static const char * const states[99][5] =
  793. {
  794. { "AL", "ambiguous", "alabama" , NULL, NULL },
  795. { "Al", "ambiguous", "alabama" , NULL, NULL },
  796. { "Ala", "", "alabama" , NULL, NULL },
  797. { "AK", "", "alaska" , NULL, NULL },
  798. { "Ak", "", "alaska" , NULL, NULL },
  799. { "AZ", "", "arizona" , NULL, NULL },
  800. { "Az", "", "arizona" , NULL, NULL },
  801. { "CA", "", "california" , NULL, NULL },
  802. { "Ca", "", "california" , NULL, NULL },
  803. { "Cal", "ambiguous", "california" , NULL, NULL },
  804. { "Calif", "", "california" , NULL, NULL },
  805. { "CO", "ambiguous", "colorado" , NULL, NULL },
  806. { "Co", "ambiguous", "colorado" , NULL, NULL },
  807. { "Colo", "", "colorado" , NULL, NULL },
  808. { "DC", "", "d" , "c", NULL },
  809. { "DE", "", "delaware" , NULL, NULL },
  810. { "De", "ambiguous", "delaware" , NULL, NULL },
  811. { "Del", "ambiguous", "delaware" , NULL, NULL },
  812. { "FL", "", "florida" , NULL, NULL },
  813. { "Fl", "ambiguous", "florida" , NULL, NULL },
  814. { "Fla", "", "florida" , NULL, NULL },
  815. { "GA", "", "georgia" , NULL, NULL },
  816. { "Ga", "", "georgia" , NULL, NULL },
  817. { "HI", "", "hawaii" , NULL, NULL },
  818. { "Hi", "ambiguous", "hawaii" , NULL, NULL },
  819. { "IA", "", "iowa" , NULL, NULL },
  820. { "Ia", "ambiguous", "iowa" , NULL, NULL },
  821. { "Ind", "ambiguous", "indiana" , NULL, NULL },
  822. { "ID", "ambiguous", "idaho" , NULL, NULL },
  823. { "IL", "ambiguous", "illinois" , NULL, NULL },
  824. { "Il", "ambiguous", "illinois" , NULL, NULL },
  825. { "ILL", "ambiguous", "illinois" , NULL, NULL },
  826. { "KS", "", "kansas" , NULL, NULL },
  827. { "Ks", "", "kansas" , NULL, NULL },
  828. { "Kans", "", "kansas" , NULL, NULL },
  829. { "KY", "ambiguous", "kentucky" , NULL, NULL },
  830. { "Ky", "ambiguous", "kentucky" , NULL, NULL },
  831. { "LA", "ambiguous", "louisiana" , NULL, NULL },
  832. { "La", "ambiguous", "louisiana" , NULL, NULL },
  833. { "Lou", "ambiguous", "louisiana" , NULL, NULL },
  834. { "Lous", "ambiguous", "louisiana" , NULL, NULL },
  835. { "MA", "ambiguous", "massachusetts" , NULL, NULL },
  836. { "Mass", "ambiguous", "massachusetts" , NULL, NULL },
  837. { "Ma", "ambiguous", "massachusetts" , NULL, NULL },
  838. { "MD", "ambiguous", "maryland" , NULL, NULL },
  839. { "Md", "ambiguous", "maryland" , NULL, NULL },
  840. { "ME", "ambiguous", "maine" , NULL, NULL },
  841. { "Me", "ambiguous", "maine" , NULL, NULL },
  842. { "MI", "", "michigan" , NULL, NULL },
  843. { "Mi", "ambiguous", "michigan" , NULL, NULL },
  844. { "Mich", "ambiguous", "michigan" , NULL, NULL },
  845. { "MN", "ambiguous", "minnestota" , NULL, NULL },
  846. { "Minn", "ambiguous", "minnestota" , NULL, NULL },
  847. { "MS", "ambiguous", "mississippi" , NULL, NULL },
  848. { "Miss", "ambiguous", "mississippi" , NULL, NULL },
  849. { "MT", "ambiguous", "montanna" , NULL, NULL },
  850. { "Mt", "ambiguous", "montanna" , NULL, NULL },
  851. { "MO", "ambiguous", "missouri" , NULL, NULL },
  852. { "Mo", "ambiguous", "missouri" , NULL, NULL },
  853. { "NC", "ambiguous", "north" , "carolina", NULL },
  854. { "ND", "ambiguous", "north" , "dakota", NULL },
  855. { "NE", "ambiguous", "nebraska" , NULL, NULL },
  856. { "Ne", "ambiguous", "nebraska" , NULL, NULL },
  857. { "Neb", "ambiguous", "nebraska" , NULL, NULL },
  858. { "NH", "ambiguous", "new" , "hampshire", NULL },
  859. { "NV", "", "nevada" , NULL, NULL },
  860. { "Nev", "", "nevada" , NULL, NULL },
  861. { "NY", "", "new" , "york", NULL },
  862. { "OH", "ambiguous", "ohio" , NULL, NULL },
  863. { "OK", "ambiguous", "oklahoma" , NULL, NULL },
  864. { "Okla", "", "oklahoma" , NULL, NULL },
  865. { "OR", "ambiguous", "oregon" , NULL, NULL },
  866. { "Or", "ambiguous", "oregon" , NULL, NULL },
  867. { "Ore", "ambiguous", "oregon" , NULL, NULL },
  868. { "PA", "ambiguous", "pennsylvania" , NULL, NULL },
  869. { "Pa", "ambiguous", "pennsylvania" , NULL, NULL },
  870. { "Penn", "ambiguous", "pennsylvania" , NULL, NULL },
  871. { "RI", "ambiguous", "rhode" , "island", NULL },
  872. { "SC", "ambiguous", "south" , "carlolina", NULL },
  873. { "SD", "ambiguous", "south" , "dakota", NULL },
  874. { "TN", "ambiguous", "tennesee" , NULL, NULL },
  875. { "Tn", "ambiguous", "tennesee" , NULL, NULL },
  876. { "Tenn", "ambiguous", "tennesee" , NULL, NULL },
  877. { "TX", "ambiguous", "texas" , NULL, NULL },
  878. { "Tx", "ambiguous", "texas" , NULL, NULL },
  879. { "Tex", "ambiguous", "texas" , NULL, NULL },
  880. { "UT", "ambiguous", "utah" , NULL, NULL },
  881. { "VA", "ambiguous", "virginia" , NULL, NULL },
  882. { "WA", "ambiguous", "washington" , NULL, NULL },
  883. { "Wa", "ambiguous", "washington" , NULL, NULL },
  884. { "Wash", "ambiguous", "washington" , NULL, NULL },
  885. { "WI", "ambiguous", "wisconsin" , NULL, NULL },
  886. { "Wi", "ambiguous", "wisconsin" , NULL, NULL },
  887. { "WV", "ambiguous", "west" , "virginia", NULL },
  888. { "WY", "ambiguous", "wyoming" , NULL, NULL },
  889. { "Wy", "ambiguous", "wyoming" , NULL, NULL },
  890. { "Wyo", "", "wyoming" , NULL, NULL },
  891. { "PR", "ambiguous", "puerto" , "rico", NULL },
  892. { NULL, NULL, "puerto" , "rico", NULL }
  893. };
  894. static cst_val *state_name(const char *name,cst_item *t)
  895. {
  896. int s,j;
  897. int do_it = 0;
  898. cst_val *r = 0;
  899. for (s=0; states[s][0]; s++)
  900. {
  901. if (cst_streq(states[s][0],name))
  902. {
  903. if (cst_streq(states[s][1],"ambiguous"))
  904. {
  905. const char *pname = ffeature_string(t,"p.name");
  906. const char *nname = ffeature_string(t,"n.name");
  907. /* previous name is capitalized */
  908. if (((strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ",pname[0])) &&
  909. (cst_strlen(pname) > 2) &&
  910. (cst_regex_match(cst_rx_alpha,pname))) &&
  911. ((strchr("abcdefghijklmnopqrstuvwxyz",nname[0])) ||
  912. (item_next(t) == 0) ||
  913. (cst_streq(".",item_feat_string(t,"punc"))) ||
  914. (((cst_strlen(nname) == 5 || (cst_strlen(nname) == 10)) &&
  915. cst_regex_match(cst_rx_digits,nname)))))
  916. do_it = 1;
  917. else
  918. do_it = 0;
  919. }
  920. else
  921. do_it = 1;
  922. if (do_it)
  923. {
  924. for (j=2; states[s][j]; j++)
  925. r = cons_val(string_val(states[s][j]),r);
  926. return val_reverse(r);
  927. }
  928. }
  929. }
  930. return r;
  931. }