use std::borrow::Cow;
use crate::converter::utility::content::is_block_level_element;
use crate::visitor::{NodeContext, NodeType, VisitResult};
#[derive(Debug)]
pub struct VisitorElementState<'p> {
parent_tag: Option<&'p str>,
index_in_parent: usize,
is_inline: bool,
}
impl<'p> VisitorElementState<'p> {
fn build_node_ctx<'a>(&'a self, tag_name: &'a str, tag: &'a tl::HTMLTag<'a>, depth: usize) -> NodeContext<'a>
where
'p: 'a,
{
NodeContext::with_lazy_attributes(
NodeType::Element,
Cow::Borrowed(tag_name),
tag,
depth,
self.index_in_parent,
self.parent_tag.map(Cow::Borrowed),
self.is_inline,
)
}
}
pub fn handle_visitor_element_start<'p>(
visitor_handle: &crate::visitor::VisitorHandle,
tag_name: &str,
node_handle: &tl::NodeHandle,
tag: &tl::HTMLTag,
parser: &'p tl::Parser<'p>,
output: &mut String,
_ctx: &crate::converter::Context,
depth: usize,
dom_ctx: &'p crate::converter::DomContext,
) -> (VisitAction, Option<VisitorElementState<'p>>) {
let state = VisitorElementState {
parent_tag: dom_ctx.parent_tag_name(node_handle.get_inner(), parser),
index_in_parent: dom_ctx.get_sibling_index(node_handle.get_inner()).unwrap_or(0),
is_inline: !is_block_level_element(tag_name),
};
let node_ctx = state.build_node_ctx(tag_name, tag, depth);
let visitor_start_result = {
let mut visitor = visitor_handle.lock().expect("visitor mutex poisoned");
visitor.visit_element_start(&node_ctx)
};
match visitor_start_result {
crate::visitor::VisitResult::Continue => (VisitAction::Continue, Some(state)),
crate::visitor::VisitResult::Skip => (VisitAction::Skip, None),
crate::visitor::VisitResult::Custom(custom_output) => {
output.push_str(&custom_output);
if !matches!(tag_name, "table") {
let element_content = &custom_output;
let mut visitor = visitor_handle.lock().expect("visitor mutex poisoned");
let _ = visitor.visit_element_end(&node_ctx, element_content);
}
(VisitAction::Custom, None)
}
crate::visitor::VisitResult::Error(_msg) => (VisitAction::Error, None),
crate::visitor::VisitResult::PreserveHtml => (VisitAction::Continue, Some(state)),
}
}
pub fn handle_visitor_element_end(
visitor_handle: &crate::visitor::VisitorHandle,
tag_name: &str,
state: &VisitorElementState<'_>,
tag: &tl::HTMLTag,
output: &mut String,
element_output_start: usize,
ctx: &crate::converter::Context,
depth: usize,
) {
if matches!(tag_name, "table") {
return;
}
let node_ctx = state.build_node_ctx(tag_name, tag, depth);
let safe_start = element_output_start.min(output.len());
let safe_start = crate::converter::utility::content::floor_char_boundary(output, safe_start);
let element_content = &output[safe_start..];
let mut visitor = visitor_handle.lock().expect("visitor mutex poisoned");
match visitor.visit_element_end(&node_ctx, element_content) {
VisitResult::Continue => {}
VisitResult::Custom(custom) => {
output.truncate(safe_start);
output.push_str(&custom);
}
VisitResult::Skip => {
output.truncate(safe_start);
}
VisitResult::Error(err) => {
if ctx.visitor_error.borrow().is_none() {
*ctx.visitor_error.borrow_mut() = Some(err);
}
}
VisitResult::PreserveHtml => {}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VisitAction {
Continue,
Skip,
Custom,
Error,
}