diff --git a/src/tn/en/telephone.rs b/src/tn/en/telephone.rs index c81a78f..bcf1a84 100644 --- a/src/tn/en/telephone.rs +++ b/src/tn/en/telephone.rs @@ -5,6 +5,8 @@ //! - "+1-234-567-8901" → "plus one, two three four, five six seven, eight nine zero one" //! - "(555) 123-4567" → "five five five, one two three, four five six seven" +use super::number_to_words; + /// Parse a written phone number to spoken form. pub fn parse(input: &str) -> Option { let trimmed = input.trim(); @@ -12,6 +14,12 @@ pub fn parse(input: &str) -> Option { return None; } + // Vanity numbers mix digit and letter groups ("1-800-GO-U-HAUL"). + if let Some(vanity) = parse_vanity(trimmed) { + return Some(vanity); + } + + // (vanity handled above) // Phone numbers contain digits and separators (-, ., space, parens) // Must have mostly digits let digit_count = trimmed.chars().filter(|c| c.is_ascii_digit()).count(); @@ -126,6 +134,50 @@ fn spell_digit_group(group: &str) -> String { .join(" ") } +/// Read a vanity number that mixes digit and letter groups +/// ("1-800-GO-U-HAUL" → "one, eight hundred, GO U HAUL"). Digit groups read as +/// cardinals followed by ", "; letter groups are kept and space-separated. +fn parse_vanity(input: &str) -> Option { + if !input.contains('-') { + return None; + } + let groups: Vec<&str> = input.split('-').collect(); + if groups.len() < 2 { + return None; + } + let mut has_digit = false; + let mut has_letter = false; + for g in &groups { + if g.is_empty() { + return None; + } + if g.chars().all(|c| c.is_ascii_digit()) { + has_digit = true; + } else if g.chars().all(|c| c.is_ascii_alphabetic()) { + has_letter = true; + } else { + return None; // mixed-content group is not a vanity number + } + } + if !(has_digit && has_letter) { + return None; + } + + let mut out = String::new(); + for (i, g) in groups.iter().enumerate() { + let is_digit = g.chars().all(|c| c.is_ascii_digit()); + if is_digit { + out.push_str(&number_to_words(g.parse().ok()?)); + } else { + out.push_str(g); + } + if i + 1 < groups.len() { + out.push_str(if is_digit { ", " } else { " " }); + } + } + Some(out) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/tn/en/whitelist.rs b/src/tn/en/whitelist.rs index 37c5077..54b2a14 100644 --- a/src/tn/en/whitelist.rs +++ b/src/tn/en/whitelist.rs @@ -60,6 +60,7 @@ lazy_static! { // Misc special terms m.insert("tv", "TV"); + m.insert("ssn", "SSN"); m.insert("401(k)", "four oh one k"); m diff --git a/tests/data/en/tn_telephone.txt b/tests/data/en/tn_telephone.txt index 660c6d0..570a525 100644 --- a/tests/data/en/tn_telephone.txt +++ b/tests/data/en/tn_telephone.txt @@ -6,3 +6,5 @@ # IPv4 (four numeric groups) reads digit-by-digit joined by "dot". 987.987.0.40~nine eight seven dot nine eight seven dot zero dot four zero 123.123.0.40~one two three dot one two three dot zero dot four zero +1-800-GO-U-HAUL~one, eight hundred, GO U HAUL +1-800-GO-2-HAUL~one, eight hundred, GO two, HAUL diff --git a/tests/parity_baseline.tsv b/tests/parity_baseline.tsv index 8943b03..4fa2b44 100644 --- a/tests/parity_baseline.tsv +++ b/tests/parity_baseline.tsv @@ -64,7 +64,7 @@ en tn range 19 20 en tn roman 4 4 en tn serial 8 32 en tn special_text 9 10 -en tn telephone 17 20 +en tn telephone 20 20 en tn time 21 21 en tn whitelist 7 7 en tn word 38 39