use crate::converter::main_helpers::{tag_name_eq, trim_trailing_whitespace};
use crate::options::{ConversionOptions, ListIndentType};
use tl;
type Context = crate::converter::Context;
type DomContext = crate::converter::DomContext;
pub const fn calculate_list_continuation_indent(depth: usize) -> usize {
if depth > 0 { 2 * depth - 1 } else { 0 }
}
pub fn is_loose_list(node_handle: tl::NodeHandle, parser: &tl::Parser, dom_ctx: &DomContext) -> bool {
if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
let children = tag.children();
{
for child_handle in children.top().iter() {
let is_li = dom_ctx.tag_info(child_handle.get_inner(), parser).map_or_else(
|| {
matches!(
child_handle.get(parser),
Some(tl::Node::Tag(child_tag))
if tag_name_eq(child_tag.name().as_utf8_str(), "li")
)
},
|info| info.name == "li",
);
if !is_li {
continue;
}
if let Some(tl::Node::Tag(child_tag)) = child_handle.get(parser) {
let li_children = child_tag.children();
for li_child_handle in li_children.top().iter() {
let is_p = dom_ctx.tag_info(li_child_handle.get_inner(), parser).map_or_else(
|| {
matches!(
li_child_handle.get(parser),
Some(tl::Node::Tag(li_child_tag))
if tag_name_eq(li_child_tag.name().as_utf8_str(), "p")
)
},
|info| info.name == "p",
);
if is_p {
return true;
}
}
}
}
}
}
false
}
pub fn add_list_continuation_indent(
output: &mut String,
list_depth: usize,
blank_line: bool,
options: &ConversionOptions,
) {
trim_trailing_whitespace(output);
if blank_line {
if !output.ends_with("\n\n") {
if output.ends_with('\n') {
output.push('\n');
} else {
output.push_str("\n\n");
}
}
} else if !output.ends_with('\n') {
output.push('\n');
}
let indent_level = calculate_list_continuation_indent(list_depth);
let indent_char = match options.list_indent_type {
ListIndentType::Tabs => "\t",
ListIndentType::Spaces => {
for _ in 0..options.list_indent_width {
output.push(' ');
}
return;
}
};
for _ in 0..indent_level {
output.push_str(indent_char);
}
}
pub fn continuation_indent_string(list_depth: usize, options: &ConversionOptions) -> Option<String> {
let indent_level = calculate_list_continuation_indent(list_depth);
if indent_level == 0 {
return None;
}
let mut indent = String::new();
match options.list_indent_type {
ListIndentType::Tabs => {
for _ in 0..indent_level {
indent.push('\t');
}
}
ListIndentType::Spaces => {
for _ in 0..(options.list_indent_width * indent_level) {
indent.push(' ');
}
}
}
Some(indent)
}
pub fn add_list_leading_separator(output: &mut String, ctx: &Context) {
if ctx.in_table_cell {
let is_table_continuation =
!output.is_empty() && !output.ends_with('|') && !output.ends_with(' ') && !output.ends_with("<br>");
if is_table_continuation {
output.push_str("<br>");
}
return;
}
if !output.is_empty() && !ctx.in_list {
let needs_newline =
!output.ends_with("\n\n") && !output.ends_with("* ") && !output.ends_with("- ") && !output.ends_with(". ");
if needs_newline {
output.push_str("\n\n");
}
return;
}
if ctx.in_list_item && !output.is_empty() {
let needs_newline =
!output.ends_with('\n') && !output.ends_with("* ") && !output.ends_with("- ") && !output.ends_with(". ");
if needs_newline {
trim_trailing_whitespace(output);
output.push('\n');
}
}
}
pub fn add_nested_list_trailing_separator(output: &mut String, ctx: &Context) {
if !ctx.in_list_item {
return;
}
if ctx.loose_list {
if !output.ends_with("\n\n") {
if !output.ends_with('\n') {
output.push('\n');
}
output.push('\n');
}
} else if !output.ends_with('\n') {
output.push('\n');
}
}
pub const fn calculate_list_nesting_depth(ctx: &Context) -> usize {
if ctx.in_list && !ctx.in_list_item {
ctx.list_depth + 1
} else {
ctx.list_depth
}
}
pub fn is_list_item(node_handle: tl::NodeHandle, parser: &tl::Parser, dom_ctx: &DomContext) -> bool {
if let Some(info) = dom_ctx.tag_info(node_handle.get_inner(), parser) {
return info.name == "li";
}
matches!(
node_handle.get(parser),
Some(tl::Node::Tag(tag)) if tag_name_eq(tag.name().as_utf8_str(), "li")
)
}
#[allow(clippy::too_many_arguments)]
pub fn process_list_children(
node_handle: tl::NodeHandle,
parser: &tl::Parser,
output: &mut String,
options: &ConversionOptions,
ctx: &Context,
depth: usize,
is_ordered: bool,
is_loose: bool,
nested_depth: usize,
start_counter: usize,
dom_ctx: &DomContext,
) {
let mut counter = start_counter;
if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
let children = tag.children();
{
for child_handle in children.top().iter() {
if let Some(tl::Node::Raw(bytes)) = child_handle.get(parser) {
if bytes.as_utf8_str().trim().is_empty() {
continue;
}
}
let list_ctx = Context {
in_ordered_list: is_ordered,
list_counter: if is_ordered { counter } else { 0 },
in_list: true,
list_depth: nested_depth,
ul_depth: if is_ordered { ctx.ul_depth } else { ctx.ul_depth + 1 },
loose_list: is_loose,
prev_item_had_blocks: false,
..ctx.clone()
};
use crate::converter::walk_node;
walk_node(child_handle, parser, output, options, &list_ctx, depth, dom_ctx);
if is_ordered && is_list_item(*child_handle, parser, dom_ctx) {
counter += 1;
}
}
}
}
}