use std::borrow::Cow;
use crate::runner::aligner::normalizer::{NormalizationError, NormalizedText, TextNormalizer};
#[derive(Default, Clone, Copy, Debug)]
pub struct ChineseNormalizer;
impl ChineseNormalizer {
pub const fn new() -> Self {
Self
}
}
fn is_cjk_punct(c: char) -> bool {
matches!(
c,
'\u{3002}' | '\u{FF0C}' | '\u{FF01}' | '\u{FF1F}' | '\u{FF1B}' | '\u{FF1A}' | '\u{2026}' | '\u{300C}' | '\u{300D}' | '\u{300E}' | '\u{300F}' | '\u{FF08}' | '\u{FF09}' | '\u{3001}' | '\u{30FB}' )
}
fn is_ascii_punct(c: char) -> bool {
matches!(
c,
'.' | ',' | '!' | '?' | ';' | ':' | '"' | '\'' | '(' | ')' | '[' | ']' | '{' | '}' | '-'
)
}
fn is_punct_either(c: char) -> bool {
is_cjk_punct(c) || is_ascii_punct(c)
}
impl TextNormalizer for ChineseNormalizer {
fn use_word_delimiter(&self) -> bool {
false
}
fn normalize<'a>(&self, text: &'a str) -> Result<NormalizedText<'a>, NormalizationError> {
let mut normalized = String::with_capacity(text.len());
let mut original_words: Vec<Cow<'a, str>> = Vec::new();
for c in text.chars() {
if c.is_whitespace() || is_punct_either(c) {
continue;
}
let lowered: String = c.to_lowercase().collect();
if !normalized.is_empty() {
normalized.push(' ');
}
normalized.push_str(&lowered);
if c.is_ascii_alphabetic() {
original_words.push(Cow::Owned(lowered));
} else {
let mut buf = [0u8; 4];
let s: &str = c.encode_utf8(&mut buf);
let owned = String::from(s);
original_words.push(Cow::Owned(owned));
}
}
if original_words.is_empty() {
return Err(NormalizationError::EmptyText);
}
Ok(NormalizedText::new(normalized, original_words))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pure_chinese_segments_per_glyph() {
let n = ChineseNormalizer::new();
let nt = n.normalize("你好世界").unwrap();
assert_eq!(nt.normalized(), "你 好 世 界");
assert_eq!(nt.original_words().len(), 4);
assert_eq!(nt.original_words()[0], "你");
assert_eq!(nt.original_words()[3], "界");
}
#[test]
fn cjk_punctuation_stripped() {
let n = ChineseNormalizer::new();
let nt = n.normalize("你好,世界。").unwrap();
assert_eq!(nt.normalized(), "你 好 世 界");
assert_eq!(nt.original_words().len(), 4);
}
#[test]
fn mixed_chinese_and_latin() {
let n = ChineseNormalizer::new();
let nt = n.normalize("我用 Python 写代码").unwrap();
assert_eq!(nt.normalized(), "我 用 p y t h o n 写 代 码");
assert_eq!(nt.original_words().len(), 11);
assert_eq!(nt.original_words()[2], "p");
assert_eq!(nt.original_words()[7], "n");
assert_eq!(nt.original_words()[10], "码");
}
#[test]
fn empty_after_punct_only_errors() {
let n = ChineseNormalizer::new();
let err = n.normalize("。,!?").unwrap_err();
assert!(matches!(err, NormalizationError::EmptyText));
}
#[test]
fn surface_glyph_preserved_in_original_words() {
let n = ChineseNormalizer::new();
let nt = n.normalize("龜").unwrap(); assert_eq!(nt.original_words()[0], "龜");
}
#[test]
fn does_not_use_word_delimiter() {
let n = ChineseNormalizer::new();
assert!(!n.use_word_delimiter());
}
}