use crate::base::{Lang, N2WError, Result};
use crate::currency::{default_to_currency, CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const ZERO: &str = "zero";
const MINUS_PREFIX_WORD: &str = "meno ";
const CARDINAL_WORDS: [&str; 20] = [
ZERO,
"uno",
"due",
"tre",
"quattro",
"cinque",
"sei",
"sette",
"otto",
"nove",
"dieci",
"undici",
"dodici",
"tredici",
"quattordici",
"quindici",
"sedici",
"diciassette",
"diciotto",
"diciannove",
];
const ORDINAL_WORDS: [&str; 20] = [
ZERO,
"primo",
"secondo",
"terzo",
"quarto",
"quinto",
"sesto",
"settimo",
"ottavo",
"nono",
"decimo",
"undicesimo",
"dodicesimo",
"tredicesimo",
"quattordicesimo",
"quindicesimo",
"sedicesimo",
"diciassettesimo",
"diciottesimo",
"diciannovesimo",
];
const EXPONENT: [&str; 11] = [
ZERO, "m", "b", "tr", "quadr", "quint", "sest", "sett", "ott", "nov", "dec",
];
fn drop_last_chars(s: &str, n: usize) -> String {
let cs: Vec<char> = s.chars().collect();
if cs.len() <= n {
String::new()
} else {
cs[..cs.len() - n].iter().collect()
}
}
fn last_chars(s: &str, n: usize) -> String {
let cs: Vec<char> = s.chars().collect();
cs[cs.len().saturating_sub(n)..].iter().collect()
}
fn omitt_if_zero(number_to_string: &str) -> &str {
if number_to_string == ZERO {
""
} else {
number_to_string
}
}
fn accentuate(string: &str) -> String {
string
.split_whitespace()
.map(|w| {
if last_chars(w, 3) == "tre" && w.chars().count() > 3 {
drop_last_chars(&w.replace("tré", "tre"), 3) + "tré"
} else {
w.replace("tré", "tre")
}
})
.collect::<Vec<_>>()
.join(" ")
}
fn phonetic_contraction(string: &str) -> String {
if string.contains("diciotto") {
return string.to_string();
}
string
.replace("oo", "o") .replace("ao", "o") .replace("io", "o") .replace("au", "u") .replace("iu", "u") }
fn exponent_length_to_string(exponent_length: usize) -> String {
let prefix = EXPONENT[(exponent_length / 6) % 5];
if exponent_length == 3 {
"mila".to_string()
} else if exponent_length % 6 == 0 {
format!("{}ilione", prefix)
} else {
format!("{}iliardo", prefix)
}
}
fn attribute_error(msg: &str) -> N2WError {
N2WError::Attribute(msg.to_string())
}
fn value_error(msg: &str) -> N2WError {
N2WError::Value(msg.to_string())
}
fn float_repr_precision(f: f64) -> u32 {
let s = format!("{}", f);
match s.split_once('.') {
Some((_, frac)) if !frac.contains('e') => frac.len() as u32,
_ => 0,
}
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
const DOLLARS: [&str; 2] = ["dollaro", "dollari"];
const CENTS: [&str; 2] = ["centesimo", "centesimi"];
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert("EUR", CurrencyForms::new(&["euro", "euro"], &CENTS));
m.insert("USD", CurrencyForms::new(&DOLLARS, &CENTS));
m.insert("CAD", CurrencyForms::new(&DOLLARS, &CENTS));
m.insert("AUD", CurrencyForms::new(&DOLLARS, &CENTS));
m.insert("NZD", CurrencyForms::new(&DOLLARS, &CENTS));
m.insert("HKD", CurrencyForms::new(&DOLLARS, &CENTS));
m.insert(
"GBP",
CurrencyForms::new(&["sterlina", "sterline"], &["penny", "penny"]),
);
m.insert(
"CNY",
CurrencyForms::new(&["yuan", "yuan"], &["fen", "fen"]),
);
m.insert("CHF", CurrencyForms::new(&["franco", "franchi"], &CENTS));
m.insert("JPY", CurrencyForms::new(&["yen", "yen"], &["sen", "sen"]));
m.insert(
"INR",
CurrencyForms::new(&["rupia", "rupie"], &["paisa", "paise"]),
);
m.insert(
"RUB",
CurrencyForms::new(&["rublo", "rubli"], &["copeco", "copechi"]),
);
m.insert(
"KRW",
CurrencyForms::new(&["won", "won"], &["jeon", "jeon"]),
);
m.insert("MXN", CurrencyForms::new(&["peso", "pesos"], &CENTS));
m
}
fn build_currency_adjectives() -> HashMap<&'static str, &'static str> {
[
("AUD", "Australian"),
("BYN", "Belarusian"),
("CAD", "Canadian"),
("EEK", "Estonian"),
("USD", "US"),
("RUB", "Russian"),
("NOK", "Norwegian"),
("MXN", "Mexican"),
("RON", "Romanian"),
("INR", "Indian"),
("HUF", "Hungarian"),
("ISK", "íslenskar"),
("UZS", "Uzbekistan"),
("SAR", "Saudi"),
("JPY", "Japanese"),
("KRW", "Korean"),
]
.into_iter()
.collect()
}
pub struct LangIt {
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl LangIt {
pub fn new() -> Self {
LangIt {
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
}
}
fn tens_to_cardinal(&self, number: u32) -> String {
let tens = (number / 10) as usize;
let units = (number % 10) as usize;
let prefix = match tens {
2 => "venti".to_string(),
3 => "trenta".to_string(),
4 => "quaranta".to_string(),
6 => "sessanta".to_string(),
_ => drop_last_chars(CARDINAL_WORDS[tens], 1) + "anta",
};
let postfix = omitt_if_zero(CARDINAL_WORDS[units]);
phonetic_contraction(&format!("{}{}", prefix, postfix))
}
fn hundreds_to_cardinal(&self, number: u32) -> Result<String> {
let hundreds = (number / 100) as usize;
let mut prefix = "cento".to_string();
if hundreds != 1 {
prefix = format!("{}{}", CARDINAL_WORDS[hundreds], prefix);
}
let inner = self.cardinal(&BigInt::from(number % 100))?;
let postfix = omitt_if_zero(&inner);
Ok(phonetic_contraction(&format!("{}{}", prefix, postfix)))
}
fn thousands_to_cardinal(&self, number: u32) -> Result<String> {
let thousands = number / 1000;
let prefix = if thousands == 1 {
"mille".to_string()
} else {
format!("{}mila", self.cardinal(&BigInt::from(thousands))?)
};
let inner = self.cardinal(&BigInt::from(number % 1000))?;
let postfix = omitt_if_zero(&inner);
Ok(format!("{}{}", prefix, postfix))
}
fn big_number_to_cardinal(&self, number: &BigInt) -> Result<String> {
let digits: Vec<char> = number.to_string().chars().collect();
let length = digits.len();
if length >= 66 {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
let predigits = if length % 3 != 0 { length % 3 } else { 3 };
let multiplier: String = digits[..predigits].iter().collect();
let exponent: String = digits[predigits..].iter().collect();
let mut infix = exponent_length_to_string(exponent.len());
let prefix = if multiplier == "1" {
"un ".to_string()
} else {
let p = self.cardinal(
&multiplier
.parse::<BigInt>()
.map_err(|_| value_error("invalid literal for int() with base 10"))?,
)?;
infix = format!(" {}i", drop_last_chars(&infix, 1));
p
};
let exponent_is_zero = !exponent.is_empty() && exponent.chars().all(|c| c == '0');
let postfix = if !exponent_is_zero {
let p = self.cardinal(
&exponent
.parse::<BigInt>()
.map_err(|_| value_error("invalid literal for int() with base 10: ''"))?,
)?;
if p.contains(" e ") {
infix.push_str(", ");
} else {
infix.push_str(" e ");
}
p
} else {
String::new()
};
Ok(format!("{}{}{}", prefix, infix, postfix))
}
fn cardinal(&self, number: &BigInt) -> Result<String> {
if number.is_negative() {
let positive_part = self.cardinal(&-number)?;
return Ok(format!("{}{}", MINUS_PREFIX_WORD, positive_part));
}
let string = if number < &BigInt::from(20) {
CARDINAL_WORDS[number.to_usize().expect("0 <= number < 20")].to_string()
} else if number < &BigInt::from(100) {
self.tens_to_cardinal(number.to_u32().expect("20 <= number < 100"))
} else if number < &BigInt::from(1000) {
self.hundreds_to_cardinal(number.to_u32().expect("100 <= number < 1000"))?
} else if number < &BigInt::from(1_000_000) {
self.thousands_to_cardinal(number.to_u32().expect("1000 <= number < 10**6"))?
} else {
self.big_number_to_cardinal(number)?
};
Ok(accentuate(&string))
}
fn ordinal(&self, number: &BigInt) -> Result<String> {
if number.is_negative() {
return Ok(format!("{}{}", MINUS_PREFIX_WORD, self.ordinal(&-number)?));
}
let tens = (number % BigInt::from(100))
.to_u32()
.expect("0 <= tens < 100");
let is_outside_teens = !(10 < tens && tens < 20);
if number < &BigInt::from(20) {
Ok(ORDINAL_WORDS[number.to_usize().expect("0 <= number < 20")].to_string())
} else if is_outside_teens && tens % 10 == 3 {
Ok(drop_last_chars(&self.cardinal(number)?, 1) + "eesimo")
} else if is_outside_teens && tens % 10 == 6 {
Ok(self.cardinal(number)? + "esimo")
} else {
let mut string = drop_last_chars(&self.cardinal(number)?, 1);
if last_chars(&string, 3) == "mil" {
string.push('l');
}
Ok(string + "esimo")
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(attribute_error(
"'Num2Word_IT' object has no attribute 'errmsg_negord'",
));
}
Ok(())
}
fn float_ordinal_body_f64(&self, x: f64, precision: u32) -> Result<String> {
let s = format!("{:.*}", precision as usize, x);
let (int_str, frac) = match s.split_once('.') {
Some(p) => p,
None => return Err(N2WError::Index("list index out of range".into())),
};
let pre: BigInt = int_str
.parse()
.map_err(|_| value_error("invalid literal for int() with base 10"))?;
let prefix = self.ordinal(&pre)?;
let mut parts: Vec<String> = Vec::new();
for c in frac.chars() {
let d = c
.to_digit(10)
.ok_or_else(|| value_error("invalid literal for int() with base 10"))?;
parts.push(self.cardinal(&BigInt::from(d))?);
}
Ok(format!("{} virgola {}", prefix, parts.join(" ")))
}
fn float_body_f64(&self, x: f64, precision: u32) -> Result<String> {
if x == x.trunc() {
let int_part: BigInt = format!("{:.0}", x)
.parse()
.map_err(|_| value_error("invalid literal for int() with base 10"))?;
return self.cardinal(&int_part);
}
let s = format!("{:.*}", precision as usize, x);
let (int_str, frac) = match s.split_once('.') {
Some(p) => p,
None => return Err(N2WError::Index("list index out of range".into())),
};
let pre: BigInt = int_str
.parse()
.map_err(|_| value_error("invalid literal for int() with base 10"))?;
let prefix = self.cardinal(&pre)?;
let mut parts: Vec<String> = Vec::new();
for c in frac.chars() {
let d = c
.to_digit(10)
.ok_or_else(|| value_error("invalid literal for int() with base 10"))?;
parts.push(self.cardinal(&BigInt::from(d))?);
}
let raw = format!("{} virgola {}", prefix, parts.join(" "));
Ok(accentuate(&raw))
}
fn float_body_dec(&self, x: &BigDecimal, precision: u32) -> Result<String> {
let scaled = x.with_scale(precision as i64);
let coeff = scaled.as_bigint_and_exponent().0; let divisor = BigInt::from(10).pow(precision);
let int_part = &coeff / &divisor;
let frac_val = &coeff % &divisor;
if frac_val.is_zero() {
return self.cardinal(&int_part);
}
let prefix = self.cardinal(&int_part)?;
let frac_raw = frac_val.to_string();
let frac_str = format!(
"{}{}",
"0".repeat((precision as usize).saturating_sub(frac_raw.len())),
frac_raw
);
let mut parts: Vec<String> = Vec::new();
for c in frac_str.chars() {
let d = c.to_digit(10).expect("frac_str is all decimal digits");
parts.push(self.cardinal(&BigInt::from(d))?);
}
let raw = format!("{} virgola {}", prefix, parts.join(" "));
Ok(accentuate(&raw))
}
}
impl Default for LangIt {
fn default() -> Self {
LangIt::new()
}
}
impl Lang for LangIt {
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
" e"
}
fn negword(&self) -> &str {
"meno"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.cardinal(value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.ordinal(value)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(value.to_string())
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let neg = value.is_negative();
let body = match value {
FloatValue::Float { value, precision } => {
self.float_body_f64(value.abs(), *precision)?
}
FloatValue::Decimal { value, precision } => {
self.float_body_dec(&value.abs(), *precision)?
}
};
if neg {
Ok(format!("{}{}", MINUS_PREFIX_WORD, body))
} else {
Ok(body)
}
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let strictly_negative = match value {
FloatValue::Float { value, .. } => *value < 0.0,
FloatValue::Decimal { value, .. } => value.is_negative(),
};
if strictly_negative {
let abs = match value {
FloatValue::Float { value, precision } => FloatValue::Float {
value: -*value,
precision: *precision,
},
FloatValue::Decimal { value, precision } => FloatValue::Decimal {
value: -value.clone(),
precision: *precision,
},
};
return Ok(format!(
"{}{}",
MINUS_PREFIX_WORD,
self.ordinal_float_entry(&abs)?
));
}
match value {
FloatValue::Float {
value: v,
precision,
} if v.fract() != 0.0 => self.float_ordinal_body_f64(*v, *precision),
FloatValue::Float { value: v, .. } => {
let i = BigInt::from_f64(v.trunc())
.ok_or_else(|| value_error("cannot convert float infinity to integer"))?;
self.ordinal(&i)
}
FloatValue::Decimal { value: d, .. } => {
self.ordinal(&d.with_scale(0).as_bigint_and_exponent().0)
}
}
}
fn ordinal_num_float_entry(&self, value: &FloatValue, _repr_str: &str) -> Result<String> {
match value.as_whole_int() {
None => Err(attribute_error(
"'Num2Word_IT' object has no attribute 'errmsg_floatord'",
)),
Some(i) if i.is_negative() => Err(attribute_error(
"'Num2Word_IT' object has no attribute 'errmsg_negord'",
)),
Some(i) => Ok(i.to_string()),
}
}
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.cardinal(numerator);
}
let is_negative = numerator.is_negative() ^ denominator.is_negative();
let abs_n = numerator.abs();
let abs_d = denominator.abs();
let den_word = if abs_d == BigInt::from(2) {
if abs_n.is_one() { "mezzo" } else { "mezzi" }.to_string()
} else {
let mut den = self.ordinal(&abs_d)?;
if !abs_n.is_one() && den.ends_with('o') {
den.pop();
den.push('i');
}
den
};
let num_word = if abs_n.is_one() {
"un".to_string()
} else {
self.cardinal(&abs_n)?
};
let sign = if is_negative { "meno " } else { "" };
Ok(format!("{}{} {}", sign, num_word, den_word))
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
match python_decimal_parse(s)? {
ParsedNumber::NaN => Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
msg: "[<class 'decimal.InvalidOperation'>]".into(),
}),
other => Ok(other),
}
}
fn cardinal_from_decimal(&self, value: &BigDecimal) -> Result<String> {
let f = value
.to_f64()
.ok_or_else(|| N2WError::Value(format!("cannot represent {} as f64", value)))?;
let precision = float_repr_precision(f);
self.to_cardinal_float(
&FloatValue::Float {
value: f,
precision,
},
None,
)
}
fn lang_name(&self) -> &str {
"Num2Word_IT"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn currency_adjective(&self, code: &str) -> Option<&str> {
self.currency_adjectives.get(code).copied()
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
let form = if n.is_one() { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
adjective: bool,
) -> Result<String> {
let separator = separator.unwrap_or(self.default_separator());
if let CurrencyValue::Int(v) = val {
let forms = match self.currency_forms.get(currency) {
Some(f) => f,
None => {
return default_to_currency(self, val, currency, cents, separator, adjective)
}
};
let cr1 = &forms.unit;
let minus = v.is_negative();
let abs_val = v.abs();
let money_str = self.cardinal(&abs_val)?;
let currency_str = if abs_val.is_one() {
cr1.first()
} else {
cr1.get(1).or_else(|| cr1.first())
}
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))?;
let result = if minus {
format!("meno {} {}", money_str, currency_str)
} else {
format!("{} {}", money_str, currency_str)
};
return Ok(result.trim().to_string());
}
default_to_currency(self, val, currency, cents, separator, adjective)
}
}