mod utils;
use crate::logging::logger::*;
use crate::logging::logging_defs::*;
use crate::parser::{new_html_element, NodeExt, NodeRef};
use crate::utils::*;
use std::collections::HashSet;
use std::sync::LazyLock;
use utils::*;
pub static MUST_BE_NOT_EMPTY_TAGS: LazyLock<HashSet<&'static str>> =
LazyLock::new(|| HashSet::from(["div", "section", "h1", "h2", "h3", "h4", "h5", "h6"]));
pub static DEFAULT_ELEMENTS_TO_SCORE: LazyLock<HashSet<&'static str>> =
LazyLock::new(|| HashSet::from(["section", "h2", "h3", "h4", "h5", "h6", "p", "td", "pre"]));
pub static CONTENT_BLOCK_ELEMENTS: LazyLock<Vec<&'static str>> = LazyLock::new(|| {
vec![
"blockquote", "dl", "div", "img", "ol", "p", "pre", "table", "ul",
]
});
#[allow(clippy::cognitive_complexity)]
pub fn clean(
doc_node: &NodeRef,
by_line: &mut Option<String>,
strip_unlikely_candidates: bool,
article_title: &str,
logger: &PerfLogger,
) -> Vec<NodeRef> {
let mut elements_to_score: Vec<NodeRef> = vec![];
let mut node = Some(doc_node.clone());
let mut should_remove_title_header = !article_title.trim().is_empty();
start_span!(logger, CONTENT_CLEANUP);
while node.is_some() {
add_point_to_span_str!(
logger,
CONTENT_CLEANUP,
"marking_start_of_content_cleanup_loop"
);
let unwrapped_node = node.clone().unwrap();
if is_probably_hidden(&unwrapped_node)
&& !(unwrapped_node.element_name() == Some("a")
&& unwrapped_node.attr_value("id").is_some())
{
node = remove_and_get_next(&unwrapped_node);
add_point_to_span_str!(logger, CONTENT_CLEANUP, "early_pass_in_is_probably_hidden");
continue;
}
add_point_to_span_str!(logger, CONTENT_CLEANUP, "past_is_probably_hidden");
let match_string = match_string_for_node(&unwrapped_node);
add_point_to_span_str!(logger, CONTENT_CLEANUP, "fetched_matching_str_for_the_node");
if unwrapped_node.attr_value("aria-modal").as_deref() == Some("true")
&& unwrapped_node.attr_value("role").as_deref() == Some("dialog")
{
node = remove_and_get_next(&unwrapped_node);
add_point_to_span_str!(logger, CONTENT_CLEANUP, "early_pass_in_modal_dialog");
continue;
}
if by_line.is_none() {
*by_line = get_article_by_line(&unwrapped_node, match_string.as_str());
if by_line.is_some() {
node = remove_and_get_next(&unwrapped_node);
add_point_to_span_str!(
logger,
CONTENT_CLEANUP,
"early_pass_in_check_and_set_article_by_line"
);
continue;
}
}
add_point_to_span_str!(
logger,
CONTENT_CLEANUP,
"past_check_and_set_article_by_line"
);
if should_remove_title_header && header_duplicates_title(&unwrapped_node, article_title) {
node = remove_and_get_next(&unwrapped_node);
should_remove_title_header = false;
add_point_to_span_str!(
logger,
CONTENT_CLEANUP,
"early_pass_in_header_duplicates_title"
);
continue;
}
if strip_unlikely_candidates {
if let Some(n) = strip_unlikely_and_get_next(&unwrapped_node, match_string.as_str()) {
node = Some(n);
add_point_to_span_str!(
logger,
CONTENT_CLEANUP,
"early_pass_in_strip_unlikely_candidates"
);
continue;
}
if let Some(role) = unwrapped_node.attr_value("role") {
if UNLIKELY_ROLES.contains(role.as_str()) {
node = remove_and_get_next(&unwrapped_node);
add_point_to_span_str!(
logger,
CONTENT_CLEANUP,
"early_pass_in_unlikely_roles"
);
continue;
}
}
}
add_point_to_span_str!(logger, CONTENT_CLEANUP, "past_strip_unlikely_candidates");
let node_name = unwrapped_node.element_name().unwrap_or_default();
if MUST_BE_NOT_EMPTY_TAGS.contains(node_name)
&& is_element_without_content(&unwrapped_node)
{
node = remove_and_get_next(&unwrapped_node);
add_point_to_span_str!(
logger,
CONTENT_CLEANUP,
"early_pass_in_MUST_BE_NOT_EMPTY_TAGS"
);
continue;
}
add_point_to_span_str!(logger, CONTENT_CLEANUP, "past_MUST_BE_NOT_EMPTY_TAGS");
if DEFAULT_ELEMENTS_TO_SCORE.contains(node_name) {
elements_to_score.push(unwrapped_node.clone());
}
add_point_to_span_str!(logger, CONTENT_CLEANUP, "past_DEFAULT_ELEMENTS_TO_SCORE");
if node_name == "div" {
add_point_to_span_str!(logger, CONTENT_CLEANUP, "before_process_div_node");
if let Some(n) =
process_div_node(unwrapped_node.clone(), &mut elements_to_score, logger)
{
node = Some(n);
}
add_point_to_span_str!(logger, CONTENT_CLEANUP, "after_process_div_node");
}
node = get_next_node(&node.clone().unwrap(), false);
}
end_span!(logger, CONTENT_CLEANUP);
elements_to_score
}
fn process_div_node(
node: NodeRef,
elements_to_score: &mut Vec<NodeRef>,
logger: &PerfLogger,
) -> Option<NodeRef> {
let mut result_node: Option<NodeRef> = None;
let mut child = node.first_child();
while let Some(current) = child {
if is_phrasing_content(¤t) {
let p = new_html_element("p");
let mut next = Some(current.clone());
while let Some(n) = next.clone() {
if !is_phrasing_content(&n) {
break;
}
let next_sibling = n.next_sibling();
p.append(n);
next = next_sibling;
}
while let Some(first) = p.first_child() {
if is_whitespace_node(&first) {
first.detach();
} else {
break;
}
}
while let Some(last) = p.last_child() {
if is_whitespace_node(&last) {
last.detach();
} else {
break;
}
}
if p.first_child().is_some() {
if let Some(insertion_point) = next.clone() {
insertion_point.insert_before(p.clone());
} else {
node.append(p.clone());
}
}
child = next;
continue;
}
child = current.next_sibling();
}
if contains_single_tag_in_element(&node, "p") && get_link_density(&node, logger) < 0.25 {
let single_paragraph_node = node.first_element_child().unwrap();
node.insert_after(single_paragraph_node.clone());
node.detach();
elements_to_score.push(single_paragraph_node.clone());
result_node = Some(single_paragraph_node);
} else if !node_contains_any_tag_of(&node, &CONTENT_BLOCK_ELEMENTS) {
let new_node = node.clone_and_rename_element("p");
elements_to_score.push(new_node.clone());
result_node = Some(new_node);
}
result_node
}