use std::cell::RefCell;
use std::collections::HashMap;
use super::exceptions::HyphenationExceptionsCollection;
use super::pattern::PatternsCollection;
pub trait Hyphenator {
fn hyphenate(&self, word: &str) -> Vec<String>;
fn count_syllables(&self, word: &str) -> i64;
}
pub struct LiangHyphenator {
patterns: PatternsCollection,
exceptions: HyphenationExceptionsCollection,
min_hyphen_left: i32,
min_hyphen_right: i32,
user_hyphenations: RefCell<HashMap<String, String>>,
}
impl LiangHyphenator {
pub fn new(
patterns: PatternsCollection,
exceptions: HyphenationExceptionsCollection,
min_hyphen_left: i32,
min_hyphen_right: i32,
) -> Self {
LiangHyphenator {
patterns,
exceptions,
min_hyphen_left,
min_hyphen_right,
user_hyphenations: RefCell::new(HashMap::new()),
}
}
pub fn add_hyphenations<I, K, V>(&self, hyphenations: I)
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
let mut map = self.user_hyphenations.borrow_mut();
for (word, hyphenated) in hyphenations {
map.insert(
word.as_ref().to_lowercase(),
hyphenated.as_ref().to_lowercase(),
);
}
}
fn split_by_hyphenation(hyphenated: &str, original_word: &str) -> Vec<String> {
let orig: Vec<char> = original_word.chars().collect();
let mut parts: Vec<String> = Vec::new();
let mut part = String::new();
let mut j = 0usize;
for ch in hyphenated.chars() {
if ch == '-' {
parts.push(std::mem::take(&mut part));
} else {
if j < orig.len() {
part.push(orig[j]);
}
j += 1;
}
}
if !part.is_empty() {
parts.push(part);
}
parts
}
fn split_by_patterns(&self, word: &str, word_len: usize, word_lower: &str) -> Vec<String> {
#![allow(clippy::needless_range_loop)]
let left = self.min_hyphen_left.max(0) as usize;
let right = self.min_hyphen_right.max(0) as usize;
let text = format!(".{word_lower}.");
let text_chars: Vec<char> = text.chars().collect();
let text_length = word_len + 2;
let mut pattern_length = self.patterns.max_length();
if pattern_length > text_length {
pattern_length = text_length;
}
let mut scores: Vec<Option<i32>> = vec![None; text_length + 1];
let end = text_length.saturating_sub(right);
for start in 0..end {
let max_len = pattern_length.min(text_length - start);
for len in 1..=max_len {
let subword: String = text_chars[start..start + len].iter().collect();
if let Some(weights) = self.patterns.get_weights(&subword) {
for (offset, &w) in weights.iter().enumerate() {
let idx = start + offset;
if scores[idx].map_or(true, |s| w > s) {
scores[idx] = Some(w);
}
}
}
}
}
let word_chars: Vec<char> = word.chars().collect();
let mut parts: Vec<String> = Vec::new();
let mut part: String = word_chars[0..left.min(word_chars.len())].iter().collect();
let break_end = text_length.saturating_sub(right);
for i in (left + 1)..break_end {
if let Some(score) = scores[i] {
if score & 1 != 0 {
parts.push(std::mem::take(&mut part));
}
}
if let Some(&c) = word_chars.get(i - 1) {
part.push(c);
}
}
for i in break_end..(text_length - 1) {
if let Some(&c) = word_chars.get(i - 1) {
part.push(c);
}
}
if !part.is_empty() {
parts.push(part);
}
parts
}
}
impl Hyphenator for LiangHyphenator {
fn hyphenate(&self, word: &str) -> Vec<String> {
let word_length = word.chars().count();
if word_length == 0 {
return Vec::new();
}
if (word_length as i32) < self.min_hyphen_left + self.min_hyphen_right {
return vec![word.to_string()];
}
let word_lower = word.to_lowercase();
if let Some(h) = self.user_hyphenations.borrow().get(&word_lower) {
return Self::split_by_hyphenation(h, word);
}
if let Some(h) = self.exceptions.get(&word_lower) {
return Self::split_by_hyphenation(h, word);
}
self.split_by_patterns(word, word_length, &word_lower)
}
fn count_syllables(&self, word: &str) -> i64 {
let parts = self.hyphenate(word);
if parts.is_empty() {
0
} else {
parts.len() as i64
}
}
}