use crate::furi::segment::kanji::as_kanji::AsKanjiSegment;
use super::{
parse::unchecked::UncheckedFuriParser,
segment::{encode::FuriEncoder, AsSegment},
Furigana,
};
use std::mem::swap;
pub struct CodeFormatter<'a, T> {
src_furi: &'a Furigana<T>,
buf: String,
src: String,
lossy: bool,
}
impl<'a, T> CodeFormatter<'a, T> {
#[inline]
pub fn new(furi: &'a Furigana<T>) -> Self {
Self {
buf: String::new(),
src: String::new(),
src_furi: furi,
lossy: false,
}
}
#[inline]
pub fn lossy(mut self) -> Self {
self.lossy = true;
self
}
}
impl<'a, T> CodeFormatter<'a, T>
where
T: AsRef<str>,
{
#[inline]
pub fn apply_all(self) -> Furigana<String> {
self.merge_kanji_parts()
.remove_empty_kanji()
.fix_kanji_blocks()
.finish()
}
pub fn fix_kanji_blocks(mut self) -> Self {
let (str, buf) = self.get_src();
let mut enc = FuriEncoder::new(buf);
for (sub, is_kanji) in Furigana(str).gen_parser() {
if !is_kanji {
enc.write_kana(sub);
continue;
}
let seg = UncheckedFuriParser::from_seg_str(sub, true);
let kanji = seg.as_kanji().unwrap();
let readings = kanji.readings();
if readings.is_empty() {
enc.write_kana(sub);
continue;
}
if kanji.is_detailed() || readings.len() == 1 {
enc.write_kanji(kanji);
continue;
}
let new_reading = readings.iter().fold(String::new(), |mut init, i| {
init.push_str(i);
init
});
enc.write_block(kanji.literals(), &new_reading);
}
self
}
pub fn remove_empty_kanji(mut self) -> Self {
let (str, buf) = self.get_src();
let mut enc = FuriEncoder::new(buf);
for (sub, is_kanji) in Furigana(str).gen_parser() {
if !is_kanji {
enc.write_kana(sub);
continue;
}
let seg = UncheckedFuriParser::from_seg_str(sub, is_kanji);
let kanji = seg.as_kanji().unwrap();
let readings = kanji.readings();
if readings.len() == 1 && readings[0].is_empty() {
enc.write_kana(kanji.literals());
continue;
} else if readings.len() == 0 {
enc.write_kana(sub);
continue;
}
enc.write_kanji(kanji);
}
self
}
pub fn merge_kanji_parts(mut self) -> Self {
let lossy = self.lossy;
let (str, buf) = self.get_src();
let mut merge_buf = String::new();
let mut pushed_kanji = false;
let mut has_undetailed = false;
#[inline(always)]
fn merge(buf: &mut String, merge_buf: &mut String, has_undetailed: bool) {
merge_buf.pop();
buf.push('|');
if !has_undetailed {
buf.push_str(&merge_buf);
} else {
buf.push_str(&merge_buf.replace('|', ""));
}
buf.push(']');
merge_buf.clear();
}
for (sub, is_kanji) in Furigana(str).gen_parser() {
let i = UncheckedFuriParser::from_seg_str(sub, is_kanji);
let detailed = i.as_kanji().map(|i| i.is_detailed()).unwrap_or_default();
let empty_kanji = i
.as_kanji()
.map(|i| i.readings().is_empty() || i.readings()[0].is_empty())
.unwrap_or_default();
let exit_undetailed = detailed || lossy;
if (!is_kanji || !exit_undetailed || empty_kanji) && !merge_buf.is_empty() {
merge(buf, &mut merge_buf, has_undetailed);
pushed_kanji = false;
has_undetailed = false;
}
if let Some(kana) = i.as_kana() {
buf.push_str(kana);
continue;
}
let kanji = i.as_kanji().unwrap();
let readings = kanji.readings();
if readings.is_empty() {
buf.push_str(sub);
continue;
}
if empty_kanji {
buf.push('[');
buf.push_str(kanji.literals());
buf.push_str("|]");
continue;
}
if !detailed {
if !lossy {
buf.push_str(sub);
continue;
}
has_undetailed = true;
}
if !pushed_kanji {
pushed_kanji = true;
buf.push('[');
}
buf.push_str(kanji.literals());
for reading in readings {
merge_buf.push_str(reading);
merge_buf.push('|');
}
}
if merge_buf.len() > 0 {
merge(buf, &mut merge_buf, has_undetailed);
}
self
}
fn get_src(&mut self) -> (&str, &mut String) {
if self.buf.is_empty() {
(self.src_furi.raw(), &mut self.buf)
} else {
swap(&mut self.buf, &mut self.src);
self.buf.clear();
(&self.src, &mut self.buf)
}
}
#[inline]
pub fn finish(self) -> Furigana<String> {
if self.buf.is_empty() {
return self.src_furi.as_owned();
}
Furigana(self.buf)
}
}
#[cfg(test)]
mod test {
use super::*;
use test_case::test_case;
#[test_case("[大|だい][丈|じょう][夫|ぶ]", "[大丈夫|だい|じょう|ぶ]"; "AllKanji")]
#[test_case("それは[大|だい][丈|じょう][夫|ぶ]", "それは[大丈夫|だい|じょう|ぶ]"; "KanaBefore")]
#[test_case(
"それは[大|だい][丈|じょう]です",
"それは[大丈|だい|じょう]です"; "kanaBeforeAndAfter"
)]
#[test_case("それは[大|だい][丈夫|じょうぶ]だよ", "それは[大|だい][丈夫|じょうぶ]だよ"; "NonDetailed")]
#[test_case("それは[音|おん][楽|がく][大学|だいがく]です", "それは[音楽|おん|がく][大学|だいがく]です"; "NonDetailed2")]
#[test_case("それは[音|おん][楽|がく][大学|だい|がく]です", "それは[音楽大学|おん|がく|だい|がく]です"; "NonDetailed3")]
#[test_case("[Wi|ワイ][-|][Fi|ファイ] ってフランス[語|ご]ではどう[発音|はつ|おん]するんですか?", "[Wi|ワイ][-|][Fi|ファイ] ってフランス[語|ご]ではどう[発音|はつ|おん]するんですか?"; "EmptyKanjiPart")]
#[test_case(
"[高校生|こう|こう|せい]の[時|とき]は[毎朝|まい|あさ][6|][時|じ]に[起|お]きていた。",
"[高校生|こう|こう|せい]の[時|とき]は[毎朝|まい|あさ][6|][時|じ]に[起|お]きていた。"
)]
#[test_case("[2|][x|えっくす]+[1|]の[定義|てい|ぎ][域|いき]が[A|えい]=[[1|],[2|]]のとき、[f|えふ]の[値域|ち|いき]は[f|えふ]([A|えい]) = [[3|],[5|]]となる。",
"[2|][x|えっくす]+[1|]の[定義域|てい|ぎ|いき]が[A|えい]=[[1|],[2|]]のとき、[f|えふ]の[値域|ち|いき]は[f|えふ]([A|えい]) = [[3|],[5|]]となる。"; "special")]
#[test_case(
"[永遠|えい|えん]にあなたのものです。 [アーメン]",
"[永遠|えい|えん]にあなたのものです。 [アーメン]"; "brackets"
)]
fn test_merge_parts(src: &str, dst: &str) {
let furi = Furigana(src);
let res = furi.code_formatter().merge_kanji_parts().finish();
assert_eq!(res.raw(), dst);
let r = res.to_reading();
assert_eq!(furi.kana_str(), r.kana());
assert_eq!(furi.kanji_str(), r.kanji().unwrap());
assert_eq!(Furigana(dst).kana_str(), res.kana_str());
assert_eq!(Furigana(dst).kanji_str(), res.kanji_str());
assert_eq!(Furigana(dst).kana_str(), furi.kana_str());
assert_eq!(Furigana(dst).kanji_str(), furi.kanji_str());
}
#[test_case("[大|だい][丈|じょう][夫|ぶ]", "[大丈夫|だい|じょう|ぶ]"; "AllKanji")]
#[test_case("それは[大|だい][丈|じょう][夫|ぶ]", "それは[大丈夫|だい|じょう|ぶ]"; "KanaBefore")]
#[test_case(
"それは[大|だい][丈|じょう]です",
"それは[大丈|だい|じょう]です"; "kanaBeforeAndAfter"
)]
#[test_case("それは[大|だい][丈夫|じょうぶ]だよ", "それは[大丈夫|だいじょうぶ]だよ"; "lossy1")]
#[test_case("それは[音|おん][楽|がく][大学|だいがく]です", "それは[音楽大学|おんがくだいがく]です"; "lossy2")]
#[test_case("それは[音|おん][楽|がく][大学|だい|がく]です", "それは[音楽大学|おん|がく|だい|がく]です"; "Detailed")]
fn test_merge_parts_lossy(src: &str, dst: &str) {
let furi = Furigana(src);
let res = CodeFormatter::new(&furi)
.lossy()
.merge_kanji_parts()
.finish();
assert_eq!(res.raw(), dst);
}
#[test_case("[Wi|ワイ][-|][Fi|ファイ] って", "[Wi|ワイ]-[Fi|ファイ] って"; "1")]
#[test_case("[毎朝|まい|あさ][6|][時|じ]に", "[毎朝|まい|あさ]6[時|じ]に";"2")]
#[test_case("[2|][x|えっくす]+[1|]の[定義|てい|ぎ][域|いき]が[A|えい]=[[1|],[2|]]のとき、[f|えふ]の[値域|ち|いき]は[f|えふ]([A|えい]) = [[3|],[5|]]となる。",
"2[x|えっくす]+1の[定義|てい|ぎ][域|いき]が[A|えい]=[1,2]のとき、[f|えふ]の[値域|ち|いき]は[f|えふ]([A|えい]) = [3,5]となる。"; "special")]
#[test_case(
"[永遠|えい|えん]にあなたのものです。 [アーメン]",
"[永遠|えい|えん]にあなたのものです。 [アーメン]"; "brackets"
)]
fn test_remove_empty_kanji(s: &str, exp: &str) {
let furi = Furigana(s);
let out = CodeFormatter::new(&furi).remove_empty_kanji().finish();
assert_eq!(out, exp);
}
#[test_case("[音楽大|おんがく|だい]", "[音楽大|おんがくだい]"; "1")]
#[test_case("おんがくが[好|す]","おんがくが[好|す]"; "End_kanji")]
#[test_case("おんがくが[好|す]きです", "おんがくが[好|す]きです")]
#[test_case("[音楽|おん|がく]が[好|す]き", "[音楽|おん|がく]が[好|す]き")]
#[test_case("[拝金主義|はい|きん|しゅ|ぎ]は[問題|もん|だい][拝金主義|はい|きん|しゅ|ぎ]は[問題|もん|だい][拝金主義|はい|きん|しゅ|ぎ]は[問題|もん|だい]","[拝金主義|はい|きん|しゅ|ぎ]は[問題|もん|だい][拝金主義|はい|きん|しゅ|ぎ]は[問題|もん|だい][拝金主義|はい|きん|しゅ|ぎ]は[問題|もん|だい]")]
#[test_case("[楽|たの]しい", "[楽|たの]しい")]
#[test_case("[音楽おん|がく]が[好す", "[音楽おん|がく]が[好す")]
#[test_case("この[人|ひと]が[嫌|きら]いです。", "この[人|ひと]が[嫌|きら]いです。")]
#[test_case("[2|][x|えっくす]+[1|]の[定義|てい|ぎ][域|いき]が[A|えい]=[[1|],[2|]]のとき、[f|えふ]の[値域|ち|いき]は[f|えふ]([A|えい]) = [[3|],[5|]]となる。", "[2|][x|えっくす]+[1|]の[定義|てい|ぎ][域|いき]が[A|えい]=[[1|],[2|]]のとき、[f|えふ]の[値域|ち|いき]は[f|えふ]([A|えい]) = [[3|],[5|]]となる。"; "with brackets")]
#[test_case(
"[永遠|えい|えん]にあなたのものです。 [アーメン]",
"[永遠|えい|えん]にあなたのものです。 [アーメン]"; "brackets"
)]
fn test_fix_kanji_blocks(s: &str, exp: &str) {
let furi = Furigana(s);
let out = CodeFormatter::new(&furi).fix_kanji_blocks().finish();
assert_eq!(out, exp);
}
}