#[cfg(feature = "visitor")]
use crate::converter::utility::content::collect_tag_attributes;
use crate::options::ConversionOptions;
use crate::text;
#[allow(unused_imports)]
use std::collections::BTreeMap;
use tl::{NodeHandle, Parser};
type Context = crate::converter::Context;
type DomContext = crate::converter::DomContext;
pub fn handle(
tag_name: &str,
node_handle: &NodeHandle,
parser: &Parser,
output: &mut String,
options: &ConversionOptions,
ctx: &Context,
depth: usize,
dom_ctx: &DomContext,
) {
match tag_name {
"code" => {
handle_code(node_handle, parser, output, options, ctx, depth, dom_ctx);
}
"kbd" | "samp" => {
handle_kbd_samp(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx);
}
_ => {}
}
}
fn handle_code(
node_handle: &NodeHandle,
parser: &Parser,
output: &mut String,
options: &ConversionOptions,
ctx: &Context,
depth: usize,
dom_ctx: &DomContext,
) {
#[allow(unused_imports)]
use crate::converter::{serialize_node, walk_node};
let Some(node) = node_handle.get(parser) else { return };
let tag = match node {
tl::Node::Tag(tag) => tag,
_ => return,
};
let code_ctx = Context {
in_code: true,
..ctx.clone()
};
if ctx.in_code {
let children = tag.children();
for child_handle in children.top().iter() {
walk_node(child_handle, parser, output, options, &code_ctx, depth + 1, dom_ctx);
}
} else {
let mut content = String::with_capacity(32);
let children = tag.children();
{
for child_handle in children.top().iter() {
walk_node(
child_handle,
parser,
&mut content,
options,
&code_ctx,
depth + 1,
dom_ctx,
);
}
}
let trimmed = &content;
if !content.trim().is_empty() {
#[cfg(feature = "visitor")]
let code_output = if let Some(ref visitor_handle) = ctx.visitor {
use crate::visitor::{NodeContext, NodeType, VisitResult};
let attributes: BTreeMap<String, String> = collect_tag_attributes(tag);
let node_id = node_handle.get_inner();
let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
let node_ctx = NodeContext {
node_type: NodeType::Code,
tag_name: tag.name().as_utf8_str().to_string(),
attributes,
depth,
index_in_parent,
parent_tag,
is_inline: true,
};
let visit_result = {
let mut visitor = visitor_handle.borrow_mut();
visitor.visit_code_inline(&node_ctx, trimmed)
};
match visit_result {
VisitResult::Continue => None,
VisitResult::Custom(custom) => Some(custom),
VisitResult::Skip => Some(String::new()),
VisitResult::PreserveHtml => Some(serialize_node(node_handle, parser)),
VisitResult::Error(err) => {
if ctx.visitor_error.borrow().is_none() {
*ctx.visitor_error.borrow_mut() = Some(err);
}
None
}
}
} else {
None
};
#[cfg(feature = "visitor")]
if let Some(custom_output) = code_output {
output.push_str(&custom_output);
} else {
render_code_with_escaping(trimmed, output);
}
#[cfg(not(feature = "visitor"))]
{
render_code_with_escaping(trimmed, output);
}
}
}
}
fn handle_kbd_samp(
_tag_name: &str,
node_handle: &NodeHandle,
parser: &Parser,
output: &mut String,
options: &ConversionOptions,
ctx: &Context,
depth: usize,
dom_ctx: &DomContext,
) {
use crate::converter::{append_inline_suffix, chomp_inline, walk_node};
let Some(node) = node_handle.get(parser) else { return };
let _tag = match node {
tl::Node::Tag(tag) => tag,
_ => return,
};
let code_ctx = Context {
in_code: true,
..ctx.clone()
};
let mut content = String::with_capacity(32);
let children = _tag.children();
{
for child_handle in children.top().iter() {
walk_node(
child_handle,
parser,
&mut content,
options,
&code_ctx,
depth + 1,
dom_ctx,
);
}
}
let normalized = text::normalize_whitespace(&content);
let (prefix, suffix, trimmed) = chomp_inline(&normalized);
if !content.trim().is_empty() {
output.push_str(prefix);
output.push('`');
output.push_str(trimmed);
output.push('`');
append_inline_suffix(output, suffix, !trimmed.is_empty(), node_handle, parser, dom_ctx);
} else if !content.is_empty() {
output.push_str(prefix);
append_inline_suffix(output, suffix, false, node_handle, parser, dom_ctx);
}
}
fn render_code_with_escaping(trimmed: &str, output: &mut String) {
let contains_backtick = trimmed.contains('`');
let needs_delimiter_spaces = {
let first_char = trimmed.chars().next();
let last_char = trimmed.chars().last();
let starts_with_space = first_char == Some(' ');
let ends_with_space = last_char == Some(' ');
let starts_with_backtick = first_char == Some('`');
let ends_with_backtick = last_char == Some('`');
let all_spaces = trimmed.chars().all(|c| c == ' ');
all_spaces
|| starts_with_backtick
|| ends_with_backtick
|| (starts_with_space && ends_with_space && contains_backtick)
};
let (num_backticks, needs_spaces) = if contains_backtick {
let max_consecutive = trimmed
.chars()
.fold((0, 0), |(max, current), c| {
if c == '`' {
let new_current = current + 1;
(max.max(new_current), new_current)
} else {
(max, 0)
}
})
.0;
let num = if max_consecutive == 1 { 2 } else { 1 };
(num, needs_delimiter_spaces)
} else {
(1, needs_delimiter_spaces)
};
for _ in 0..num_backticks {
output.push('`');
}
if needs_spaces {
output.push(' ');
}
output.push_str(trimmed);
if needs_spaces {
output.push(' ');
}
for _ in 0..num_backticks {
output.push('`');
}
}