2 * Portions Copyright 2001-2003 Sun Microsystems, Inc.
3 * Portions Copyright 1999-2001 Language Technologies Institute,
4 * Carnegie Mellon University.
5 * All Rights Reserved. Use is subject to license terms.
7 * See the file "license.terms" for information on usage and
8 * redistribution of this file, and for a DISCLAIMER OF ALL
11 package com.sun.speech.freetts.en.us;
13 import com.sun.speech.freetts.FeatureSet;
14 import com.sun.speech.freetts.Item;
15 import com.sun.speech.freetts.util.Utilities;
18 * Expands Strings containing digits characters into
19 * a list of words representing those digits.
21 * It translates the following code from flite:
22 * <code>lang/usEnglish/us_expand.c</code>
24 public class NumberExpander {
26 private static final String[] digit2num = {
38 private static final String[] digit2teen = {
39 "ten", /* shouldn't get called */
50 private static final String[] digit2enty = {
51 "zero", /* shouldn't get called */
62 private static final String[] ord2num = {
74 private static final String[] ord2teen = {
75 "tenth", /* shouldn't get called */
86 private static final String[] ord2enty = {
87 "zeroth", /* shouldn't get called */
102 private NumberExpander() {
107 * Expands a digit string into a list of English words of those digits.
108 * For example, "1234" expands to "one two three four"
110 * @param numberString the digit string to expand.
111 * @param wordRelation words are added to this Relation
113 public static void expandNumber(String numberString,
114 WordRelation wordRelation) {
115 int numDigits = numberString.length();
117 if (numDigits == 0) {
118 // wordRelation = null;
119 } else if (numDigits == 1) {
120 expandDigits(numberString, wordRelation);
121 } else if (numDigits == 2) {
122 expand2DigitNumber(numberString, wordRelation);
123 } else if (numDigits == 3) {
124 expand3DigitNumber(numberString, wordRelation);
125 } else if (numDigits < 7) {
126 expandBelow7DigitNumber(numberString, wordRelation);
127 } else if (numDigits < 10) {
128 expandBelow10DigitNumber(numberString, wordRelation);
129 } else if (numDigits < 13) {
130 expandBelow13DigitNumber(numberString, wordRelation);
132 expandDigits(numberString, wordRelation);
138 * Expands a two-digit string into a list of English words.
140 * @param numberString the string which is the number to expand
141 * @param wordRelation words are added to this Relation
143 private static void expand2DigitNumber(String numberString,
144 WordRelation wordRelation) {
145 if (numberString.charAt(0) == '0') {
146 // numberString is "0X"
147 if (numberString.charAt(1) == '0') {
148 // numberString is "00", do nothing
150 // numberString is "01", "02" ...
151 String number = digit2num[numberString.charAt(1)-'0'];
152 wordRelation.addWord(number);
154 } else if (numberString.charAt(1) == '0') {
155 // numberString is "10", "20", ...
156 String number = digit2enty[numberString.charAt(0)-'0'];
157 wordRelation.addWord(number);
158 } else if (numberString.charAt(0) == '1') {
159 // numberString is "11", "12", ..., "19"
160 String number = digit2teen[numberString.charAt(1)-'0'];
161 wordRelation.addWord(number);
163 // numberString is "2X", "3X", ...
164 String enty = digit2enty[numberString.charAt(0)-'0'];
165 wordRelation.addWord(enty);
166 expandDigits(numberString.substring(1,numberString.length()),
173 * Expands a three-digit string into a list of English words.
175 * @param numberString the string which is the number to expand
176 * @param wordRelation words are added to this Relation
178 private static void expand3DigitNumber(String numberString,
179 WordRelation wordRelation) {
180 if (numberString.charAt(0) == '0') {
181 expandNumberAt(numberString, 1, wordRelation);
183 String hundredDigit = digit2num[numberString.charAt(0)-'0'];
184 wordRelation.addWord(hundredDigit);
185 wordRelation.addWord("hundred");
186 expandNumberAt(numberString, 1, wordRelation);
192 * Expands a string that is a 4 to 6 digits number into a list
193 * of English words. For example, "333000" into "three hundred
194 * and thirty-three thousand".
196 * @param numberString the string which is the number to expand
197 * @param wordRelation words are added to this Relation
199 private static void expandBelow7DigitNumber(String numberString,
200 WordRelation wordRelation) {
201 expandLargeNumber(numberString, "thousand", 3, wordRelation);
206 * Expands a string that is a 7 to 9 digits number into a list
207 * of English words. For example, "19000000" into nineteen million.
209 * @param numberString the string which is the number to expand
210 * @param wordRelation words are added to this Relation
212 private static void expandBelow10DigitNumber(String numberString,
213 WordRelation wordRelation) {
214 expandLargeNumber(numberString, "million", 6, wordRelation);
219 * Expands a string that is a 10 to 12 digits number into a list
220 * of English words. For example, "27000000000" into twenty-seven
223 * @param numberString the string which is the number to expand
224 * @param wordRelation words are added to this Relation
226 private static void expandBelow13DigitNumber(String numberString,
227 WordRelation wordRelation) {
228 expandLargeNumber(numberString, "billion", 9, wordRelation);
233 * Expands a string that is a number longer than 3 digits into a list
234 * of English words. For example, "1000" into one thousand.
236 * @param numberString the string which is the number to expand
237 * @param order either "thousand", "million", or "billion"
238 * @param numberZeroes the number of zeroes, depending on the order, so
239 * its either 3, 6, or 9
240 * @param wordRelation words are added to this Relation
242 private static void expandLargeNumber(String numberString,
245 WordRelation wordRelation) {
246 int numberDigits = numberString.length();
248 // parse out the prefix, e.g., "113" in "113,000"
249 int i = numberDigits - numberZeroes;
250 String part = numberString.substring(0, i);
252 // get how many thousands/millions/billions
253 Item oldTail = wordRelation.getTail();
255 expandNumber(part, wordRelation);
257 if (wordRelation.getTail() == oldTail) {
258 expandNumberAt(numberString, i, wordRelation);
260 wordRelation.addWord(order);
261 expandNumberAt(numberString, i, wordRelation);
267 * Returns the number string list of the given string starting at
268 * the given index. E.g., expandNumberAt("1100", 1) gives "one hundred"
270 * @param numberString the string which is the number to expand
271 * @param startIndex the starting position
272 * @param wordRelation words are added to this Relation
274 private static void expandNumberAt(String numberString,
276 WordRelation wordRelation) {
277 expandNumber(numberString.substring(startIndex,numberString.length()),
283 * Expands given token to list of words pronouncing it as digits
285 * @param numberString the string which is the number to expand
286 * @param wordRelation words are added to this Relation
288 public static void expandDigits(String numberString,
289 WordRelation wordRelation) {
290 int numberDigits = numberString.length();
291 for (int i = 0; i < numberDigits; i++) {
292 char digit = numberString.charAt(i);
293 if (isDigit(digit)) {
294 wordRelation.addWord(digit2num[numberString.charAt(i)-'0']);
296 wordRelation.addWord("umpty");
303 * Expands the digit string of an ordinal number.
305 * @param rawNumberString the string which is the number to expand
306 * @param wordRelation words are added to this Relation
308 public static void expandOrdinal(String rawNumberString,
309 WordRelation wordRelation) {
310 // remove all ','s from the raw number string
311 String numberString = Utilities.deleteChar(rawNumberString, ',');
313 expandNumber(numberString, wordRelation);
315 // get the last in the list of number strings
316 Item lastItem = wordRelation.getTail();
318 if (lastItem != null) {
320 FeatureSet featureSet = lastItem.getFeatures();
321 String lastNumber = featureSet.getString("name");
322 String ordinal = findMatchInArray(lastNumber, digit2num, ord2num);
324 if (ordinal == null) {
325 ordinal = findMatchInArray(lastNumber, digit2teen, ord2teen);
327 if (ordinal == null) {
328 ordinal = findMatchInArray(lastNumber, digit2enty, ord2enty);
331 if (lastNumber.equals("hundred")) {
332 ordinal = "hundredth";
333 } else if (lastNumber.equals("thousand")) {
334 ordinal = "thousandth";
335 } else if (lastNumber.equals("billion")) {
336 ordinal = "billionth";
339 // if there was an ordinal, set the last element of the list
340 // to that ordinal; otherwise, don't do anything
341 if (ordinal != null) {
342 wordRelation.setLastWord(ordinal);
349 * Finds a match of the given string in the given array,
350 * and returns the element at the same index in the returnInArray
352 * @param strToMatch the string to match
353 * @param matchInArray the source array
354 * @param returnInArray the return array
356 * @return an element in returnInArray, or <code>null</code>
357 * if a match is not found
359 private static String findMatchInArray(String strToMatch,
360 String[] matchInArray,
361 String[] returnInArray) {
362 for (int i = 0; i < matchInArray.length; i++) {
363 if (strToMatch.equals(matchInArray[i])) {
364 if (i < returnInArray.length) {
365 return returnInArray[i];
376 * Expands the given number string as pairs as in years or IDs
378 * @param numberString the string which is the number to expand
379 * @param wordRelation words are added to this Relation
381 public static void expandID(String numberString, WordRelation wordRelation) {
383 int numberDigits = numberString.length();
385 if ((numberDigits == 4) &&
386 (numberString.charAt(2) == '0') &&
387 (numberString.charAt(3) == '0')) {
388 if (numberString.charAt(1) == '0') { // e.g. 2000, 3000
389 expandNumber(numberString, wordRelation);
391 expandNumber(numberString.substring(0,2), wordRelation);
392 wordRelation.addWord("hundred");
394 } else if ((numberDigits == 2) && (numberString.charAt(0) == '0')) {
395 wordRelation.addWord("oh");
396 expandDigits(numberString.substring(1,2), wordRelation);
397 } else if ((numberDigits == 4 &&
398 numberString.charAt(1) == '0') ||
400 expandNumber(numberString, wordRelation);
401 } else if (numberDigits % 2 == 1) {
402 String firstDigit = digit2num[numberString.charAt(0)-'0'];
403 wordRelation.addWord(firstDigit);
404 expandID(numberString.substring(1,numberDigits), wordRelation);
406 expandNumber(numberString.substring(0,2), wordRelation);
407 expandID(numberString.substring(2,numberDigits), wordRelation);
413 * Expands the given number string as a real number.
415 * @param numberString the string which is the real number to expand
416 * @param wordRelation words are added to this Relation
418 public static void expandReal(String numberString, WordRelation wordRelation) {
420 int stringLength = numberString.length();
423 if (numberString.charAt(0) == '-') {
424 // negative real numbers
425 wordRelation.addWord("minus");
426 expandReal(numberString.substring(1, stringLength), wordRelation);
427 } else if (numberString.charAt(0) == '+') {
428 // prefixed with a '+'
429 wordRelation.addWord("plus");
430 expandReal(numberString.substring(1, stringLength), wordRelation);
431 } else if ((position = numberString.indexOf('e')) != -1 ||
432 (position = numberString.indexOf('E')) != -1) {
433 // numbers with 'E' or 'e'
434 expandReal(numberString.substring(0, position), wordRelation);
435 wordRelation.addWord("e");
436 expandReal(numberString.substring(position + 1), wordRelation);
437 } else if ((position = numberString.indexOf('.')) != -1) {
439 String beforeDot = numberString.substring(0, position);
440 if (beforeDot.length() > 0) {
441 expandReal(beforeDot, wordRelation);
443 wordRelation.addWord("point");
444 String afterDot = numberString.substring(position + 1);
445 if (afterDot.length() > 0) {
446 expandDigits(afterDot, wordRelation);
450 expandNumber(numberString, wordRelation);
456 * Expands the given string of letters as a list of single char symbols.
458 * @param letters the string of letters to expand
459 * @param wordRelation words are added to this Relation
461 public static void expandLetters(String letters,
462 WordRelation wordRelation) {
463 letters = letters.toLowerCase();
466 for (int i = 0; i < letters.length(); i++) {
467 // if this is a number
468 c = letters.charAt(i);
471 wordRelation.addWord(digit2num[c-'0']);
472 } else if (letters.equals("a")) {
473 wordRelation.addWord("_a");
475 wordRelation.addWord(String.valueOf(c));
482 * Returns the integer value of the given string of Roman numerals.
484 * @param roman the string of Roman numbers
486 * @return the integer value
488 public static int expandRoman(String roman) {
491 for (int p = 0; p < roman.length(); p++) {
492 char c = roman.charAt(p);
495 } else if (c == 'V') {
497 } else if (c == 'I') {
498 if (p+1 < roman.length()) {
499 char p1 = roman.charAt(p+1);
503 } else if (p1 == 'X') {
519 * Returns true if the given character is a digit (0-9 only).
521 * @param ch the character to test
523 * @return true or false
525 public static boolean isDigit(char ch) {
526 return ('0' <= ch && ch <= '9');