use crate::base::{
default_to_cardinal, set_low_numwords, set_mid_numwords, Cards, Kwargs, KwVal, Lang, N2WError,
Result,
};
use crate::currency::{parse_currency_parts, CurrencyValue};
use crate::floatpath::{float2tuple, FloatValue};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const NEGWORD: &str = "負";
const POINTWORD: &str = "點";
const ORD_PREFIX: &str = "第";
const YEAR: &str = "年";
const YEAR_PREFIX: &str = "公元";
const YEAR_BCE: &str = "前";
const STUFF_ZERO: u8 = 2;
const ZERO_WORD: &str = "零";
const CURRENCY_FLOATS: [&str; 2] = ["角", "分"];
const XXX_UNIT: &str = "元";
#[derive(Debug, Clone, Copy)]
enum Form {
Name(&'static str),
Nested,
}
fn build_currency_forms() -> HashMap<&'static str, Form> {
let mut m: HashMap<&'static str, Form> = [
("XXX", "元"), ("CNY", "人民幣"),
("NTD", "新台幣"),
("HKD", "港幣"),
("MOP", "澳門幣"),
("SGD", "新加坡元"),
("MYR", "馬來西亞令吉"),
("USD", "美元"),
("EUR", "歐元"),
("GBP", "英鎊"),
("JPY", "日元"),
("CHF", "瑞士法郎"),
("CAD", "加元"),
("AUD", "澳幣"),
("NZD", "紐西蘭元"),
("THB", "泰銖"),
("KRW", "韓元"),
]
.into_iter()
.map(|(code, name)| (code, Form::Name(name)))
.collect();
m.insert("XXX", Form::Nested);
m
}
pub struct LangZhTw {
cards: Cards,
maxval: BigInt,
one: String,
ten: String,
currency_forms: HashMap<&'static str, Form>,
}
impl Default for LangZhTw {
fn default() -> Self {
Self::new()
}
}
impl LangZhTw {
pub fn new() -> Self {
let mut cards = Cards::new();
const HIGH: [&str; 18] = [
"不可說", "無量", "不可思議", "那由他", "阿僧祇", "恆河沙", "極", "載", "正", "澗", "溝", "穣", "秭", "垓", "京", "兆", "億", "萬", ];
let max_exp: u32 = 4 * HIGH.len() as u32; for (i, word) in HIGH.iter().enumerate() {
let exp = max_exp - 4 * i as u32;
cards.insert(BigInt::from(10u8).pow(exp), *word);
}
set_mid_numwords(&mut cards, &[(1000, "千"), (100, "百")]);
set_low_numwords(
&mut cards,
&[
"十", "九", "八", "七", "六", "五", "四", "三", "二", "一", "零", ],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let one = cards.get(&BigInt::one()).unwrap_or("").to_string();
let ten = cards.get(&BigInt::from(10)).unwrap_or("").to_string();
LangZhTw {
cards,
maxval,
one,
ten,
currency_forms: build_currency_forms(),
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
fn zh_to_cap(&self, value: &str) -> String {
let prefix = format!("{}{}", self.one, self.ten);
if value.starts_with(&prefix) {
return value[self.one.len()..].to_string();
}
value.to_string()
}
fn card_digit(&self, d: u32) -> String {
self.cards.get(&BigInt::from(d)).unwrap_or("").to_string()
}
fn to_currency_float(&self, value: &BigInt) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
if !value.is_positive() {
return out;
}
let cents = format!("{:0>2}", value.to_string());
let digits: Vec<u32> = cents
.chars()
.map(|c| c.to_digit(10).expect("%02d of a non-negative int is digits"))
.collect();
let (d0, d1) = (digits[0], digits[1]);
out.push(self.card_digit(d0));
if d0 > 0 {
out.push(CURRENCY_FLOATS[0].to_string());
}
if d1 > 0 {
out.push(self.card_digit(d1));
out.push(CURRENCY_FLOATS[1].to_string());
}
out
}
fn kw_cardinal(
&self,
value: &BigInt,
stuff_zero: i64,
reading: Reading,
prefer: Option<&[String]>,
) -> Result<String> {
let eng = TwKwEngine {
base: self,
cards: resolved_cards(reading, prefer),
negword: select2(NEG_ALTS, reading, prefer),
stuff_zero,
zero_word: select2(TW_LOW[0], reading, prefer),
};
let out = default_to_cardinal(&eng, value)?;
let out = match reading {
Reading::Capital => apply_cap_map(&out, &TW_CAP_MAP),
_ => {
let one = select2(TW_LOW[1], reading, prefer);
let ten = select2(TW_LOW[10], reading, prefer);
let prefix = format!("{}{}", one, ten);
if out.starts_with(&prefix) {
out[one.len()..].to_string()
} else {
out
}
}
};
Ok(out.replace(' ', ""))
}
fn kw_cardinal_float(
&self,
value: &FloatValue,
reading: Reading,
prefer: Option<&[String]>,
) -> Result<String> {
let f: f64 = match value {
FloatValue::Float { value, .. } => *value,
FloatValue::Decimal { value, .. } => value
.to_f64()
.ok_or_else(|| N2WError::Value(format!("cannot represent {} as f64", value)))?,
};
let precision = float_repr_precision(f);
let (pre, post) = float2tuple(&FloatValue::Float {
value: f,
precision,
});
let post_str = post.to_string();
let post_str = format!(
"{}{}",
"0".repeat((precision as usize).saturating_sub(post_str.len())),
post_str
);
let mut out: Vec<String> = vec![self.kw_cardinal(&pre, 2, reading, prefer)?];
let is_negative = match value {
FloatValue::Float { value, .. } => *value < 0.0,
FloatValue::Decimal { value, .. } => value.is_negative(),
};
if is_negative && pre.is_zero() {
out.insert(0, select2(NEG_ALTS, reading, prefer).trim().to_string());
}
if precision > 0 {
out.push(select2(POINT_ALTS, reading, prefer).to_string());
}
for ch in post_str.chars().take(precision as usize) {
let d = ch
.to_digit(10)
.ok_or_else(|| N2WError::Value(format!("non-digit {:?} in fractional part", ch)))?;
out.push(self.kw_cardinal(&BigInt::from(d), 2, reading, prefer)?);
}
let joined = out.join(" ");
let capped = match reading {
Reading::Capital => apply_cap_map(&joined, &TW_CAP_MAP),
_ => {
let one = select2(TW_LOW[1], reading, prefer);
let ten = select2(TW_LOW[10], reading, prefer);
let prefix = format!("{}{}", one, ten);
if joined.starts_with(&prefix) {
joined[one.len()..].to_string()
} else {
joined
}
}
};
Ok(capped.replace(' ', ""))
}
fn kw_zh_to_cap(&self, value: &str, reading: Reading, prefer: Option<&[String]>) -> String {
match reading {
Reading::Capital => apply_cap_map(value, &TW_CAP_MAP),
_ => {
let one = select2(TW_LOW[1], reading, prefer);
let ten = select2(TW_LOW[10], reading, prefer);
let prefix = format!("{}{}", one, ten);
if value.starts_with(&prefix) {
value[one.len()..].to_string()
} else {
value.to_string()
}
}
}
}
fn kw_year(&self, value: &BigInt, reading: Reading, prefer: Option<&[String]>) -> Result<String> {
let mut out = String::new();
if value.is_negative() {
out.push_str(select2(YEAR_PREFIX_ALTS, reading, prefer));
out.push_str(select2(YEAR_BCE_ALTS, reading, prefer));
} else if reading == Reading::Capital {
out.push_str(select2(YEAR_PREFIX_ALTS, reading, prefer));
}
for ch in value.abs().to_string().chars() {
let d = ch.to_digit(10).expect("BigInt::to_string emits digits") as usize;
out.push_str(select2(TW_LOW[d], reading, prefer));
}
out.push_str(select2(YEAR_W_ALTS, reading, prefer));
Ok(out)
}
fn kw_currency_float(
&self,
value: &BigInt,
reading: Reading,
prefer: Option<&[String]>,
) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
if !value.is_positive() {
return out;
}
let cents = format!("{:0>2}", value.to_string());
let digits: Vec<u32> = cents
.chars()
.map(|c| c.to_digit(10).expect("%02d of a non-negative int is digits"))
.collect();
let (d0, d1) = (digits[0], digits[1]);
if !(d0 == 0 && reading == Reading::Capital) {
out.push(select2(TW_LOW[d0 as usize], reading, prefer).to_string());
}
if d0 > 0 {
out.push(CURRENCY_FLOATS[0].to_string());
}
if d1 > 0 {
out.push(select2(TW_LOW[d1 as usize], reading, prefer).to_string());
out.push(CURRENCY_FLOATS[1].to_string());
}
out
}
}
fn dec_len(n: &BigInt) -> usize {
n.to_string().len()
}
fn float_repr_precision(f: f64) -> u32 {
if f == 0.0 {
return 1; }
let sci = format!("{:e}", f.abs()); let (mant, exp) = sci.split_once('e').expect("{:e} always carries an exponent");
let exp10: i64 = exp.parse().expect("{:e} exponent is an integer");
let nd = (mant.len() - usize::from(mant.contains('.'))) as i64;
if exp10 < -4 || exp10 >= 16 {
(exp10 - (nd - 1)).unsigned_abs() as u32
} else if nd - 1 <= exp10 {
1
} else {
(nd - 1 - exp10) as u32
}
}
fn zh_merge(
l: (&str, &BigInt),
r: (&str, &BigInt),
stuff_zero: i64,
zero_word: &str,
) -> (String, BigInt) {
let (ltext, lnum) = l;
let (rtext, rnum) = r;
if lnum.is_one() && rnum < &BigInt::from(10) {
return (rtext.to_string(), rnum.clone());
}
let sum = lnum + rnum;
let with_zero = (format!("{}{}{}", ltext, zero_word, rtext), sum.clone());
let no_zero = (format!("{}{}", ltext, rtext), sum);
let (llen, rlen) = (dec_len(lnum), dec_len(rnum));
if llen as isize - rlen as isize > 1 {
match stuff_zero {
1 => with_zero,
2 => {
if rlen % 4 != 0 {
with_zero
} else {
no_zero
}
}
3 => no_zero,
_ => no_zero,
}
} else if rnum > lnum {
(format!("{}{}", ltext, rtext), lnum * rnum)
} else {
no_zero
}
}
type Alts = (&'static [&'static str], &'static [&'static str]);
const TW_LOW: [Alts; 11] = [
(&["零", "〇"], &["ㄌㄧㄥˊ"]), (&["一", "壹"], &["ㄧ"]), (&["二", "貳"], &["ㄦˋ"]), (&["三", "參"], &["ㄙㄢ"]), (&["四", "肆"], &["ㄙˋ"]), (&["五", "伍"], &["ㄨˇ"]), (&["六", "陸"], &["ㄌㄧㄡˋ"]), (&["七", "柒"], &["ㄑㄧ"]), (&["八", "捌"], &["ㄅㄚ"]), (&["九", "玖"], &["ㄐㄧㄡˇ"]), (&["十", "拾"], &["ㄕˊ"]), ];
const TW_MID: [(i64, Alts); 2] = [
(1000, (&["千"], &["ㄑㄧㄢ"])),
(100, (&["百"], &["ㄅㄞˇ"])),
];
const TW_HIGH: [(u32, Alts); 18] = [
(72, (&["不可說"], &["ㄅㄨˋㄎㄜˇㄕㄨㄛ"])),
(68, (&["無量"], &["ㄨˊㄌㄧㄤˋ"])),
(64, (&["不可思議"], &["ㄅㄨˋㄎㄜˇㄙㄧˋ"])),
(60, (&["那由他"], &["ㄋㄚˋㄧㄡˊㄊㄚ"])),
(56, (&["阿僧祇"], &["ㄚㄙㄥㄑㄧˊ"])),
(52, (&["恆河沙"], &["ㄏㄥˊㄏㄜˊㄕㄚ"])),
(48, (&["極"], &["ㄐㄧˊ"])),
(44, (&["載"], &["ㄗㄞˇ"])),
(40, (&["正"], &["ㄓㄥˋ"])),
(36, (&["澗"], &["ㄐㄧㄢˋ"])),
(32, (&["溝"], &["ㄍㄡ"])),
(28, (&["穣"], &["ㄖㄤ"])),
(24, (&["秭"], &["ㄗˇ"])),
(20, (&["垓"], &["ㄍㄞ"])),
(16, (&["京"], &["ㄐㄧㄥ"])),
(12, (&["兆"], &["ㄓㄠˋ"])),
(8, (&["億"], &["ㄧˋ"])),
(4, (&["萬"], &["ㄨㄢˋ"])),
];
const NEG_ALTS: Alts = (&["負"], &["ㄈㄨˋ"]);
const POINT_ALTS: Alts = (&["點"], &["ㄉㄧㄢˇ"]);
const ORD_ALTS: Alts = (&["第"], &["ㄉㄧˋ"]);
const YEAR_W_ALTS: Alts = (&["年"], &["ㄋㄧㄢˊ"]);
const YEAR_PREFIX_ALTS: Alts = (&["公元", "西元"], &["ㄍㄨㄥ ㄩㄢˊ", "ㄒㄧㄩㄢˊ"]);
const YEAR_BCE_ALTS: Alts = (&["前"], &["ㄑㄧㄢˊ"]);
const ROC_ERA_ALTS: Alts = (&["民國"], &["ㄇㄧㄣˊㄍㄨㄛˊ"]);
const YUAN_ALTS: Alts = (&["元"], &["ㄩㄢˊ"]);
const CHEQUE_ALTS: Alts = (&["正"], &["ㄓㄥˋ"]);
const TW_CAP_MAP: [(&str, &str); 14] = [
("千", "仟"),
("百", "佰"),
("十", "拾"),
("九", "玖"),
("八", "捌"),
("七", "柒"),
("六", "陸"),
("五", "伍"),
("四", "肆"),
("三", "叁"),
("二", "貳"),
("一", "壹"),
("元", "圓"),
("正", "整"),
];
fn tw_counter(counter: &str) -> Option<&'static str> {
match counter {
"個" => Some("˙ㄍㄜ"),
"名" => Some("ㄇㄧㄥˊ"),
"位" => Some("ㄨㄟˋ"),
_ => None,
}
}
#[derive(Clone, Copy, PartialEq)]
enum Reading {
Plain,
True,
Capital,
}
fn parse_reading(kw: &Kwargs) -> Reading {
match kw.get("reading") {
Some(KwVal::Bool(true)) => Reading::True,
Some(KwVal::Str(s)) if s == "capital" => Reading::Capital,
_ => Reading::Plain,
}
}
fn parse_prefer(kw: &Kwargs) -> Result<Option<Vec<String>>> {
match kw.get("prefer") {
None | Some(KwVal::None) => Ok(None),
Some(KwVal::List(l)) => Ok(Some(l.clone())),
Some(KwVal::Str(s)) => Ok(Some(s.chars().map(|c| c.to_string()).collect())),
Some(_) => Err(N2WError::Fallback("kwargs".into())),
}
}
fn parse_stuff_zero(kw: &Kwargs) -> i64 {
match kw.get("stuff_zero") {
None => 2,
Some(KwVal::Int(i)) => *i,
Some(KwVal::Bool(b)) => *b as i64,
Some(_) => 0,
}
}
fn parse_counter(kw: &Kwargs) -> Result<&str> {
match kw.get("counter") {
None => Ok(""),
Some(KwVal::Str(s)) => Ok(s),
Some(_) => Err(N2WError::Fallback("kwargs".into())),
}
}
fn kw_truthy(v: Option<&KwVal>) -> bool {
match v {
None | Some(KwVal::None) => false,
Some(KwVal::Bool(b)) => *b,
Some(KwVal::Int(i)) => *i != 0,
Some(KwVal::Str(s)) => !s.is_empty(),
Some(KwVal::List(l)) => !l.is_empty(),
}
}
fn select_alt<'a>(alts: &[&'a str], prefer: Option<&[String]>) -> &'a str {
if let Some(p) = prefer {
let hits: Vec<&str> = alts
.iter()
.copied()
.filter(|a| p.iter().any(|x| x == a))
.collect();
if hits.len() == 1 {
return hits[0];
}
}
alts[0]
}
fn select2(alts: Alts, reading: Reading, prefer: Option<&[String]>) -> &'static str {
let side = if reading == Reading::True { alts.1 } else { alts.0 };
select_alt(side, prefer)
}
fn apply_cap_map(value: &str, map: &[(&str, &str)]) -> String {
let mut out = value.to_string();
for (plain, cap) in map {
if out.contains(plain) {
out = out.replace(plain, cap);
}
}
out
}
fn resolved_cards(reading: Reading, prefer: Option<&[String]>) -> Cards {
let mut cards = Cards::new();
for (exp, alts) in TW_HIGH.iter() {
cards.insert(BigInt::from(10u8).pow(*exp), select2(*alts, reading, prefer));
}
for (v, alts) in TW_MID.iter() {
cards.insert(BigInt::from(*v), select2(*alts, reading, prefer));
}
for (d, alts) in TW_LOW.iter().enumerate() {
cards.insert(BigInt::from(d), select2(*alts, reading, prefer));
}
cards
}
struct TwKwEngine<'a> {
base: &'a LangZhTw,
cards: Cards,
negword: &'static str,
stuff_zero: i64,
zero_word: &'static str,
}
impl Lang for TwKwEngine<'_> {
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
self.base.maxval()
}
fn negword(&self) -> &str {
self.negword
}
fn pointword(&self) -> &str {
self.base.pointword()
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
zh_merge(l, r, self.stuff_zero, self.zero_word)
}
}
fn py_float_str(value: f64, precision: u32) -> String {
let a = value.abs();
let mag = if a != 0.0 && (a >= 1e16 || a < 1e-4) {
let s = format!("{:e}", a);
let (mant, exp) = s.split_once('e').expect("LowerExp always emits an e");
let exp: i32 = exp.parse().expect("exponent is a small int");
format!(
"{}e{}{:02}",
mant,
if exp < 0 { '-' } else { '+' },
exp.abs()
)
} else {
format!("{:.*}", precision as usize, a)
};
if value.is_sign_negative() {
format!("-{}", mag)
} else {
mag
}
}
fn py_value_str(value: &FloatValue) -> String {
match value {
FloatValue::Float { value, precision } => py_float_str(*value, *precision),
FloatValue::Decimal { value, .. } => crate::strnum::python_decimal_str(value),
}
}
impl Lang for LangZhTw {
fn default_currency(&self) -> &str {
"XXX"
}
fn default_separator(&self) -> &str {
""
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
POINTWORD
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
zh_merge(l, r, STUFF_ZERO as i64, ZERO_WORD)
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let out = default_to_cardinal(self, value)?;
let out = self.zh_to_cap(&out);
Ok(out.replace(' ', ""))
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let base = self.to_cardinal(value)?;
Ok(format!("{}{}", ORD_PREFIX, base))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}{}", ORD_PREFIX, value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
let mut out = String::new();
if value.is_negative() {
out.push_str(YEAR_PREFIX);
out.push_str(YEAR_BCE);
}
for ch in value.abs().to_string().chars() {
let digit = BigInt::from(ch.to_digit(10).expect("BigInt::to_string emits digits"));
out.push_str(self.cards.get(&digit).unwrap_or(""));
}
out.push_str(YEAR);
Ok(out)
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let f: f64 = match value {
FloatValue::Float { value, .. } => *value,
FloatValue::Decimal { value, .. } => value
.to_f64()
.ok_or_else(|| N2WError::Value(format!("cannot represent {} as f64", value)))?,
};
let precision = float_repr_precision(f);
let (pre, post) = float2tuple(&FloatValue::Float {
value: f,
precision,
});
let post_str = post.to_string();
let post_str = format!(
"{}{}",
"0".repeat((precision as usize).saturating_sub(post_str.len())),
post_str
);
let mut out: Vec<String> = vec![self.to_cardinal(&pre)?];
let is_negative = match value {
FloatValue::Float { value, .. } => *value < 0.0,
FloatValue::Decimal { value, .. } => value.is_negative(),
};
if is_negative && pre.is_zero() {
out.insert(0, NEGWORD.to_string());
}
if precision > 0 {
out.push(POINTWORD.to_string());
}
for ch in post_str.chars().take(precision as usize) {
let d = ch
.to_digit(10)
.ok_or_else(|| N2WError::Value(format!("non-digit {:?} in fractional part", ch)))?;
out.push(self.to_cardinal(&BigInt::from(d))?);
}
let joined = out.join(" ");
Ok(self.zh_to_cap(&joined).replace(' ', ""))
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let Some(i) = value.as_whole_int() else {
return Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
py_value_str(value)
)));
};
if i.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
py_value_str(value)
)));
}
Ok(format!("{}{}", ORD_PREFIX, self.to_cardinal(&i)?))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}{}", ORD_PREFIX, repr_str))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
let Some(i) = value.as_whole_int() else {
return Err(N2WError::Type(format!(
"Cannot treat float {} as year.",
py_value_str(value)
)));
};
self.to_year(&i)
}
fn to_fraction(&self, numerator: &BigInt, denominator: &BigInt) -> Result<String> {
if denominator.is_zero() {
return Err(N2WError::ZeroDivision(
"denominator must not be zero".into(),
));
}
if denominator == &BigInt::one() || numerator.is_zero() {
return self.to_cardinal(numerator);
}
let is_negative = numerator.is_negative() ^ denominator.is_negative();
if is_negative {
return Err(N2WError::Attribute(
"'tuple' object has no attribute 'strip'".into(),
));
}
let num_word = self.to_cardinal(&numerator.abs())?;
let mut den_word = self.to_ordinal(&denominator.abs())?;
if numerator.abs() != BigInt::one() {
den_word.push('s'); }
Ok(format!("{} {}", num_word, den_word))
}
fn lang_name(&self) -> &str {
"Num2Word_ZH_TW"
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
_cents: bool,
_separator: Option<&str>,
_adjective: bool,
) -> Result<String> {
let (left, right, is_negative) = parse_currency_parts(val, false, false, 100);
let form = *self.currency_forms.get(currency).ok_or_else(|| {
N2WError::NotImplemented(format!(
"Currency code \"{}\" not implemented for \"{}\"",
currency,
self.lang_name()
))
})?;
let minus_str = if is_negative { NEGWORD } else { "" };
let money_str = self.to_cardinal(&left)?;
let cr_pre: &str = if currency == "XXX" {
""
} else {
match form {
Form::Name(name) => name,
Form::Nested => {
return Err(N2WError::Type(
"can only concatenate tuple (not \"str\") to tuple".into(),
))
}
}
};
let cents_str = self.to_currency_float(&right.as_bigint_and_exponent().0);
let mut out = String::from(cr_pre);
out.push_str(&self.zh_to_cap(minus_str));
out.push_str(&self.zh_to_cap(&money_str));
out.push_str(&self.zh_to_cap(XXX_UNIT));
for c in ¢s_str {
out.push_str(&self.zh_to_cap(c));
}
Ok(out)
}
fn to_cheque(&self, val: &BigDecimal, currency: &str) -> Result<String> {
let form = *self.currency_forms.get(currency).ok_or_else(|| {
N2WError::NotImplemented(format!(
"Currency code \"{}\" not implemented for \"{}\"",
currency,
self.lang_name()
))
})?;
let unit: String = match form {
Form::Nested => XXX_UNIT.to_string(),
Form::Name(name) => match name.chars().count() {
2 => name
.chars()
.next()
.expect("a 2-char name has a first char")
.to_string(),
n if n > 2 => {
return Err(N2WError::Value(
"too many values to unpack (expected 2)".into(),
))
}
n => {
return Err(N2WError::Value(format!(
"not enough values to unpack (expected 2, got {})",
n
)))
}
},
};
let divisor = self.currency_precision(currency);
let is_negative = val.is_negative();
let abs_val = val.abs();
let whole = abs_val.with_scale(0).as_bigint_and_exponent().0;
let fraction_str = if divisor > 1 {
let sub = (&abs_val - BigDecimal::from(whole.clone())) * BigDecimal::from(divisor);
let sub = sub.with_scale(0).as_bigint_and_exponent().0;
let digits = divisor.to_string().len() - 1;
format!("{:0>width$}/{}", sub.to_string(), divisor, width = digits)
} else {
String::new()
};
let words = self.money_verbose(&whole, currency)?;
let sign = if is_negative { "MINUS " } else { "" };
let body = if fraction_str.is_empty() {
format!("{} {}", words, unit)
} else {
format!("{} AND {} {}", words, fraction_str, unit)
};
Ok(format!("{}{}", sign, body).to_uppercase())
}
fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if kw.is_empty() {
return self.to_cardinal(value);
}
if !kw.only(&["stuff_zero", "reading", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let reading = parse_reading(kw);
let prefer = parse_prefer(kw)?;
self.kw_cardinal(value, parse_stuff_zero(kw), reading, prefer.as_deref())
}
fn to_ordinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if kw.is_empty() {
return self.to_ordinal(value);
}
if !kw.only(&["counter", "reading", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let reading = parse_reading(kw);
let prefer = parse_prefer(kw)?;
let mut counter = parse_counter(kw)?;
if reading == Reading::True {
match tw_counter(counter) {
Some(c) => counter = c,
None if !counter.is_empty() => {
return Err(N2WError::NotImplemented(format!(
"Reading not implemented for {}",
counter
)));
}
None => counter = "",
}
}
self.verify_ordinal(value)?;
let base = self.kw_cardinal(value, STUFF_ZERO as i64, reading, prefer.as_deref())?;
Ok(format!(
"{}{}{}",
select2(ORD_ALTS, reading, prefer.as_deref()),
base,
counter
))
}
fn to_ordinal_num_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if kw.is_empty() {
return self.to_ordinal_num(value);
}
if !kw.only(&["counter", "reading", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let reading = parse_reading(kw);
let prefer = parse_prefer(kw)?;
let mut counter = parse_counter(kw)?;
if reading == Reading::True {
match tw_counter(counter) {
Some(c) => counter = c,
None if !counter.is_empty() => {
return Err(N2WError::NotImplemented(format!(
"Reading not implemented for {}",
counter
)));
}
None => counter = "",
}
}
Ok(format!(
"{}{}{}",
select2(ORD_ALTS, reading, prefer.as_deref()),
value,
counter
))
}
fn to_year_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if kw.is_empty() {
return self.to_year(value);
}
if !kw.only(&["era", "reading", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let reading = parse_reading(kw);
let prefer = parse_prefer(kw)?;
if !kw_truthy(kw.get("era")) {
return self.kw_year(value, reading, prefer.as_deref());
}
let min_year = BigInt::from(1912);
if value < &min_year {
return Err(N2WError::Value(
"Can't convert years less than 1912 to ROC era".into(),
));
}
let era_year = value - BigInt::from(1911);
let mut tail_reading = reading;
let mut tail_prefer = prefer.clone();
let era_year_words: String = if kw.str("reading") == Some("arabic") {
era_year.to_string()
} else if era_year.is_one() {
select2(YUAN_ALTS, reading, prefer.as_deref()).to_string()
} else if era_year < BigInt::from(101) {
tail_reading = Reading::Plain;
tail_prefer = None;
self.to_cardinal(&era_year)?
} else {
era_year
.to_string()
.chars()
.map(|c| {
select2(
TW_LOW[c.to_digit(10).expect("BigInt digits") as usize],
reading,
prefer.as_deref(),
)
})
.collect()
};
Ok(format!(
"{}{}{}",
select2(ROC_ERA_ALTS, tail_reading, tail_prefer.as_deref()),
era_year_words,
select2(YEAR_W_ALTS, tail_reading, tail_prefer.as_deref())
))
}
fn to_currency_kw(
&self,
val: &CurrencyValue,
currency: &str,
_cents: bool,
_separator: Option<&str>,
_adjective: bool,
kw: &Kwargs,
) -> Result<String> {
if kw.is_empty() {
return self.to_currency(val, currency, _cents, _separator, _adjective);
}
if !kw.only(&["reading", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let reading = parse_reading(kw);
let prefer = parse_prefer(kw)?;
let prefer = prefer.as_deref();
let (left, right, is_negative) = parse_currency_parts(val, false, false, 100);
let form = *self.currency_forms.get(currency).ok_or_else(|| {
N2WError::NotImplemented(format!(
"Currency code \"{}\" not implemented for \"{}\"",
currency,
self.lang_name()
))
})?;
let minus_str = if is_negative {
select2(NEG_ALTS, reading, prefer)
} else {
""
};
let money_str = self.kw_cardinal(&left, STUFF_ZERO as i64, reading, prefer)?;
let cr_pre: &str = if currency == "XXX" {
""
} else {
match form {
Form::Name(name) => name,
Form::Nested => {
return Err(N2WError::Type(
"can only concatenate tuple (not \"str\") to tuple".into(),
))
}
}
};
let cr_post = select2(YUAN_ALTS, reading, prefer);
let cents_str = self.kw_currency_float(&right.as_bigint_and_exponent().0, reading, prefer);
let cheque = if cents_str.is_empty() && reading == Reading::Capital {
select2(CHEQUE_ALTS, reading, prefer)
} else {
""
};
let mut out = String::from(cr_pre);
out.push_str(&self.kw_zh_to_cap(minus_str, reading, prefer));
out.push_str(&self.kw_zh_to_cap(&money_str, reading, prefer));
out.push_str(&self.kw_zh_to_cap(cr_post, reading, prefer));
for c in ¢s_str {
out.push_str(&self.kw_zh_to_cap(c, reading, prefer));
}
out.push_str(&self.kw_zh_to_cap(cheque, reading, prefer));
Ok(out)
}
fn to_cardinal_float_kw(
&self,
value: &FloatValue,
precision_override: Option<u32>,
kw: &Kwargs,
) -> Result<String> {
if kw.is_empty() {
return self.cardinal_float_entry(value, precision_override);
}
if !kw.only(&["stuff_zero", "reading", "prefer"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let reading = parse_reading(kw);
let prefer = parse_prefer(kw)?;
if let Some(i) = value.as_whole_int() {
return self.kw_cardinal(&i, parse_stuff_zero(kw), reading, prefer.as_deref());
}
self.kw_cardinal_float(value, reading, prefer.as_deref())
}
}