use crate::converter::dom_context::DomContext;
use crate::converter::main_helpers::is_inline_element;
use crate::converter::utility::attributes::{attribute_matches_any, element_has_navigation_hint};
use crate::options::ConversionOptions;
pub fn inline_ancestor_allows_block(tag_name: &str) -> bool {
matches!(tag_name, "a" | "ins" | "del")
}
#[derive(Clone, Copy)]
struct MisnestState {
inside_preformatted: bool,
blocked_by_inline_ancestor: bool,
p_ancestor_state: bool,
}
impl MisnestState {
const ROOT: Self = Self {
inside_preformatted: false,
blocked_by_inline_ancestor: false,
p_ancestor_state: false,
};
}
pub fn has_inline_block_misnest(dom_ctx: &DomContext, parser: &tl::Parser) -> bool {
let mut stack: Vec<(tl::NodeHandle, MisnestState)> = dom_ctx
.root_children
.iter()
.map(|handle| (*handle, MisnestState::ROOT))
.collect();
while let Some((handle, state)) = stack.pop() {
let node_id = handle.get_inner();
if !matches!(handle.get(parser), Some(tl::Node::Tag(_))) {
continue;
}
let Some(info) = dom_ctx.tag_info(node_id, parser) else {
continue;
};
if matches!(info.name.as_str(), "td" | "tr" | "th") && state.p_ancestor_state {
return true;
}
let self_inside_preformatted = state.inside_preformatted || matches!(info.name.as_str(), "pre" | "code");
if info.is_block && !self_inside_preformatted && state.blocked_by_inline_ancestor {
return true;
}
if let Some(children) = dom_ctx.children_of(node_id) {
let child_state = MisnestState {
inside_preformatted: self_inside_preformatted,
blocked_by_inline_ancestor: state.blocked_by_inline_ancestor
|| (is_inline_element(&info.name) && !inline_ancestor_allows_block(&info.name)),
p_ancestor_state: p_ancestor_state_for(&info.name, state.p_ancestor_state),
};
stack.extend(children.iter().map(|child| (*child, child_state)));
}
}
false
}
fn p_ancestor_state_for(tag_name: &str, inherited: bool) -> bool {
if tag_name == "p" {
true
} else if matches!(tag_name, "table" | "body" | "html") {
false
} else {
inherited
}
}
pub fn should_drop_for_preprocessing(tag_name: &str, tag: &tl::HTMLTag, options: &ConversionOptions) -> bool {
use crate::options::PreprocessingPreset;
if !options.preprocessing.enabled {
return false;
}
let preset = options.preprocessing.preset;
if preset == PreprocessingPreset::Minimal {
return false;
}
if options.preprocessing.remove_forms && tag_name == "form" {
return true;
}
let is_aggressive = preset == PreprocessingPreset::Aggressive;
if is_aggressive && tag_name == "noscript" {
return true;
}
if !options.preprocessing.remove_navigation {
return false;
}
let has_nav_hint = element_has_navigation_hint(tag);
if tag_name == "nav" {
return true;
}
if tag_name == "header" {
return has_nav_hint;
}
if tag_name == "footer" || tag_name == "aside" {
return is_aggressive || has_nav_hint;
}
if is_aggressive && has_nav_hint {
return true;
}
if is_aggressive {
if element_has_noise_hint(tag) {
return true;
}
}
false
}
fn element_has_noise_hint(tag: &tl::HTMLTag) -> bool {
const NOISE_KEYWORDS: &[&str] = &[
"cookie",
"consent",
"gdpr",
"banner",
"advertisement",
"ad-container",
"advert",
"social-share",
"share-buttons",
"popup",
"modal-overlay",
"newsletter-signup",
];
attribute_matches_any(tag, "class", NOISE_KEYWORDS) || attribute_matches_any(tag, "id", NOISE_KEYWORDS)
}