1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
pub mod types; pub mod parser; pub mod utils; #[allow(unused_imports)] #[macro_use] extern crate anyhow; use bincode; use types::*; use std::collections::HashSet; use std::io::{ BufWriter, BufReader }; static SWORDS: &[&str] = &["он", "оно", "они", "ты", "вы", "мы"]; pub static SEPARATORS: [char; 10] = [' ',',','.',';',':','!','?','_','\n','\r']; #[allow(clippy::unnecessary_unwrap)] impl Kathoey { pub fn load(bin: &str) -> anyhow::Result<Kathoey> { let f = std::fs::File::open(bin)?; let b = BufReader::new(f); let k = bincode::deserialize_from(b)?; Ok(k) } pub fn from_xml(csv: &str) -> anyhow::Result<Kathoey> { let text = std::fs::read_to_string(csv)?; parser::parse_xml(text.as_str()) } fn fem( &self , string: &str , extreme: bool ) -> Option<String> { let ff = self.map.get(string)?; if extreme || ff.lemma != Lemma::Other { if ff.fem < self.dict.len() { let fem = self.dict[ff.fem].clone(); Some( fem ) } else { None } } else { None } } pub fn feminize_word( &self , string: &str , extreme: bool ) -> Option<String> { if let Some(result) = self.fem(string, extreme) { Some(result) } else if string.contains('е') { let yo = string.replace('е', "ё"); self.fem(&yo, extreme) } else { None } } fn process_sentance( &self , string: &str , extreme: bool) -> String { let mut out = string.to_string(); let mut processed_words : HashSet<&str> = HashSet::new(); let words = string.split(&SEPARATORS[..]); for word in words { if word.is_empty() { continue; } let small_word = word.to_lowercase(); if let Some(mut fw) = self.feminize_word(&small_word, extreme) { if !processed_words.contains(&word) { let mut whole_word_uppercase = true; let mut first_char_uppercase = true; let mut first_char_checked = false; for ch in word.chars() { if ch.is_lowercase() { if !first_char_checked { first_char_uppercase = false; } whole_word_uppercase = false; break; } first_char_checked = true; } if whole_word_uppercase { fw = fw.to_uppercase(); } else if first_char_uppercase { fw = utils::capital_first(&fw); } out = out.replace(word, &fw); processed_words.insert(word); } } } out } pub fn feminize( &self , string: &str ) -> String { let lower = string.to_lowercase(); let lwords = lower.split(&SEPARATORS[..]) .collect::<Vec<&str>>(); if lwords.contains(&"я") { if let Some(o) = SWORDS.iter().find(|o| lwords.contains(o)) { let ipos = lwords.iter().position(|&w| w == "я"); let opos = lwords.iter().position(|&w| w == *o); if ipos.is_some() && opos.is_some() { let ip = ipos.unwrap(); let op = opos.unwrap(); if ip > op { let pos = lwords[0..ip].join(" ").len(); let (first, last) = string.split_at(pos); let fem_first = self.feminize(first); format!("{}{}", fem_first, self.process_sentance(last, false)) } else { let pos = lwords[0..op].join(" ").len(); let (first, last) = string.split_at(pos); let fem_last = self.feminize(last); format!("{}{}", self.process_sentance(first, false), fem_last) } } else { self.process_sentance(string, false) } } else { self.process_sentance(string, false) } } else if SWORDS.iter().any(|o| lower.contains(*o)) { string.to_string() } else { self.process_sentance(string, false) } } pub fn extreme_feminize( &self , string: &str ) -> String { self.process_sentance(string, true) } pub fn print_this(&self) { for (kk, vv) in self.map.iter() { println!("{} -> {}", kk, vv.fem); } } pub fn save(&self, fname: &str) -> anyhow::Result<()> { let f = std::fs::File::create(fname)?; let mut bw = BufWriter::new(f); bincode::serialize_into(&mut bw, &self)?; Ok(()) } } #[cfg(test)] mod tests { use serial_test::serial; use super::*; #[test] #[serial] fn from_csv() -> anyhow::Result<()> { match Kathoey::from_xml("dict.opcorpora.xml") { Ok(k) => { assert_eq!("Начала наруто смотреть", k.feminize("Начал наруто смотреть")); if let Err(exerr) = k.save("dict.bin") { return Err(anyhow!("Failed to export {:?}", exerr)); } } Err(kerr) => { return Err(anyhow!("Failed to create {:?}", kerr)); } } Ok(()) } #[test] #[serial] fn from_bincode() -> anyhow::Result<()> { match Kathoey::load("dict.bin") { Ok(k) => { assert_eq!("Я сделала это!", k.feminize("Я сделал это!")); assert_eq!("Я потеряла ключи", k.feminize("Я потерял ключи")); assert_eq!("Хорошо, я ответила.", k.feminize("Хорошо, я ответил.")); assert_eq!("Я не хотела этого говорить на случай, если ты увидишь", k.feminize("Я не хотел этого говорить на случай, если ты увидишь")); assert_eq!("Я уверена, что у него была идея получше, он просто забыл", k.feminize("Я уверен, что у него была идея получше, он просто забыл")); assert_eq!("Вообще-то, я была немного удивлена.", k.feminize("Вообще-то, я был немного удивлен.")); assert_eq!("Мне нравилось, когда я в аниме и не беспокойся о спойлерах.", k.feminize("Мне нравилось, когда я в аниме и не беспокойся о спойлерах.")); assert_eq!("Я скажу ему это.", k.feminize("Я скажу ему это.")); assert_eq!("Ничего страшного и спасибо, что посмотрел на меня, если ты когда-нибудь захочешь вернуться в Воу, я всегда рада играть с тобой.", k.feminize("Ничего страшного и спасибо, что посмотрел на меня, если ты когда-нибудь захочешь вернуться в Воу, я всегда рад играть с тобой.")); } Err(kerr) => { return Err(anyhow!("Failed to import bin {:?}", kerr)); } } Ok(()) } }