use indexmap::IndexMap;
use regex::Regex;
use serde::Deserialize;
use std::sync::LazyLock;
#[derive(Deserialize, Debug, Clone)]
struct KanjiMapping {
oyaji: String,
itaiji: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct KanjiProcessor {
regex: Regex,
conversion_map: IndexMap<char, char>,
}
impl KanjiProcessor {
fn new() -> Self {
let mapping_list_json = include_str!("../json_lists/full_list.json");
let itaiji_list_json = include_str!("../json_lists/itaiji_list.json");
let mappings: Vec<KanjiMapping> =
serde_json::from_str(mapping_list_json).expect("Failed to parse full_list.json");
let itaiji_list: Vec<String> =
serde_json::from_str(itaiji_list_json).expect("Failed to parse itaiji_list.json");
let regex_string: String = itaiji_list.join("");
let regex =
Regex::new(&format!("[{regex_string}]")).expect("Failed to compile Kanji regex");
let mut conversion_map = IndexMap::new();
for mapping in mappings {
let oyaji_char = mapping
.oyaji
.chars()
.next()
.expect("Oyaji should be a single character");
for itaiji in mapping.itaiji {
let itaiji_char = itaiji
.chars()
.next()
.expect("Itaiji should be a single character");
conversion_map.insert(itaiji_char, oyaji_char);
}
}
KanjiProcessor {
regex,
conversion_map,
}
}
pub fn convert_to_parent(&self, text: &str) -> String {
self.regex
.replace_all(text, |caps: ®ex::Captures| {
let matched_char = caps[0].chars().next().unwrap_or_default();
match self.conversion_map.get(&matched_char) {
Some(&parent_char) => parent_char.to_string(),
None => unreachable!(), }
})
.to_string() }
}
static DEFAULT_PROCESSOR: LazyLock<KanjiProcessor> = LazyLock::new(KanjiProcessor::new);
pub fn convert_variants(text: &str) -> String {
DEFAULT_PROCESSOR.convert_to_parent(text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constructor() {
let processor = KanjiProcessor::new();
assert!(
!processor.conversion_map.is_empty(),
"Conversion map should not be empty"
);
assert!(
!processor.regex.as_str().is_empty(),
"Regex should not be empty"
);
println!(
"Regex contains {} chars.",
processor.regex.as_str().chars().count()
);
println!("Map contains {} entries.", processor.conversion_map.len());
}
#[test]
fn test_single_variant() {
let input = "大日本帝國";
let expected = "大日本帝国";
assert_eq!(convert_variants(input), expected);
}
#[test]
fn test_multiple_variants() {
let input = "一萬円の鐵道で體育を學ぶ";
let expected = "一万円の鉄道で体育を学ぶ";
assert_eq!(convert_variants(input), expected);
}
#[test]
fn test_mixed_variants_and_parents() {
let input = "學問と学問、國と国";
let expected = "学問と学問、国と国";
assert_eq!(convert_variants(input), expected);
}
#[test]
fn test_no_variants() {
let input = "こんにちは世界、ABC 123";
let expected = "こんにちは世界、ABC 123";
assert_eq!(convert_variants(input), expected);
}
#[test]
fn test_empty_string() {
let input = "";
let expected = "";
assert_eq!(convert_variants(input), expected);
}
#[test]
fn test_variants_only() {
let input = "舊龍靜";
let expected = "旧竜静";
assert_eq!(convert_variants(input), expected);
}
#[test]
fn test_some_specific_cases() {
assert_eq!(convert_variants("萬"), "万"); assert_eq!(convert_variants("與"), "与"); assert_eq!(convert_variants("龜"), "亀"); assert_eq!(convert_variants("佛"), "仏"); assert_eq!(convert_variants("傳"), "伝"); assert_eq!(convert_variants("僞"), "偽"); assert_eq!(convert_variants("圓"), "円"); assert_eq!(convert_variants("寫"), "写"); assert_eq!(convert_variants("劍"), "剣"); assert_eq!(convert_variants("勞"), "労"); assert_eq!(convert_variants("單"), "単"); assert_eq!(convert_variants("臺"), "台"); assert_eq!(convert_variants("國"), "国"); assert_eq!(convert_variants("鹽"), "塩"); assert_eq!(convert_variants("學"), "学"); assert_eq!(convert_variants("壽"), "寿"); assert_eq!(convert_variants("德"), "徳"); assert_eq!(convert_variants("戰"), "戦"); assert_eq!(convert_variants("戲"), "戯"); assert_eq!(convert_variants("擴"), "拡"); assert_eq!(convert_variants("搖"), "揺"); assert_eq!(convert_variants("樣"), "様"); assert_eq!(convert_variants("齒"), "歯"); assert_eq!(convert_variants("氣"), "気"); assert_eq!(convert_variants("澤"), "沢"); assert_eq!(convert_variants("燒"), "焼"); assert_eq!(convert_variants("獨"), "独"); assert_eq!(convert_variants("畫"), "画"); assert_eq!(convert_variants("發"), "発"); assert_eq!(convert_variants("禮"), "礼"); assert_eq!(convert_variants("稻"), "稲"); assert_eq!(convert_variants("龍"), "竜"); assert_eq!(convert_variants("絲"), "糸"); assert_eq!(convert_variants("繪"), "絵"); assert_eq!(convert_variants("續"), "続"); assert_eq!(convert_variants("緣"), "縁"); assert_eq!(convert_variants("聽"), "聴"); assert_eq!(convert_variants("脫"), "脱"); assert_eq!(convert_variants("藏"), "蔵"); assert_eq!(convert_variants("證"), "証"); assert_eq!(convert_variants("鐵"), "鉄"); assert_eq!(convert_variants("顏"), "顔"); assert_eq!(convert_variants("驛"), "駅"); assert_eq!(convert_variants("魚"), "魚"); assert_eq!(convert_variants("𩵋"), "魚"); }
}