use std::borrow::Cow;
use crate::runner::aligner::normalizer::{NormalizationError, NormalizedText, TextNormalizer};
#[derive(Default, Clone, Copy, Debug)]
pub struct KoreanNormalizer;
impl KoreanNormalizer {
pub const fn new() -> Self {
Self
}
}
fn is_ko_punct(c: char) -> bool {
matches!(
c,
'\u{3002}' | '\u{3001}' | '\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{30FB}' | '\u{00B7}' | '.' | ',' | '!' | '?' | ';' | ':' | '"' | '\'' | '(' | ')'
| '[' | ']' | '{' | '}' | '-'
)
}
impl TextNormalizer for KoreanNormalizer {
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_ko_punct(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);
original_words.push(Cow::Owned(String::from(s)));
}
}
if original_words.is_empty() {
return Err(NormalizationError::EmptyText);
}
Ok(NormalizedText::new(normalized, original_words))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hangul_syllables_per_char() {
let n = KoreanNormalizer::new();
let nt = n.normalize("안녕하세요").unwrap();
assert_eq!(nt.normalized(), "안 녕 하 세 요");
assert_eq!(nt.original_words().len(), 5);
assert_eq!(nt.original_words()[0], "안");
assert_eq!(nt.original_words()[4], "요");
}
#[test]
fn hangul_jamo_per_char() {
let n = KoreanNormalizer::new();
let nt = n.normalize("\u{1100}\u{1161}\u{11A8}").unwrap();
assert_eq!(nt.original_words().len(), 3);
}
#[test]
fn hangul_compatibility_jamo_per_char() {
let n = KoreanNormalizer::new();
let nt = n.normalize("ㄱㄴㄷ").unwrap();
assert_eq!(nt.normalized(), "ㄱ ㄴ ㄷ");
assert_eq!(nt.original_words().len(), 3);
}
#[test]
fn mixed_hangul_and_latin() {
let n = KoreanNormalizer::new();
let nt = n.normalize("USA에서").unwrap();
assert_eq!(nt.normalized(), "u s a 에 서");
assert_eq!(nt.original_words().len(), 5);
assert_eq!(nt.original_words()[0], "u");
assert_eq!(nt.original_words()[1], "s");
assert_eq!(nt.original_words()[2], "a");
assert_eq!(nt.original_words()[3], "에");
}
#[test]
fn mixed_hangul_and_digits() {
let n = KoreanNormalizer::new();
let nt = n.normalize("3개").unwrap();
assert_eq!(nt.normalized(), "3 개");
assert_eq!(nt.original_words().len(), 2);
assert_eq!(nt.original_words()[0], "3");
assert_eq!(nt.original_words()[1], "개");
}
#[test]
fn korean_punctuation_stripped() {
let n = KoreanNormalizer::new();
let nt = n.normalize("안녕, 세계!").unwrap();
assert_eq!(nt.normalized(), "안 녕 세 계");
assert_eq!(nt.original_words().len(), 4);
}
#[test]
fn cjk_punctuation_stripped() {
let n = KoreanNormalizer::new();
let nt = n.normalize("안녕。세계、").unwrap();
assert_eq!(nt.normalized(), "안 녕 세 계");
assert_eq!(nt.original_words().len(), 4);
}
#[test]
fn empty_after_punct_only_errors() {
let n = KoreanNormalizer::new();
let err = n.normalize("。、!?").unwrap_err();
assert!(matches!(err, NormalizationError::EmptyText));
}
#[test]
fn does_not_use_word_delimiter() {
let n = KoreanNormalizer::new();
assert!(!n.use_word_delimiter());
}
}