use crate::constants::regexps;
use crate::dom::{build_match_string, get_tag_name, has_tag_name};
use crate::options::ReaderableOptions;
use crate::scoring::is_probably_visible;
use dom_query::{Document, Node, NodeId};
use hashbrown::HashSet;
pub fn is_probably_readerable(html: &str, options: Option<ReaderableOptions>) -> bool {
let doc = Document::from(html);
is_probably_readerable_doc(&doc, options)
}
pub(crate) fn is_probably_readerable_doc(
doc: &Document,
options: Option<ReaderableOptions>,
) -> bool {
let options = options.unwrap_or_default();
let mut score = 0.0;
let mut seen_div_ids: HashSet<NodeId> = HashSet::new();
let mut match_string_buf = String::with_capacity(128);
for node in doc.root().descendants_it().filter(|node| node.is_element()) {
let tag_name = get_tag_name(&node).unwrap_or_default();
if matches!(&*tag_name, "P" | "PRE" | "ARTICLE") {
if score_readerable_node(&node, &options, &mut score, &mut match_string_buf) {
return true;
}
continue;
}
if tag_name == "BR"
&& let Some(parent) = node.parent()
&& has_tag_name(&parent, "div")
&& seen_div_ids.insert(parent.id)
&& score_readerable_node(&parent, &options, &mut score, &mut match_string_buf)
{
return true;
}
}
false
}
fn score_readerable_node(
node: &Node<'_>,
options: &ReaderableOptions,
score: &mut f64,
match_string_buf: &mut String,
) -> bool {
if !is_probably_visible(node) {
return false;
}
build_match_string(node, match_string_buf);
let candidate_matches = regexps::CANDIDATE_FILTER_SET.matches(match_string_buf);
if candidate_matches.matched(0) && !candidate_matches.matched(1) {
return false;
}
let mut parent = node.parent();
while let Some(p) = parent {
if has_tag_name(&p, "li") {
return false;
}
parent = p.parent();
}
let text_length = node.normalized_char_count();
if text_length < options.min_content_length {
return false;
}
*score += ((text_length - options.min_content_length) as f64).sqrt();
*score > options.min_score
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_short_content_not_readerable() {
let html = "<html><body><p>Short</p></body></html>";
assert!(!is_probably_readerable(html, None));
}
#[test]
fn test_long_content_is_readerable() {
let long_text = "a".repeat(600);
let html = format!("<html><body><p>{}</p></body></html>", long_text);
assert!(is_probably_readerable(&html, None));
}
#[test]
fn test_unlikely_candidates_ignored() {
let long_text = "a".repeat(600);
let html = format!(
"<html><body><p class=\"sidebar\">{}</p></body></html>",
long_text
);
assert!(!is_probably_readerable(&html, None));
}
#[test]
fn test_article_tag_helps() {
let text = "a".repeat(600);
let html = format!("<html><body><article>{}</article></body></html>", text);
assert!(is_probably_readerable(&html, None));
}
}