use std::collections::BTreeMap;
use std::rc::Rc;
use crate::error::{Error, Result};
use crate::hyphenation::{Hyphenator, LiangHyphenator};
use crate::language::language::value_as_f64;
use crate::language::Language;
use crate::syllable::{SyllableCounter, SyllableCounterKind};
use super::splitter::TextSplitter;
use super::statistics::TextStatistics;
pub struct TextAnalyzer {
hyphenator: Rc<LiangHyphenator>,
syllable_counter: SyllableCounterKind,
splitter: TextSplitter,
language: Rc<Language>,
}
impl TextAnalyzer {
pub fn new(
hyphenator: Rc<LiangHyphenator>,
syllable_counter: SyllableCounterKind,
splitter: TextSplitter,
language: Rc<Language>,
) -> Self {
TextAnalyzer {
hyphenator,
syllable_counter,
splitter,
language,
}
}
pub fn split_word(&self, word: &str) -> Vec<String> {
self.hyphenator.hyphenate(word)
}
pub fn split_syllables(&self, word: &str) -> Vec<String> {
self.syllable_counter.split_syllables(word)
}
pub fn syllable_count(&self, word: &str) -> i64 {
self.syllable_counter.count_syllables(word)
}
pub fn word_count(&self, text: &str) -> i64 {
self.splitter.count_words(text)
}
pub fn sentence_count(&self, text: &str) -> i64 {
self.splitter.count_sentences(text)
}
pub fn letter_count(&self, text: &str) -> i64 {
self.splitter.count_letters(text)
}
pub fn total_syllables(&self, text: &str) -> i64 {
self.splitter
.split_words(text)
.iter()
.map(|w| self.syllable_counter.count_syllables(w))
.sum()
}
pub fn average_syllables_per_word(&self, text: &str) -> f64 {
let words = self.splitter.split_words(text);
if words.is_empty() {
return 0.0;
}
let total: i64 = words
.iter()
.map(|w| self.syllable_counter.count_syllables(w))
.sum();
total as f64 / words.len() as f64
}
pub fn average_words_per_sentence(&self, text: &str) -> f64 {
let word_count = self.splitter.count_words(text);
let sentence_count = self.splitter.count_sentences(text);
if sentence_count == 0 {
word_count as f64
} else {
word_count as f64 / sentence_count as f64
}
}
pub fn words_with_more_than_n_syllables(
&self,
text: &str,
n: i64,
count_proper_nouns: bool,
) -> i64 {
let mut count = 0i64;
for word in self.splitter.split_words(text) {
if self.syllable_counter.count_syllables(&word) > n {
let counts = count_proper_nouns
|| word.chars().next().is_some_and(|first| {
first.to_string() != first.to_uppercase().collect::<String>()
});
if counts {
count += 1;
}
}
}
count
}
pub fn polysyllable_count(&self, text: &str, count_proper_nouns: bool) -> i64 {
self.words_with_more_than_n_syllables(text, 2, count_proper_nouns)
}
pub fn histogram_syllables(&self, text: &str) -> BTreeMap<i64, i64> {
let mut histogram: BTreeMap<i64, i64> = BTreeMap::new();
for word in self.splitter.split_words(text) {
let syllables = self.syllable_counter.count_syllables(&word);
if syllables > 0 {
*histogram.entry(syllables).or_insert(0) += 1;
}
}
histogram
}
pub fn analyze(&self, text: &str) -> Result<TextStatistics> {
let text = text.trim();
let words = self.splitter.split_words(text);
let word_count = words.len() as i64;
if word_count == 0 {
return Err(Error::EmptyText);
}
let letter_count = self.splitter.count_letters(text);
let sentence_count = self.splitter.count_sentences(text);
let mut total_syllables = 0i64;
let mut polysyllable_count = 0i64;
let mut histogram: BTreeMap<i64, i64> = BTreeMap::new();
for word in &words {
let syllables = self.syllable_counter.count_syllables(word);
total_syllables += syllables;
if syllables > 2 {
polysyllable_count += 1;
}
if syllables > 0 {
*histogram.entry(syllables).or_insert(0) += 1;
}
}
let sentence_count_for_average = sentence_count.max(1);
let long_word_threshold = self
.language
.get_formula_config("lix")
.and_then(|cfg| cfg.get("longWordThreshold"))
.and_then(value_as_f64)
.map(|v| v as i64)
.unwrap_or(6);
let long_word_count = self.splitter.count_long_words(text, long_word_threshold);
Ok(TextStatistics {
letter_count,
word_count,
sentence_count,
syllable_count: total_syllables,
polysyllable_count,
average_syllables_per_word: total_syllables as f64 / word_count as f64,
average_words_per_sentence: word_count as f64 / sentence_count_for_average as f64,
long_word_count,
syllable_histogram: histogram,
})
}
pub fn add_hyphenations<I, K, V>(&self, hyphenations: I)
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
self.hyphenator.add_hyphenations(hyphenations);
}
}