blob: 536303adb1a86baf2a622bb611c3343d103db1e7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.util;
import java.util.ArrayList;
import java.util.List;
// CSOFF: LineLengthCheck
// CSOFF: InnerAssignmentCheck
// CSOFF: NoWhitespaceAfterCheck
// CSOFF: AvoidNestedBlocksCheck
/**
* Implementation of Number to String Conversion algorithm specified by
* XSL Transformations (XSLT) Version 2.0, W3C Recommendation, 23 January 2007.
*
* This algorithm differs from that specified in XSLT 1.0 in the following
* ways:
* <ul>
* <li>input numbers are greater than or equal to zero rather than greater than zero;</li>
* <li>introduces format tokens { w, W, Ww };</li>
* <li>introduces ordinal parameter to generate ordinal numbers;</li>
* </ul>
*
* Implementation Defaults and Limitations
* <ul>
* <li>If language parameter is unspecified (null or empty string), then the value
* of DEFAULT_LANGUAGE is used, which is defined below as "eng" (English).</li>
* <li>Only English, French, and Spanish word numerals are supported, and only if less than one trillion (1,000,000,000,000).</li>
* <li>Ordinal word numerals are supported for French and Spanish only when less than or equal to ten (10).</li>
* </ul>
*
* Implementation Notes
* <ul>
* <li>In order to handle format tokens outside the Unicode BMP, all processing is
* done in Unicode Scalar Values represented with Integer and Integer[]
* types. Without affecting behavior, this may be subsequently optimized to
* use int and int[] types.</li>
* <li>In order to communicate various sub-parameters, including ordinalization, a <em>features</em>
* is employed, which consists of comma separated name and optional value tokens, where name and value
* are separated by an equals '=' sign.</li>
* <li>Ordinal numbers are selected by specifying a word based format token in combination with a 'ordinal' feature with no value, in which case
* the features 'male' and 'female' may be used to specify gender for gender sensitive languages. For example, the feature string "ordinal,female"
* selects female ordinals.</li>
* </ul>
*
* @author Glenn Adams
*/
public class NumberConverter {
/** alphabetical */
public static final int LETTER_VALUE_ALPHABETIC = 1;
/** traditional */
public static final int LETTER_VALUE_TRADITIONAL = 2;
/** no token type */
private static final int TOKEN_NONE = 0;
/** alhphanumeric token type */
private static final int TOKEN_ALPHANUMERIC = 1;
/** nonalphanumeric token type */
private static final int TOKEN_NONALPHANUMERIC = 2;
/** default token */
private static final Integer[] DEFAULT_TOKEN = new Integer[] { (int) '1' };
/** default separator */
private static final Integer[] DEFAULT_SEPARATOR = new Integer[] { (int) '.' };
/** default language */
private static final String DEFAULT_LANGUAGE = "eng";
/** prefix token */
private Integer[] prefix;
/** suffix token */
private Integer[] suffix;
/** sequence of tokens, as parsed from format */
private Integer[][] tokens;
/** sequence of separators, as parsed from format */
private Integer[][] separators;
/** grouping separator */
private int groupingSeparator;
/** grouping size */
private int groupingSize;
/** letter value */
private int letterValue;
/** letter value system */
private String features;
/** language */
private String language;
/** country */
private String country;
/**
* Construct parameterized number converter.
* @param format format for the page number (may be null or empty, which is treated as null)
* @param groupingSeparator grouping separator (if zero, then no grouping separator applies)
* @param groupingSize grouping size (if zero or negative, then no grouping size applies)
* @param letterValue letter value (must be one of the above letter value enumeration values)
* @param features features (feature sub-parameters)
* @param language (may be null or empty, which is treated as null)
* @param country (may be null or empty, which is treated as null)
* @throws IllegalArgumentException if format is not a valid UTF-16 string (e.g., has unpaired surrogate)
*/
public NumberConverter ( String format, int groupingSeparator, int groupingSize, int letterValue, String features, String language, String country )
throws IllegalArgumentException {
this.groupingSeparator = groupingSeparator;
this.groupingSize = groupingSize;
this.letterValue = letterValue;
this.features = features;
this.language = ( language != null ) ? language.toLowerCase() : null;
this.country = ( country != null ) ? country.toLowerCase() : null;
parseFormatTokens ( format );
}
/**
* Convert a number to string according to conversion parameters.
* @param number number to conver
* @return string representing converted number
*/
public String convert ( long number ) {
List<Long> numbers = new ArrayList<Long>();
numbers.add ( number );
return convert ( numbers );
}
/**
* Convert list of numbers to string according to conversion parameters.
* @param numbers list of numbers to convert
* @return string representing converted list of numbers
*/
public String convert ( List<Long> numbers ) {
List<Integer> scalars = new ArrayList<Integer>();
if ( prefix != null ) {
appendScalars ( scalars, prefix );
}
convertNumbers ( scalars, numbers );
if ( suffix != null ) {
appendScalars ( scalars, suffix );
}
return scalarsToString ( scalars );
}
private void parseFormatTokens ( String format ) throws IllegalArgumentException {
List<Integer[]> tokens = new ArrayList<Integer[]>();
List<Integer[]> separators = new ArrayList<Integer[]>();
if ( ( format == null ) || ( format.length() == 0 ) ) {
format = "1";
}
int tokenType = TOKEN_NONE;
List<Integer> token = new ArrayList<Integer>();
Integer[] ca = CharUtilities.toUTF32 ( format, 0, true );
for ( int i = 0, n = ca.length; i < n; i++ ) {
int c = ca[i];
int tokenTypeNew = isAlphaNumeric ( c ) ? TOKEN_ALPHANUMERIC : TOKEN_NONALPHANUMERIC;
if ( tokenTypeNew != tokenType ) {
if ( token.size() > 0 ) {
if ( tokenType == TOKEN_ALPHANUMERIC ) {
tokens.add ( token.toArray ( new Integer [ token.size() ] ) );
} else {
separators.add ( token.toArray ( new Integer [ token.size() ] ) );
}
token.clear();
}
tokenType = tokenTypeNew;
}
token.add ( c );
}
if ( token.size() > 0 ) {
if ( tokenType == TOKEN_ALPHANUMERIC ) {
tokens.add ( token.toArray ( new Integer [ token.size() ] ) );
} else {
separators.add ( token.toArray ( new Integer [ token.size() ] ) );
}
}
if ( ! separators.isEmpty() ) {
this.prefix = separators.remove ( 0 );
}
if ( ! separators.isEmpty() ) {
this.suffix = separators.remove ( separators.size() - 1 );
}
this.separators = separators.toArray ( new Integer [ separators.size() ] [] );
this.tokens = tokens.toArray ( new Integer [ tokens.size() ] [] );
}
private static boolean isAlphaNumeric ( int c ) {
switch ( Character.getType ( c ) ) {
case Character.DECIMAL_DIGIT_NUMBER: // Nd
case Character.LETTER_NUMBER: // Nl
case Character.OTHER_NUMBER: // No
case Character.UPPERCASE_LETTER: // Lu
case Character.LOWERCASE_LETTER: // Ll
case Character.TITLECASE_LETTER: // Lt
case Character.MODIFIER_LETTER: // Lm
case Character.OTHER_LETTER: // Lo
return true;
default:
return false;
}
}
private void convertNumbers ( List<Integer> scalars, List<Long> numbers ) {
Integer[] tknLast = DEFAULT_TOKEN;
int tknIndex = 0;
int tknCount = tokens.length;
int sepIndex = 0;
int sepCount = separators.length;
int numIndex = 0;
for ( Long number : numbers ) {
Integer[] sep = null;
Integer[] tkn;
if ( tknIndex < tknCount ) {
if ( numIndex > 0 ) {
if ( sepIndex < sepCount ) {
sep = separators [ sepIndex++ ];
} else {
sep = DEFAULT_SEPARATOR;
}
}
tkn = tokens [ tknIndex++ ];
} else {
tkn = tknLast;
}
appendScalars ( scalars, convertNumber ( number, sep, tkn ) );
tknLast = tkn;
numIndex++;
}
}
private Integer[] convertNumber ( long number, Integer[] separator, Integer[] token ) {
List<Integer> sl = new ArrayList<Integer>();
if ( separator != null ) {
appendScalars ( sl, separator );
}
if ( token != null ) {
appendScalars ( sl, formatNumber ( number, token ) );
}
return sl.toArray ( new Integer [ sl.size() ] );
}
private Integer[] formatNumber ( long number, Integer[] token ) {
Integer[] fn = null;
assert token.length > 0;
if ( number < 0 ) {
throw new IllegalArgumentException ( "number must be non-negative" );
} else if ( token.length == 1 ) {
int s = token[0].intValue();
switch ( s ) {
case (int) '1':
{
fn = formatNumberAsDecimal ( number, (int) '1', 1 );
break;
}
case (int) 'W':
case (int) 'w':
{
fn = formatNumberAsWord ( number, ( s == (int) 'W' ) ? Character.UPPERCASE_LETTER : Character.LOWERCASE_LETTER );
break;
}
case (int) 'A': // handled as numeric sequence
case (int) 'a': // handled as numeric sequence
case (int) 'I': // handled as numeric special
case (int) 'i': // handled as numeric special
default:
{
if ( isStartOfDecimalSequence ( s ) ) {
fn = formatNumberAsDecimal ( number, s, 1 );
} else if ( isStartOfAlphabeticSequence ( s ) ) {
fn = formatNumberAsSequence ( number, s, getSequenceBase ( s ), null );
} else if ( isStartOfNumericSpecial ( s ) ) {
fn = formatNumberAsSpecial ( number, s );
} else {
fn = null;
}
break;
}
}
} else if ( ( token.length == 2 ) && ( token[0] == (int) 'W' ) && ( token[1] == (int) 'w' ) ) {
fn = formatNumberAsWord ( number, Character.TITLECASE_LETTER );
} else if ( isPaddedOne ( token ) ) {
int s = token [ token.length - 1 ].intValue();
fn = formatNumberAsDecimal ( number, s, token.length );
} else {
throw new IllegalArgumentException ( "invalid format token: \"" + CharUtilities.fromUTF32 ( token ) + "\"" );
}
if ( fn == null ) {
fn = formatNumber ( number, DEFAULT_TOKEN );
}
assert fn != null;
return fn;
}
/**
* Format NUMBER as decimal using characters denoting digits that start at ONE,
* adding one or more (zero) padding characters as needed to fill out field WIDTH.
* @param number to be formatted
* @param one unicode scalar value denoting numeric value 1
* @param width non-negative integer denoting field width of number, possible including padding
* @return formatted number as array of unicode scalars
*/
private Integer[] formatNumberAsDecimal ( long number, int one, int width ) {
assert Character.getNumericValue ( one ) == 1;
assert Character.getNumericValue ( one - 1 ) == 0;
assert Character.getNumericValue ( one + 8 ) == 9;
List<Integer> sl = new ArrayList<Integer>();
int zero = one - 1;
while ( number > 0 ) {
long digit = number % 10;
sl.add ( 0, zero + (int) digit );
number = number / 10;
}
while ( width > sl.size() ) {
sl.add ( 0, zero );
}
if ( ( groupingSize != 0 ) && ( groupingSeparator != 0 ) ) {
sl = performGrouping ( sl, groupingSize, groupingSeparator );
}
return sl.toArray ( new Integer [ sl.size() ] );
}
private static List<Integer> performGrouping ( List<Integer> sl, int groupingSize, int groupingSeparator ) {
assert groupingSize > 0;
assert groupingSeparator != 0;
if ( sl.size() > groupingSize ) {
List<Integer> gl = new ArrayList<Integer>();
for ( int i = 0, n = sl.size(), g = 0; i < n; i++ ) {
int k = n - i - 1;
if ( g == groupingSize ) {
gl.add ( 0, groupingSeparator );
g = 1;
} else {
g++;
}
gl.add ( 0, sl.get ( k ) );
}
return gl;
} else {
return sl;
}
}
/**
* Format NUMBER as using sequence of characters that start at ONE, and
* having BASE radix.
* @param number to be formatted
* @param one unicode scalar value denoting start of sequence (numeric value 1)
* @param base number of elements in sequence
* @param map if non-null, then maps sequences indices to unicode scalars
* @return formatted number as array of unicode scalars
*/
private Integer[] formatNumberAsSequence ( long number, int one, int base, int[] map ) {
assert base > 1;
assert ( map == null ) || ( map.length >= base );
List<Integer> sl = new ArrayList<Integer>();
if ( number == 0 ) {
return null;
} else {
long n = number;
while ( n > 0 ) {
int d = (int) ( ( n - 1 ) % (long) base );
int s = ( map != null ) ? map [ d ] : ( one + d );
sl.add ( 0, s );
n = ( n - 1 ) / base;
}
return sl.toArray ( new Integer [ sl.size() ] );
}
}
/**
* Format NUMBER as using special system that starts at ONE.
* @param number to be formatted
* @param one unicode scalar value denoting start of system (numeric value 1)
* @return formatted number as array of unicode scalars
*/
private Integer[] formatNumberAsSpecial ( long number, int one ) {
SpecialNumberFormatter f = getSpecialFormatter ( one, letterValue, features, language, country );
if ( f != null ) {
return f.format ( number, one, letterValue, features, language, country );
} else {
return null;
}
}
/**
* Format NUMBER as word according to TYPE, which must be either
* Character.UPPERCASE_LETTER, Character.LOWERCASE_LETTER, or
* Character.TITLECASE_LETTER. Makes use of this.language to
* determine language of word.
* @param number to be formatted
* @param caseType unicode character type for case conversion
* @return formatted number as array of unicode scalars
*/
private Integer[] formatNumberAsWord ( long number, int caseType ) {
SpecialNumberFormatter f = null;
if ( isLanguage ( "eng" ) ) {
f = new EnglishNumberAsWordFormatter ( caseType );
} else if ( isLanguage ( "spa" ) ) {
f = new SpanishNumberAsWordFormatter ( caseType );
} else if ( isLanguage ( "fra" ) ) {
f = new FrenchNumberAsWordFormatter ( caseType );
} else {
f = new EnglishNumberAsWordFormatter ( caseType );
}
return f.format ( number, 0, letterValue, features, language, country );
}
private boolean isLanguage ( String iso3Code ) {
if ( language == null ) {
return false;
} else if ( language.equals ( iso3Code ) ) {
return true;
} else {
return isSameLanguage ( iso3Code, language );
}
}
private static String[][] equivalentLanguages = {
{ "eng", "en" },
{ "fra", "fre", "fr" },
{ "spa", "es" },
};
private static boolean isSameLanguage ( String i3c, String lc ) {
for ( String[] el : equivalentLanguages ) {
assert el.length >= 2;
if ( el[0].equals ( i3c ) ) {
for ( int i = 0, n = el.length; i < n; i++ ) {
if ( el[i].equals ( lc ) ) {
return true;
}
}
return false;
}
}
return false;
}
private static boolean hasFeature ( String features, String feature ) {
if ( features != null ) {
assert feature != null;
assert feature.length() != 0;
String[] fa = features.split(",");
for ( String f : fa ) {
String[] fp = f.split("=");
assert fp.length > 0;
String fn = fp[0];
String fv = ( fp.length > 1 ) ? fp[1] : "";
if ( fn.equals ( feature ) ) {
return true;
}
}
}
return false;
}
/* not yet used
private static String getFeatureValue ( String features, String feature ) {
if ( features != null ) {
assert feature != null;
assert feature.length() != 0;
String[] fa = features.split(",");
for ( String f : fa ) {
String[] fp = f.split("=");
assert fp.length > 0;
String fn = fp[0];
String fv = ( fp.length > 1 ) ? fp[1] : "";
if ( fn.equals ( feature ) ) {
return fv;
}
}
}
return "";
}
*/
private static void appendScalars ( List<Integer> scalars, Integer[] sa ) {
for ( Integer s : sa ) {
scalars.add ( s );
}
}
private static String scalarsToString ( List<Integer> scalars ) {
Integer[] sa = scalars.toArray ( new Integer [ scalars.size() ] );
return CharUtilities.fromUTF32 ( sa );
}
private static boolean isPaddedOne ( Integer[] token ) {
if ( getDecimalValue ( token [ token.length - 1 ] ) != 1 ) {
return false;
} else {
for ( int i = 0, n = token.length - 1; i < n; i++ ) {
if ( getDecimalValue ( token [ i ] ) != 0 ) {
return false;
}
}
return true;
}
}
private static int getDecimalValue ( Integer scalar ) {
int s = scalar.intValue();
if ( Character.getType ( s ) == Character.DECIMAL_DIGIT_NUMBER ) {
return Character.getNumericValue ( s );
} else {
return -1;
}
}
private static boolean isStartOfDecimalSequence ( int s ) {
return ( Character.getNumericValue ( s ) == 1 )
&& ( Character.getNumericValue ( s - 1 ) == 0 )
&& ( Character.getNumericValue ( s + 8 ) == 9 );
}
private static int[][] supportedAlphabeticSequences = {
{ 'A', 26 }, // A...Z
{ 'a', 26 }, // a...z
};
private static boolean isStartOfAlphabeticSequence ( int s ) {
for ( int[] ss : supportedAlphabeticSequences ) {
assert ss.length >= 2;
if ( ss[0] == s ) {
return true;
}
}
return false;
}
private static int getSequenceBase ( int s ) {
for ( int[] ss : supportedAlphabeticSequences ) {
assert ss.length >= 2;
if ( ss[0] == s ) {
return ss[1];
}
}
return 0;
}
private static int[][] supportedSpecials = {
{ 'I' }, // latin - uppercase roman numerals
{ 'i' }, // latin - lowercase roman numerals
{ '\u0391' }, // greek - uppercase isopsephry numerals
{ '\u03B1' }, // greek - lowercase isopsephry numerals
{ '\u05D0' }, // hebrew - gematria numerals
{ '\u0623' }, // arabic - abjadi numberals
{ '\u0627' }, // arabic - either abjadi or hijai alphabetic sequence
{ '\u0E01' }, // thai - default alphabetic sequence
{ '\u3042' }, // kana - hiragana (gojuon) - default alphabetic sequence
{ '\u3044' }, // kana - hiragana (iroha)
{ '\u30A2' }, // kana - katakana (gojuon) - default alphabetic sequence
{ '\u30A4' }, // kana - katakana (iroha)
};
private static boolean isStartOfNumericSpecial ( int s ) {
for ( int[] ss : supportedSpecials ) {
assert ss.length >= 1;
if ( ss[0] == s ) {
return true;
}
}
return false;
}
private SpecialNumberFormatter getSpecialFormatter ( int one, int letterValue, String features, String language, String country ) {
if ( one == (int) 'I' ) {
return new RomanNumeralsFormatter();
} else if ( one == (int) 'i' ) {
return new RomanNumeralsFormatter();
} else if ( one == (int) '\u0391' ) {
return new IsopsephryNumeralsFormatter();
} else if ( one == (int) '\u03B1' ) {
return new IsopsephryNumeralsFormatter();
} else if ( one == (int) '\u05D0' ) {
return new GematriaNumeralsFormatter();
} else if ( one == (int) '\u0623' ) {
return new ArabicNumeralsFormatter();
} else if ( one == (int) '\u0627' ) {
return new ArabicNumeralsFormatter();
} else if ( one == (int) '\u0E01' ) {
return new ThaiNumeralsFormatter();
} else if ( one == (int) '\u3042' ) {
return new KanaNumeralsFormatter();
} else if ( one == (int) '\u3044' ) {
return new KanaNumeralsFormatter();
} else if ( one == (int) '\u30A2' ) {
return new KanaNumeralsFormatter();
} else if ( one == (int) '\u30A4' ) {
return new KanaNumeralsFormatter();
} else {
return null;
}
}
private static Integer[] toUpperCase ( Integer[] sa ) {
assert sa != null;
for ( int i = 0, n = sa.length; i < n; i++ ) {
Integer s = sa [ i ];
sa [ i ] = Character.toUpperCase ( s );
}
return sa;
}
private static Integer[] toLowerCase ( Integer[] sa ) {
assert sa != null;
for ( int i = 0, n = sa.length; i < n; i++ ) {
Integer s = sa [ i ];
sa [ i ] = Character.toLowerCase ( s );
}
return sa;
}
/* not yet used
private static Integer[] toTitleCase ( Integer[] sa ) {
assert sa != null;
if ( sa.length > 0 ) {
sa [ 0 ] = Character.toTitleCase ( sa [ 0 ] );
}
return sa;
}
*/
private static List<String> convertWordCase ( List<String> words, int caseType ) {
List<String> wl = new ArrayList<String>();
for ( String w : words ) {
wl.add ( convertWordCase ( w, caseType ) );
}
return wl;
}
private static String convertWordCase ( String word, int caseType ) {
if ( caseType == Character.UPPERCASE_LETTER ) {
return word.toUpperCase();
} else if ( caseType == Character.LOWERCASE_LETTER ) {
return word.toLowerCase();
} else if ( caseType == Character.TITLECASE_LETTER ) {
StringBuffer sb = new StringBuffer();
for ( int i = 0, n = word.length(); i < n; i++ ) {
String s = word.substring ( i, i + 1 );
if ( i == 0 ) {
sb.append ( s.toUpperCase() );
} else {
sb.append ( s.toLowerCase() );
}
}
return sb.toString();
} else {
return word;
}
}
private static String joinWords ( List<String> words, String separator ) {
StringBuffer sb = new StringBuffer();
for ( String w : words ) {
if ( sb.length() > 0 ) {
sb.append ( separator );
}
sb.append ( w );
}
return sb.toString();
}
/**
* Special number formatter.
*/
interface SpecialNumberFormatter {
/**
* Format number with special numeral system.
* @param number to be formatted
* @param one unicode scalar value denoting numeric value 1
* @param letterValue letter value (must be one of the above letter value enumeration values)
* @param features features (feature sub-parameters)
* @param language denotes applicable language
* @param country denotes applicable country
* @return formatted number as array of unicode scalars
*/
Integer[] format ( long number, int one, int letterValue, String features, String language, String country );
}
/**
* English Word Numerals
*/
private static String[] englishWordOnes = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
private static String[] englishWordTeens = { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
private static String[] englishWordTens = { "", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" };
private static String[] englishWordOthers = { "hundred", "thousand", "million", "billion" };
private static String[] englishWordOnesOrd = { "none", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth" };
private static String[] englishWordTeensOrd = { "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth" };
private static String[] englishWordTensOrd = { "", "tenth", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth", "seventieth", "eightieth", "ninetith" };
private static String[] englishWordOthersOrd = { "hundredth", "thousandth", "millionth", "billionth" };
private static class EnglishNumberAsWordFormatter implements SpecialNumberFormatter {
private int caseType = Character.UPPERCASE_LETTER;
EnglishNumberAsWordFormatter ( int caseType ) {
this.caseType = caseType;
}
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
List<String> wl = new ArrayList<String>();
if ( number >= 1000000000000L ) {
return null;
} else {
boolean ordinal = hasFeature ( features, "ordinal" );
if ( number == 0 ) {
wl.add ( englishWordOnes [ 0 ] );
} else if ( ordinal && ( number < 10 ) ) {
wl.add ( englishWordOnesOrd [ (int) number ] );
} else {
int ones = (int) ( number % 1000 );
int thousands = (int) ( ( number / 1000 ) % 1000 );
int millions = (int) ( ( number / 1000000 ) % 1000 );
int billions = (int) ( ( number / 1000000000 ) % 1000 );
if ( billions > 0 ) {
wl = formatOnesInThousand ( wl, billions );
if ( ordinal && ( ( number % 1000000000 ) == 0 ) ) {
wl.add ( englishWordOthersOrd[3] );
} else {
wl.add ( englishWordOthers[3] );
}
}
if ( millions > 0 ) {
wl = formatOnesInThousand ( wl, millions );
if ( ordinal && ( ( number % 1000000 ) == 0 ) ) {
wl.add ( englishWordOthersOrd[2] );
} else {
wl.add ( englishWordOthers[2] );
}
}
if ( thousands > 0 ) {
wl = formatOnesInThousand ( wl, thousands );
if ( ordinal && ( ( number % 1000 ) == 0 ) ) {
wl.add ( englishWordOthersOrd[1] );
} else {
wl.add ( englishWordOthers[1] );
}
}
if ( ones > 0 ) {
wl = formatOnesInThousand ( wl, ones, ordinal );
}
}
wl = convertWordCase ( wl, caseType );
return CharUtilities.toUTF32 ( joinWords ( wl, " " ), 0, true );
}
}
private List<String> formatOnesInThousand ( List<String> wl, int number ) {
return formatOnesInThousand ( wl, number, false );
}
private List<String> formatOnesInThousand ( List<String> wl, int number, boolean ordinal ) {
assert number < 1000;
int ones = number % 10;
int tens = ( number / 10 ) % 10;
int hundreds = ( number / 100 ) % 10;
if ( hundreds > 0 ) {
wl.add ( englishWordOnes [ hundreds ] );
if ( ordinal && ( ( number % 100 ) == 0 ) ) {
wl.add ( englishWordOthersOrd[0] );
} else {
wl.add ( englishWordOthers[0] );
}
}
if ( tens > 0 ) {
if ( tens == 1 ) {
if ( ordinal ) {
wl.add ( englishWordTeensOrd [ ones ] );
} else {
wl.add ( englishWordTeens [ ones ] );
}
} else {
if ( ordinal && ( ones == 0 ) ) {
wl.add ( englishWordTensOrd [ tens ] );
} else {
wl.add ( englishWordTens [ tens ] );
}
if ( ones > 0 ) {
if ( ordinal ) {
wl.add ( englishWordOnesOrd [ ones ] );
} else {
wl.add ( englishWordOnes [ ones ] );
}
}
}
} else if ( ones > 0 ) {
if ( ordinal ) {
wl.add ( englishWordOnesOrd [ ones ] );
} else {
wl.add ( englishWordOnes [ ones ] );
}
}
return wl;
}
}
/**
* French Word Numerals
*/
private static String[] frenchWordOnes = { "z\u00e9ro", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf" };
private static String[] frenchWordTeens = { "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf" };
private static String[] frenchWordTens = { "", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "soixante-dix", "quatre-vingt", "quatre-vingt-dix" };
private static String[] frenchWordOthers = { "cent", "cents", "mille", "million", "millions", "milliard", "milliards" };
private static String[] frenchWordOnesOrdMale = { "premier", "deuxi\u00e8me", "troisi\u00e8me", "quatri\u00e8me", "cinqui\u00e8me", "sixi\u00e8me", "septi\u00e8me", "huiti\u00e8me", "neuvi\u00e8me", "dixi\u00e8me" };
private static String[] frenchWordOnesOrdFemale = { "premi\u00e8re", "deuxi\u00e8me", "troisi\u00e8me", "quatri\u00e8me", "cinqui\u00e8me", "sixi\u00e8me", "septi\u00e8me", "huiti\u00e8me", "neuvi\u00e8me", "dixi\u00e8me" };
private static class FrenchNumberAsWordFormatter implements SpecialNumberFormatter {
private int caseType = Character.UPPERCASE_LETTER;
FrenchNumberAsWordFormatter ( int caseType ) {
this.caseType = caseType;
}
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
List<String> wl = new ArrayList<String>();
if ( number >= 1000000000000L ) {
return null;
} else {
boolean ordinal = hasFeature ( features, "ordinal" );
if ( number == 0 ) {
wl.add ( frenchWordOnes [ 0 ] );
} else if ( ordinal && ( number <= 10 ) ) {
boolean female = hasFeature ( features, "female" );
if ( female ) {
wl.add ( frenchWordOnesOrdFemale [ (int) number ] );
} else {
wl.add ( frenchWordOnesOrdMale [ (int) number ] );
}
} else {
int ones = (int) ( number % 1000 );
int thousands = (int) ( ( number / 1000 ) % 1000 );
int millions = (int) ( ( number / 1000000 ) % 1000 );
int billions = (int) ( ( number / 1000000000 ) % 1000 );
if ( billions > 0 ) {
wl = formatOnesInThousand ( wl, billions );
if ( billions == 1 ) {
wl.add ( frenchWordOthers[5] );
} else {
wl.add ( frenchWordOthers[6] );
}
}
if ( millions > 0 ) {
wl = formatOnesInThousand ( wl, millions );
if ( millions == 1 ) {
wl.add ( frenchWordOthers[3] );
} else {
wl.add ( frenchWordOthers[4] );
}
}
if ( thousands > 0 ) {
if ( thousands > 1 ) {
wl = formatOnesInThousand ( wl, thousands );
}
wl.add ( frenchWordOthers[2] );
}
if ( ones > 0 ) {
wl = formatOnesInThousand ( wl, ones );
}
}
wl = convertWordCase ( wl, caseType );
return CharUtilities.toUTF32 ( joinWords ( wl, " " ), 0, true );
}
}
private List<String> formatOnesInThousand ( List<String> wl, int number ) {
assert number < 1000;
int ones = number % 10;
int tens = ( number / 10 ) % 10;
int hundreds = ( number / 100 ) % 10;
if ( hundreds > 0 ) {
if ( hundreds > 1 ) {
wl.add ( frenchWordOnes [ hundreds ] );
}
if ( ( hundreds > 1 ) && ( tens == 0 ) && ( ones == 0 ) ) {
wl.add ( frenchWordOthers[1] );
} else {
wl.add ( frenchWordOthers[0] );
}
}
if ( tens > 0 ) {
if ( tens == 1 ) {
wl.add ( frenchWordTeens [ ones ] );
} else if ( tens < 7 ) {
if ( ones == 1 ) {
wl.add ( frenchWordTens [ tens ] );
wl.add ( "et" );
wl.add ( frenchWordOnes [ ones ] );
} else {
StringBuffer sb = new StringBuffer();
sb.append ( frenchWordTens [ tens ] );
if ( ones > 0 ) {
sb.append ( '-' );
sb.append ( frenchWordOnes [ ones ] );
}
wl.add ( sb.toString() );
}
} else if ( tens == 7 ) {
if ( ones == 1 ) {
wl.add ( frenchWordTens [ 6 ] );
wl.add ( "et" );
wl.add ( frenchWordTeens [ ones ] );
} else {
StringBuffer sb = new StringBuffer();
sb.append ( frenchWordTens [ 6 ] );
sb.append ( '-' );
sb.append ( frenchWordTeens [ ones ] );
wl.add ( sb.toString() );
}
} else if ( tens == 8 ) {
StringBuffer sb = new StringBuffer();
sb.append ( frenchWordTens [ tens ] );
if ( ones > 0 ) {
sb.append ( '-' );
sb.append ( frenchWordOnes [ ones ] );
} else {
sb.append ( 's' );
}
wl.add ( sb.toString() );
} else if ( tens == 9 ) {
StringBuffer sb = new StringBuffer();
sb.append ( frenchWordTens [ 8 ] );
sb.append ( '-' );
sb.append ( frenchWordTeens [ ones ] );
wl.add ( sb.toString() );
}
} else if ( ones > 0 ) {
wl.add ( frenchWordOnes [ ones ] );
}
return wl;
}
}
/**
* Spanish Word Numerals
*/
private static String[] spanishWordOnes = { "cero", "uno", "dos", "tres", "cuatro", "cinco", "seise", "siete", "ocho", "nueve" };
private static String[] spanishWordTeens = { "diez", "once", "doce", "trece", "catorce", "quince", "diecis\u00e9is", "diecisiete", "dieciocho", "diecinueve" };
private static String[] spanishWordTweens = { "veinte", "veintiuno", "veintid\u00f3s", "veintitr\u00e9s", "veinticuatro", "veinticinco", "veintis\u00e9is", "veintisiete", "veintiocho", "veintinueve" };
private static String[] spanishWordTens = { "", "diez", "veinte", "treinta", "cuarenta", "cincuenta", "sesenta", "setenta", "ochenta", "noventa" };
private static String[] spanishWordHundreds = { "", "ciento", "doscientos", "trescientos", "cuatrocientos", "quinientos", "seiscientos", "setecientos", "ochocientos", "novecientos" };
private static String[] spanishWordOthers = { "un", "cien", "mil", "mill\u00f3n", "millones" };
private static String[] spanishWordOnesOrdMale = { "ninguno", "primero", "segundo", "tercero", "cuarto", "quinto", "sexto", "s\u00e9ptimo", "octavo", "novento", "d\u00e9cimo" };
private static String[] spanishWordOnesOrdFemale = { "ninguna", "primera", "segunda", "tercera", "cuarta", "quinta", "sexta", "s\u00e9ptima", "octava", "noventa", "d\u00e9cima" };
private static class SpanishNumberAsWordFormatter implements SpecialNumberFormatter {
private int caseType = Character.UPPERCASE_LETTER;
SpanishNumberAsWordFormatter ( int caseType ) {
this.caseType = caseType;
}
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
List<String> wl = new ArrayList<String>();
if ( number >= 1000000000000L ) {
return null;
} else {
boolean ordinal = hasFeature ( features, "ordinal" );
if ( number == 0 ) {
wl.add ( spanishWordOnes [ 0 ] );
} else if ( ordinal && ( number <= 10 ) ) {
boolean female = hasFeature ( features, "female" );
if ( female ) {
wl.add ( spanishWordOnesOrdFemale [ (int) number ] );
} else {
wl.add ( spanishWordOnesOrdMale [ (int) number ] );
}
} else {
int ones = (int) ( number % 1000 );
int thousands = (int) ( ( number / 1000 ) % 1000 );
int millions = (int) ( ( number / 1000000 ) % 1000 );
int billions = (int) ( ( number / 1000000000 ) % 1000 );
if ( billions > 0 ) {
if ( billions > 1 ) {
wl = formatOnesInThousand ( wl, billions );
}
wl.add ( spanishWordOthers[2] );
wl.add ( spanishWordOthers[4] );
}
if ( millions > 0 ) {
if ( millions == 1 ) {
wl.add ( spanishWordOthers[0] );
} else {
wl = formatOnesInThousand ( wl, millions );
}
if ( millions > 1 ) {
wl.add ( spanishWordOthers[4] );
} else {
wl.add ( spanishWordOthers[3] );
}
}
if ( thousands > 0 ) {
if ( thousands > 1 ) {
wl = formatOnesInThousand ( wl, thousands );
}
wl.add ( spanishWordOthers[2] );
}
if ( ones > 0 ) {
wl = formatOnesInThousand ( wl, ones );
}
}
wl = convertWordCase ( wl, caseType );
return CharUtilities.toUTF32 ( joinWords ( wl, " " ), 0, true );
}
}
private List<String> formatOnesInThousand ( List<String> wl, int number ) {
assert number < 1000;
int ones = number % 10;
int tens = ( number / 10 ) % 10;
int hundreds = ( number / 100 ) % 10;
if ( hundreds > 0 ) {
if ( ( hundreds == 1 ) && ( tens == 0 ) && ( ones == 0 ) ) {
wl.add ( spanishWordOthers[1] );
} else {
wl.add ( spanishWordHundreds [ hundreds ] );
}
}
if ( tens > 0 ) {
if ( tens == 1 ) {
wl.add ( spanishWordTeens [ ones ] );
} else if ( tens == 2 ) {
wl.add ( spanishWordTweens [ ones ] );
} else {
wl.add ( spanishWordTens [ tens ] );
if ( ones > 0 ) {
wl.add ( "y" );
wl.add ( spanishWordOnes [ ones ] );
}
}
} else if ( ones > 0 ) {
wl.add ( spanishWordOnes [ ones ] );
}
return wl;
}
}
/**
* Roman (Latin) Numerals
*/
private static int[] romanMapping = {
100000,
90000,
50000,
40000,
10000,
9000,
5000,
4000,
1000,
900,
500,
400,
100,
90,
50,
40,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
};
private static String[] romanStandardForms = {
null,
null,
null,
null,
null,
null,
null,
null,
"m",
"cm",
"d",
"cd",
"c",
"xc",
"l",
"xl",
"x",
"ix",
null,
null,
null,
"v",
"iv",
null,
null,
"i"
};
private static String[] romanLargeForms = {
"\u2188",
"\u2182\u2188",
"\u2187",
"\u2182\u2187",
"\u2182",
"\u2180\u2182",
"\u2181",
"\u2180\u2181",
"m",
"cm",
"d",
"cd",
"c",
"xc",
"l",
"xl",
"x",
"ix",
null,
null,
null,
"v",
"iv",
null,
null,
"i"
};
private static String[] romanNumberForms = {
"\u2188",
"\u2182\u2188",
"\u2187",
"\u2182\u2187",
"\u2182",
"\u2180\u2182",
"\u2181",
"\u2180\u2181",
"\u216F",
"\u216D\u216F",
"\u216E",
"\u216D\u216E",
"\u216D",
"\u2169\u216D",
"\u216C",
"\u2169\u216C",
"\u2169",
"\u2168",
"\u2167",
"\u2166",
"\u2165",
"\u2164",
"\u2163",
"\u2162",
"\u2161",
"\u2160"
};
private static class RomanNumeralsFormatter implements SpecialNumberFormatter {
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
List<Integer> sl = new ArrayList<Integer>();
if ( number == 0 ) {
return null;
} else {
String[] forms;
int maxNumber;
if ( hasFeature ( features, "unicode-number-forms" ) ) {
forms = romanNumberForms;
maxNumber = 199999;
} else if ( hasFeature ( features, "large" ) ) {
forms = romanLargeForms;
maxNumber = 199999;
} else {
forms = romanStandardForms;
maxNumber = 4999;
}
if ( number > maxNumber ) {
return null;
} else {
while ( number > 0 ) {
for ( int i = 0, n = romanMapping.length; i < n; i++ ) {
int d = romanMapping [ i ];
if ( ( number >= d ) && ( forms [ i ] != null ) ) {
appendScalars ( sl, CharUtilities.toUTF32 ( forms [ i ], 0, true ) );
number = number - d;
break;
}
}
}
if ( one == (int) 'I' ) {
return toUpperCase ( sl.toArray ( new Integer [ sl.size() ] ) );
} else if ( one == (int) 'i' ) {
return toLowerCase ( sl.toArray ( new Integer [ sl.size() ] ) );
} else {
return null;
}
}
}
}
}
/**
* Isopsephry (Greek) Numerals
*/
private static class IsopsephryNumeralsFormatter implements SpecialNumberFormatter {
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
return null;
}
}
/**
* Gematria (Hebrew) Numerals
*/
private static int[] hebrewGematriaAlphabeticMap = {
// ones
0x05D0, // ALEF
0x05D1, // BET
0x05D2, // GIMEL
0x05D3, // DALET
0x05D4, // HE
0x05D5, // VAV
0x05D6, // ZAYIN
0x05D7, // HET
0x05D8, // TET
// tens
0x05D9, // YOD
0x05DB, // KAF
0x05DC, // LAMED
0x05DE, // MEM
0x05E0, // NUN
0x05E1, // SAMEKH
0x05E2, // AYIN
0x05E4, // PE
0x05E6, // TSADHI
// hundreds
0x05E7, // QOF
0x05E8, // RESH
0x05E9, // SHIN
0x05EA, // TAV
0x05DA, // FINAL KAF
0x05DD, // FINAL MEM
0x05DF, // FINAL NUN
0x05E3, // FINAL PE
0x05E5, // FINAL TSADHI
};
private class GematriaNumeralsFormatter implements SpecialNumberFormatter {
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
if ( one == 0x05D0 ) {
if ( letterValue == LETTER_VALUE_ALPHABETIC ) {
return formatNumberAsSequence ( number, one, hebrewGematriaAlphabeticMap.length, hebrewGematriaAlphabeticMap );
} else if ( letterValue == LETTER_VALUE_TRADITIONAL ) {
if ( ( number == 0 ) || ( number > 1999 ) ) {
return null;
} else {
return formatAsGematriaNumber ( number, features, language, country );
}
} else {
return null;
}
} else {
return null;
}
}
private Integer[] formatAsGematriaNumber ( long number, String features, String language, String country ) {
List<Integer> sl = new ArrayList<Integer>();
assert hebrewGematriaAlphabeticMap.length == 27;
assert hebrewGematriaAlphabeticMap[0] == 0x05D0; // ALEF
assert hebrewGematriaAlphabeticMap[21] == 0x05EA; // TAV
assert number != 0;
assert number < 2000;
int[] map = hebrewGematriaAlphabeticMap;
int thousands = (int) ( ( number / 1000 ) % 10 );
int hundreds = (int) ( ( number / 100 ) % 10 );
int tens = (int) ( ( number / 10 ) % 10 );
int ones = (int) ( ( number / 1 ) % 10 );
if ( thousands > 0 ) {
sl.add ( map [ 0 + ( thousands - 1 ) ] );
sl.add ( 0x05F3 );
}
if ( hundreds > 0 ) {
assert hundreds < 10;
if ( hundreds < 5 ) {
sl.add ( map [ 18 + ( hundreds - 1 ) ] );
} else if ( hundreds < 9 ) {
sl.add ( map [ 18 + ( 4 - 1 ) ] );
sl.add ( 0x05F4 );
sl.add ( map [ 18 + ( hundreds - 5 ) ] );
} else if ( hundreds == 9 ) {
sl.add ( map [ 18 + ( 4 - 1 ) ] );
sl.add ( map [ 18 + ( 4 - 1 ) ] );
sl.add ( 0x05F4 );
sl.add ( map [ 18 + ( hundreds - 9 ) ] );
}
}
if ( number == 15 ) {
sl.add ( map [ 9 - 1] );
sl.add ( 0x05F4 );
sl.add ( map [ 6 - 1] );
} else if ( number == 16 ) {
sl.add ( map [ 9 - 1 ] );
sl.add ( 0x05F4 );
sl.add ( map [ 7 - 1 ] );
} else {
if ( tens > 0 ) {
assert tens < 10;
sl.add ( map [ 9 + ( tens - 1 ) ] );
}
if ( ones > 0 ) {
assert ones < 10;
sl.add ( map [ 0 + ( ones - 1 ) ] );
}
}
return sl.toArray ( new Integer [ sl.size() ] );
}
}
/**
* Arabic Numerals
*/
private static int[] arabicAbjadiAlphabeticMap = {
// ones
0x0623, // ALEF WITH HAMZA ABOVE
0x0628, // BEH
0x062C, // JEEM
0x062F, // DAL
0x0647, // HEH
0x0648, // WAW
0x0632, // ZAIN
0x062D, // HAH
0x0637, // TAH
// tens
0x0649, // ALEF MAQSURA
0x0643, // KAF
0x0644, // LAM
0x0645, // MEEM
0x0646, // NOON
0x0633, // SEEN
0x0639, // AIN
0x0641, // FEH
0x0635, // SAD
// hundreds
0x0642, // QAF
0x0631, // REH
0x0634, // SHEEN
0x062A, // TEH
0x062B, // THEH
0x062E, // KHAH
0x0630, // THAL
0x0636, // DAD
0x0638, // ZAH
// thousands
0x063A, // GHAIN
};
private static int[] arabicHijaiAlphabeticMap = {
0x0623, // ALEF WITH HAMZA ABOVE
0x0628, // BEH
0x062A, // TEH
0x062B, // THEH
0x062C, // JEEM
0x062D, // HAH
0x062E, // KHAH
0x062F, // DAL
0x0630, // THAL
0x0631, // REH
0x0632, // ZAIN
0x0633, // SEEN
0x0634, // SHEEN
0x0635, // SAD
0x0636, // DAD
0x0637, // TAH
0x0638, // ZAH
0x0639, // AIN
0x063A, // GHAIN
0x0641, // FEH
0x0642, // QAF
0x0643, // KAF
0x0644, // LAM
0x0645, // MEEM
0x0646, // NOON
0x0647, // HEH
0x0648, // WAW
0x0649, // ALEF MAQSURA
};
private class ArabicNumeralsFormatter implements SpecialNumberFormatter {
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
if ( one == 0x0627 ) {
int[] map;
if ( letterValue == LETTER_VALUE_TRADITIONAL ) {
map = arabicAbjadiAlphabeticMap;
} else if ( letterValue == LETTER_VALUE_ALPHABETIC ) {
map = arabicHijaiAlphabeticMap;
} else {
map = arabicAbjadiAlphabeticMap;
}
return formatNumberAsSequence ( number, one, map.length, map );
} else if ( one == 0x0623 ) {
if ( ( number == 0 ) || ( number > 1999 ) ) {
return null;
} else {
return formatAsAbjadiNumber ( number, features, language, country );
}
} else {
return null;
}
}
private Integer[] formatAsAbjadiNumber ( long number, String features, String language, String country ) {
List<Integer> sl = new ArrayList<Integer>();
assert arabicAbjadiAlphabeticMap.length == 28;
assert arabicAbjadiAlphabeticMap[0] == 0x0623; // ALEF WITH HAMZA ABOVE
assert arabicAbjadiAlphabeticMap[27] == 0x063A; // GHAIN
assert number != 0;
assert number < 2000;
int[] map = arabicAbjadiAlphabeticMap;
int thousands = (int) ( ( number / 1000 ) % 10 );
int hundreds = (int) ( ( number / 100 ) % 10 );
int tens = (int) ( ( number / 10 ) % 10 );
int ones = (int) ( ( number / 1 ) % 10 );
if ( thousands > 0 ) {
assert thousands < 2;
sl.add ( map [ 27 + ( thousands - 1 ) ] );
}
if ( hundreds > 0 ) {
assert thousands < 10;
sl.add ( map [ 18 + ( hundreds - 1 ) ] );
}
if ( tens > 0 ) {
assert tens < 10;
sl.add ( map [ 9 + ( tens - 1 ) ] );
}
if ( ones > 0 ) {
assert ones < 10;
sl.add ( map [ 0 + ( ones - 1 ) ] );
}
return sl.toArray ( new Integer [ sl.size() ] );
}
}
/**
* Kana (Japanese) Numerals
*/
private static int[] hiraganaGojuonAlphabeticMap = {
0x3042, // A
0x3044, // I
0x3046, // U
0x3048, // E
0x304A, // O
0x304B, // KA
0x304D, // KI
0x304F, // KU
0x3051, // KE
0x3053, // KO
0x3055, // SA
0x3057, // SI
0x3059, // SU
0x305B, // SE
0x305D, // SO
0x305F, // TA
0x3061, // TI
0x3064, // TU
0x3066, // TE
0x3068, // TO
0x306A, // NA
0x306B, // NI
0x306C, // NU
0x306D, // NE
0x306E, // NO
0x306F, // HA
0x3072, // HI
0x3075, // HU
0x3078, // HE
0x307B, // HO
0x307E, // MA
0x307F, // MI
0x3080, // MU
0x3081, // ME
0x3082, // MO
0x3084, // YA
0x3086, // YU
0x3088, // YO
0x3089, // RA
0x308A, // RI
0x308B, // RU
0x308C, // RE
0x308D, // RO
0x308F, // WA
0x3090, // WI
0x3091, // WE
0x3092, // WO
0x3093, // N
};
private static int[] katakanaGojuonAlphabeticMap = {
0x30A2, // A
0x30A4, // I
0x30A6, // U
0x30A8, // E
0x30AA, // O
0x30AB, // KA
0x30AD, // KI
0x30AF, // KU
0x30B1, // KE
0x30B3, // KO
0x30B5, // SA
0x30B7, // SI
0x30B9, // SU
0x30BB, // SE
0x30BD, // SO
0x30BF, // TA
0x30C1, // TI
0x30C4, // TU
0x30C6, // TE
0x30C8, // TO
0x30CA, // NA
0x30CB, // NI
0x30CC, // NU
0x30CD, // NE
0x30CE, // NO
0x30CF, // HA
0x30D2, // HI
0x30D5, // HU
0x30D8, // HE
0x30DB, // HO
0x30DE, // MA
0x30DF, // MI
0x30E0, // MU
0x30E1, // ME
0x30E2, // MO
0x30E4, // YA
0x30E6, // YU
0x30E8, // YO
0x30E9, // RA
0x30EA, // RI
0x30EB, // RU
0x30EC, // RE
0x30ED, // RO
0x30EF, // WA
0x30F0, // WI
0x30F1, // WE
0x30F2, // WO
0x30F3, // N
};
private class KanaNumeralsFormatter implements SpecialNumberFormatter {
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
if ( ( one == 0x3042 ) && ( letterValue == LETTER_VALUE_ALPHABETIC ) ) {
return formatNumberAsSequence ( number, one, hiraganaGojuonAlphabeticMap.length, hiraganaGojuonAlphabeticMap );
} else if ( ( one == 0x30A2 ) && ( letterValue == LETTER_VALUE_ALPHABETIC ) ) {
return formatNumberAsSequence ( number, one, katakanaGojuonAlphabeticMap.length, katakanaGojuonAlphabeticMap );
} else {
return null;
}
}
}
/**
* Thai Numerals
*/
private static int[] thaiAlphabeticMap = {
0x0E01,
0x0E02,
0x0E03,
0x0E04,
0x0E05,
0x0E06,
0x0E07,
0x0E08,
0x0E09,
0x0E0A,
0x0E0B,
0x0E0C,
0x0E0D,
0x0E0E,
0x0E0F,
0x0E10,
0x0E11,
0x0E12,
0x0E13,
0x0E14,
0x0E15,
0x0E16,
0x0E17,
0x0E18,
0x0E19,
0x0E1A,
0x0E1B,
0x0E1C,
0x0E1D,
0x0E1E,
0x0E1F,
0x0E20,
0x0E21,
0x0E22,
0x0E23,
// 0x0E24, // RU - not used in modern sequence
0x0E25,
// 0x0E26, // LU - not used in modern sequence
0x0E27,
0x0E28,
0x0E29,
0x0E2A,
0x0E2B,
0x0E2C,
0x0E2D,
0x0E2E,
};
private class ThaiNumeralsFormatter implements SpecialNumberFormatter {
@Override
public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
if ( ( one == 0x0E01 ) && ( letterValue == LETTER_VALUE_ALPHABETIC ) ) {
return formatNumberAsSequence ( number, one, thaiAlphabeticMap.length, thaiAlphabeticMap );
} else {
return null;
}
}
}
}