Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/tn/en/telephone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
//! - "+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<String> {
let trimmed = input.trim();
if trimmed.is_empty() {
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();
Expand Down Expand Up @@ -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<String> {
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::*;
Expand Down
1 change: 1 addition & 0 deletions src/tn/en/whitelist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tests/data/en/tn_telephone.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion tests/parity_baseline.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading