diff --git a/codegen/dict/kakasidict.utf8 b/codegen/dict/kakasidict.utf8 index 58b166c..78294ff 100644 --- a/codegen/dict/kakasidict.utf8 +++ b/codegen/dict/kakasidict.utf8 @@ -8317,6 +8317,7 @@ おうようじょう 応用上 おうようじょうほう 応用情報 おうようじょうほうがく 応用情報学 +おうじょうけん 応用情報学研究センター おうようすいしんか 応用推進課 おうようすうがく 応用数学 おうようすうがっか 応用数学科 @@ -14907,6 +14908,7 @@ かんいしんぱん 簡易新版 かんいてき 簡易的 かんいほけん 簡易保険 +かんいほけんほーる 簡易保険ホール かんいほうしき 簡易方式 かんいむせん 簡易無線 かんけつ 簡潔 @@ -42586,6 +42588,7 @@ しょせきるい 書籍類 しょせん 書泉 しょせんぐらんで 書泉グランデ +しょせんぶっくまーと 書泉ブックマート しょたい 書体 しょだな 書棚 かきおき 書置 @@ -46958,6 +46961,7 @@ しんにっぽん 新日本 しんにほんしょうけん 新日本証券 しんにほんせいてつ 新日本製鉄 +しんにってつ 新日本製鉄株式会社 しんにほんせいてつ 新日本製鐵 しんにっぽんせいてつ 新日本製鐵 しんにってつ 新日鐵 @@ -54137,6 +54141,7 @@ せんこうちゅう 選考中 せんこうび 選考日 せんこう 選鉱 +せんけん 選鉱精錬研究所 せんじゃ 選者 せんしゅ 選手 せんしゅいちらん 選手一覧 @@ -62102,6 +62107,7 @@ ちゅうがくせいばん 中学生版 ちゅうがくにゅうしもんだい 中学入試問題 ちゅうかっこ 中括弧 +なかま 中間 ちゅうかんれべるがくしゅう 中間レベル学習 ちゅうかんえき 中間駅 ちゅうかんえきしはつ 中間駅始発 @@ -66465,6 +66471,7 @@ でんしききぶ 電子機器部 でんしぎじゅつ 電子技術 でんしぎじゅつしゃ 電子技術者 +でんそうけん 電子技術総合研究所 でんしきょう 電子協 でんしけいじばん 電子掲示板 でんしけい 電子系 @@ -66479,6 +66486,7 @@ でんしこうがくきょうしつ 電子工学教室 でんしこうがくせんこう 電子工学専攻 でんしこうぎょう 電子工業 +でんしきょう 電子工業振興協会 でんしこうさく 電子工作 でんしざいりょう 電子材料 でんししき 電子式 @@ -67633,6 +67641,7 @@ とうきょうがす 東京ガス とうきょうすたいる 東京スタイル とうきょうてあとる 東京テアトル +とうきょうべいえぬけーほーる 東京ベイNKホール とうきょういがい 東京以外 とうきょういち 東京一 とうきょうえき 東京駅 @@ -68458,6 +68467,7 @@ とうけいしょり 統計処理 とうけいじょうほう 統計情報 とうけいすうがく 統計数学 +とうすうけん 統計数理研究所 とうけいち 統計値 とうけいてき 統計的 とうけいてきぱたあん 統計的パターン @@ -71459,10 +71469,12 @@ にっぽんとむそん 日本トムソン にっぽんはむ 日本ハム にほんはむ 日本ハム +にっぽんひゅーむかん 日本ヒューム管 にっぽんびくたー 日本ビクター にっぽんぺいんと 日本ペイント にっぽんゆにばっく 日本ユニバック にっぽんれーす 日本レース +にほんろぼっとがっかい 日本ロボット学会 にほんいがい 日本以外 にほんいじょう 日本以上 にほんいち 日本一 @@ -72020,6 +72032,8 @@ にゅうしゅつりょく 入出力 にゅうしゅつりょくh 入出力 にゅうしゅつりょくせっと 入出力セット +にゅうしゅつりょくぱ 入出力パターン +にゅうしゅつりょくぱたーん 入出力パターン にゅうしゅつりょくかんけい 入出力関係 にゅうしゅつりょくけい 入出力系 にゅうしゅつりょくそうち 入出力装置 @@ -79662,6 +79676,7 @@ ぶんいちそうごうしゅっぱん 文一総合出版 ぶんえんどう 文苑堂 ぶんか 文化 +ぶんかしゃったー 文化シャッター ぶんかかい 文化会 ぶんかかいかん 文化会館 ぶんかかいかん 文化会舘 @@ -80063,6 +80078,7 @@ へいおん 平温 へいおん 平穏 ひらかな 平仮名 +ひらかなかくていにゅうりょく 平仮名確定入力 へいか 平価 へいけ 平家 へいけものがたり 平家物語 @@ -110762,6 +110778,7 @@ せんせいよう 先生用 せんせんげつ 先先月 せんたんいりょう 先端医療 +せんたんかがくぎじゅつけんきゅうせんたー 先端科学技術研究センター せんたんぎじゅつけんきゅう 先端技術研究 せんたんきょうふしょう 先端恐怖症 せんたんけん 先端研 @@ -114496,6 +114513,7 @@ でんしききるい 電子機器類 でんしきどう 電子軌道 でんしぎじゅつかんけい 電子技術関係 +でんしぎじゅつそうごうけんきゅうしょ 電子技術総合研究所 でんしけいじばん 電子掲示版 でんしけいさんきしつ 電子計算機室 でんしこうさくよう 電子工作用 @@ -116004,6 +116022,7 @@ にほんちんぼつ 日本沈没 にほんていえん 日本庭園 にほんてつどう 日本鉄道 +にほんにんちかがくかい 日本認知科学会 にほんねこ 日本猫 にほんばんじまく 日本版字幕 にほんぶっきょう 日本仏教 @@ -116096,6 +116115,7 @@ にゅうよくざい 入浴剤 にゅうよくはっぽうき 入浴発泡器 にゅうらい 入来 +にゅうりょくぱらめーた 入力パラメータ にゅうりょくかんじ 入力漢字 にゅうりょくかんきょう 入力環境 にゅうりょくじたい 入力自体 diff --git a/codegen/src/main.rs b/codegen/src/main.rs index acf9774..1b07f8b 100644 --- a/codegen/src/main.rs +++ b/codegen/src/main.rs @@ -1,5 +1,4 @@ mod phfbin_gen; -mod testconv; use std::{borrow::Cow, collections::HashMap, path::Path}; @@ -67,13 +66,12 @@ fn parse_dict_ln(records: &mut Records, line: &str, ln: usize) { .or_else(|| context.map(str::to_owned)) .unwrap_or_default(), ) { - std::collections::hash_map::Entry::Occupied(_) => { - /* - // Replace reading if the new one is shorter + std::collections::hash_map::Entry::Occupied(mut e) => { + // Replace reading if the new one is longer let val = e.get_mut(); - if val.len() > reading.len() { + if val.len() < reading.len() { *val = reading.to_owned(); - }*/ + } } std::collections::hash_map::Entry::Vacant(e) => { e.insert(reading.to_owned()); @@ -200,63 +198,9 @@ impl Encodable for Readings { } } -fn find_redundant_compounds(dict: &Records) -> Records { - let mut wdict = dict.clone(); - - for (kanji, readings) in dict { - if kanji.chars().count() <= 3 { - continue; - } - if readings.len() != 1 { - continue; - } - - if let Some(reading) = readings.get("") { - // Try to convert the entry without it being present - let entry = wdict.remove_entry(kanji).unwrap(); - let res = testconv::convert(kanji, &wdict); - - if &res == reading || to_romaji_nodc(&res) == to_romaji_nodc(reading) { - println!("Redundant: {} - {}", kanji, reading); - } else { - // Put the entry back if it is necessary - wdict.insert(entry.0, entry.1); - } - } - } - wdict -} - -/// Romanize and remove double consonants -fn to_romaji_nodc(text: &str) -> String { - let rom = wana_kana::to_romaji::to_romaji(text); - - let mut buf = String::new(); - let mut citer = rom.chars().peekable(); - - while let Some(c) = citer.next() { - if matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') { - match citer.peek() { - Some(nc) => { - if &c != nc { - buf.push(c); - } - } - None => buf.push(c), - } - } else { - buf.push(c); - } - } - buf -} - fn generate_kanji_dict() -> Vec { let mut records = Records::default(); parse_dict(&mut records, Path::new("dict/kakasidict.utf8")); - records = find_redundant_compounds(&records); - - println!("kanji_dict: {} entries", records.len()); let mut phfmap = phfbin_gen::Map::::default(); for (kanji, readings) in records { diff --git a/codegen/src/testconv.rs b/codegen/src/testconv.rs deleted file mode 100644 index 850d27d..0000000 --- a/codegen/src/testconv.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::{Records, CLETTERS}; - -const ENDMARK: [char; 11] = [ - ')', ']', '!', '.', ',', '\u{3001}', '\u{3002}', '\u{ff1f}', '\u{ff10}', '\u{ff1e}', '\u{ff1c}', -]; -const DASH_SYMBOLS: [char; 4] = ['\u{30FC}', '\u{2015}', '\u{2212}', '\u{FF70}']; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum CharType { - Kanji, - Katakana, - Hiragana, - Symbol, - Alpha, -} - -pub fn convert(text: &str, dict: &Records) -> String { - // TODO: char conversion should be done with iterators - let mut char_indices = text.char_indices(); - let mut kana_text = String::new(); - let mut hiragana = String::new(); - let mut prev_type = CharType::Kanji; - - // output_flag - // means (output buffer?, output text[i]?, copy to buffer and increment i?) - // possible (False, True, True), (True, False, False), (True, True, True) - // (False, False, True) - - while let Some((i, c)) = char_indices.next() { - let output_flag = if ENDMARK.contains(&c) { - (CharType::Symbol, true, true, true) - } else if DASH_SYMBOLS.contains(&c) { - (prev_type, false, false, true) - } else if is_sym(c) { - if prev_type != CharType::Symbol { - (CharType::Symbol, true, false, true) - } else { - (CharType::Symbol, false, true, true) - } - } else if wana_kana::utils::is_char_katakana(c) { - ( - CharType::Katakana, - prev_type != CharType::Katakana, - false, - true, - ) - } else if wana_kana::utils::is_char_hiragana(c) { - ( - CharType::Hiragana, - prev_type != CharType::Hiragana, - false, - true, - ) - } else if c.is_ascii() { - (CharType::Alpha, prev_type != CharType::Alpha, false, true) - } else if wana_kana::utils::is_char_kanji(c) { - if !kana_text.is_empty() { - hiragana.push_str(&convert_kana(&kana_text)); - } - let (t, n) = convert_kanji(&text[i..], &kana_text, &dict); - - if n > 0 { - kana_text = t; - for _ in 1..n { - char_indices.next(); - } - (CharType::Kanji, false, false, false) - } else { - // Unknown kanji - kana_text.clear(); - // TODO: FOR TESTING - hiragana.push_str("🯄"); - (CharType::Kanji, true, false, false) - } - } else if matches!(c as u32, 0xf000..=0xfffd | 0x10000..=0x10ffd) { - // PUA: ignore and drop - if !kana_text.is_empty() { - hiragana.push_str(&convert_kana(&kana_text)); - } - (prev_type, false, false, false) - } else { - (prev_type, true, true, true) - }; - - prev_type = output_flag.0; - - if output_flag.1 && output_flag.2 { - kana_text.push(c); - hiragana.push_str(&convert_kana(&kana_text)); - kana_text.clear() - } else if output_flag.1 && output_flag.3 { - if !kana_text.is_empty() { - hiragana.push_str(&convert_kana(&kana_text)); - } - kana_text = c.to_string(); - } else if output_flag.3 { - kana_text.push(c); - } - } - - // Convert last word - if !kana_text.is_empty() { - hiragana.push_str(&convert_kana(&kana_text)); - } - - hiragana -} - -fn is_sym(c: char) -> bool { - matches!(c as u32, - 0x3000..=0x3020 | - 0x3030..=0x303F | - 0x0391..=0x03A1 | - 0x03A3..=0x03A9 | - 0x03B1..=0x03C9 | - 0x0410..= 0x044F | - 0xFF01..=0xFF1A | - 0x00A1..=0x00FF | - 0xFF20..=0xFF5E | - 0x0451 | - 0x0401 - ) -} - -fn convert_kana(text: &str) -> String { - wana_kana::to_hiragana::to_hiragana_with_opt( - text, - wana_kana::Options { - use_obsolete_kana: false, - pass_romaji: true, - upcase_katakana: false, - imemode: false, - }, - ) -} - -/// Convert the leading kanji from the input string to hiragana -fn convert_kanji(text: &str, btext: &str, dict: &Records) -> (String, usize) { - let mut translation: Option = None; - let mut i_c = 0; - let mut n_c = 0; - let mut char_indices = text.char_indices().peekable(); - - while let Some((i, c)) = char_indices.next() { - let kanji = &text[0..i + c.len_utf8()]; - - let this_tl = dict.get(kanji).and_then(|readings| { - readings - .iter() - .find_map(|(k, reading)| { - if k.is_empty() { - None - } else if let Some(cltr) = CLETTERS.get(&k.chars().next().unwrap_or_default()) { - char_indices.peek().and_then(|(_, next_c)| { - // Shortcut if the next character is not hiragana - if wana_kana::utils::is_char_hiragana(*next_c) { - if cltr.contains(&&next_c.to_string().as_str()) { - // Add the next character to the char count - i_c += 1; - let mut hira = reading.to_owned(); - hira.push(*next_c); - return Some(hira); - } else { - None - } - } else { - None - } - }) - } else if wana_kana::is_hiragana::is_hiragana(&k) { - if btext.contains(reading) { - Some(reading.to_owned()) - } else { - None - } - } else { - panic!("invalid reading key") - } - }) - .or_else(|| readings.get("").cloned()) - }); - - i_c += 1; - if let Some(tl) = this_tl { - translation = Some(tl); - n_c = i_c; - } - if i_c >= 12 { - break; - } - } - - translation - .map(|tl| (tl.to_owned(), n_c)) - .unwrap_or_default() -} diff --git a/src/kanji_dict.bin b/src/kanji_dict.bin index 02af08a..159f5a4 100644 Binary files a/src/kanji_dict.bin and b/src/kanji_dict.bin differ diff --git a/src/lib.rs b/src/lib.rs index 5ed25a7..e5770ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ use phfbin::PhfMap; use types::{KanjiString, Readings}; const KANJI_DICT: &[u8] = include_bytes!("./kanji_dict.bin"); -const MAX_KANJI_LEN: usize = 7; static CLETTERS: phf::Map = phf::phf_map!( b'a' => &['あ', 'ぁ', 'っ', 'わ', 'ゎ'], @@ -40,20 +39,6 @@ static CLETTERS: phf::Map = phf::phf_map!( b'v' => &['ゔ'], ); -const ENDMARK: [char; 11] = [ - ')', ']', '!', '.', ',', '\u{3001}', '\u{3002}', '\u{ff1f}', '\u{ff10}', '\u{ff1e}', '\u{ff1c}', -]; -const DASH_SYMBOLS: [char; 4] = ['\u{30FC}', '\u{2015}', '\u{2212}', '\u{FF70}']; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum CharType { - Kanji, - Katakana, - Hiragana, - Symbol, - Alpha, -} - pub fn convert(text: &str) -> KakasiResult { let dict = PhfMap::new(KANJI_DICT); @@ -61,131 +46,12 @@ pub fn convert(text: &str) -> KakasiResult { let text = text.nfkc().collect::(); let text = convert_syn(&text); - let mut char_indices = text.char_indices(); - let mut kana_text = String::new(); - let mut prev_type = CharType::Kanji; - - let mut hiragana = String::new(); - let mut romaji = String::new(); - - let conv_kana_txt = |kana_text: &mut String, hiragana: &mut String, romaji: &mut String| { - if !kana_text.is_empty() { - let h = convert_kana(&kana_text); - hiragana.push_str(&h); - romaji.push_str(&wana_kana::to_romaji::to_romaji(&h)); - romaji.push(' '); - } - }; - - // output_flag - // means (output buffer?, output text[i]?, copy to buffer and increment i?) - // possible (False, True, True), (True, False, False), (True, True, True) - // (False, False, True) - - while let Some((i, c)) = char_indices.next() { - let output_flag = if ENDMARK.contains(&c) { - (CharType::Symbol, true, true, true) - } else if DASH_SYMBOLS.contains(&c) { - (prev_type, false, false, true) - } else if is_sym(c) { - if prev_type != CharType::Symbol { - (CharType::Symbol, true, false, true) - } else { - (CharType::Symbol, false, true, true) - } - } else if wana_kana::utils::is_char_katakana(c) { - ( - CharType::Katakana, - prev_type != CharType::Katakana, - false, - true, - ) - } else if wana_kana::utils::is_char_hiragana(c) { - ( - CharType::Hiragana, - prev_type != CharType::Hiragana, - false, - true, - ) - } else if c.is_ascii() { - (CharType::Alpha, prev_type != CharType::Alpha, false, true) - } else if wana_kana::utils::is_char_kanji(c) { - conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji); - let (t, n) = convert_kanji(&text[i..], &kana_text, &dict); - - if n > 0 { - kana_text = t; - for _ in 1..n { - char_indices.next(); - } - (CharType::Kanji, false, false, false) - } else { - // Unknown kanji - kana_text.clear(); - // TODO: FOR TESTING - hiragana.push_str("🯄"); - romaji.push_str("🯄"); - (CharType::Kanji, true, false, false) - } - } else if matches!(c as u32, 0xf000..=0xfffd | 0x10000..=0x10ffd) { - // PUA: ignore and drop - conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji); - kana_text.clear(); - (prev_type, false, false, false) - } else { - (prev_type, true, true, true) - }; - - prev_type = output_flag.0; - - if output_flag.1 && output_flag.2 { - kana_text.push(c); - conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji); - kana_text.clear() - } else if output_flag.1 && output_flag.3 { - conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji); - kana_text = c.to_string(); - } else if output_flag.3 { - kana_text.push(c); - } - } - - // Convert last word - conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji); - // Remove trailing space - romaji.pop(); + let hiragana = convert_kanji(&text, "", &dict).0; + let romaji = wana_kana::to_romaji::to_romaji(&hiragana); KakasiResult { hiragana, romaji } } -fn is_sym(c: char) -> bool { - matches!(c as u32, - 0x3000..=0x3020 | - 0x3030..=0x303F | - 0x0391..=0x03A1 | - 0x03A3..=0x03A9 | - 0x03B1..=0x03C9 | - 0x0410..= 0x044F | - 0xFF01..=0xFF1A | - 0x00A1..=0x00FF | - 0xFF20..=0xFF5E | - 0x0451 | - 0x0401 - ) -} - -fn convert_kana(text: &str) -> String { - wana_kana::to_hiragana::to_hiragana_with_opt( - text, - wana_kana::Options { - use_obsolete_kana: false, - pass_romaji: true, - upcase_katakana: false, - imemode: false, - }, - ) -} - /// Convert the leading kanji from the input string to hiragana /// /// # Arguments @@ -195,7 +61,7 @@ fn convert_kana(text: &str) -> String { /// The input needs to be NFKC-normalized and synonymous kanji need to be /// replaced using [`convert_syn`]. /// -/// * `btext` - Buffer string (leading kana) +/// * `btext` - /// /// # Return /// @@ -203,7 +69,6 @@ fn convert_kana(text: &str) -> String { /// * `1` - Number of converted chars from the input string fn convert_kanji(text: &str, btext: &str, dict: &PhfMap) -> (String, usize) { let mut translation = None; - let mut i_c = 0; let mut n_c = 0; let mut char_indices = text.char_indices().peekable(); @@ -222,7 +87,7 @@ fn convert_kanji(text: &str, btext: &str, dict: &PhfMap) -> (String, usize) { CLETTERS.get(&ch).and_then(|cltr| { if cltr.contains(next_c) { // Add the next character to the char count - i_c += 1; + n_c += 1; hira.push(*next_c); Some(hira) } else { @@ -244,14 +109,11 @@ fn convert_kanji(text: &str, btext: &str, dict: &PhfMap) -> (String, usize) { }) }); - i_c += 1; - if let Some(tl) = this_tl { - translation = Some(tl); - n_c = i_c; - } - if i_c >= MAX_KANJI_LEN { - break; + match this_tl { + Some(this_tl) => translation = Some(this_tl), + None => break, } + n_c += 1; } translation @@ -303,9 +165,6 @@ mod tests { #[rstest] #[case("会っAbc", "あっ", 2)] - #[case("渋谷", "しぶや", 2)] - // #[case("渋谷公会堂", "しぶやこうかいどう", 5)] - // #[case("家畜衛生試験場", "かちくえいせいしけんじょう", 7)] fn t_convert_kanji(#[case] text: &str, #[case] expect: &str, #[case] expect_n: usize) { let dict = PhfMap::new(KANJI_DICT); let (res, n) = convert_kanji(text, "", &dict); diff --git a/src/main.rs b/src/main.rs index 482d32a..205cad9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ fn main() { for line in std::io::stdin().lines() { let res = kakasi::convert(&line.unwrap()); - println!("{}\n{}\n\n", res.hiragana, res.romaji); + println!("{} - {}", res.hiragana, res.romaji); } }