use super::StructuredPrinter;
use super::TagHandler;
use super::{clean_markdown, walk};
use std::sync::Arc;
use std::{cmp, collections::HashMap};
use html5ever::LocalName;
use markup5ever_rcdom::{Handle, NodeData};
use url::Url;
#[derive(Default)]
pub struct TableHandler {
commonmark: bool,
url: Option<Arc<Url>>,
}
const TD: LocalName = html5ever::local_name!("td");
const TH: LocalName = html5ever::local_name!("th");
const TABLE_LIMIT: usize = 1000;
impl TableHandler {
pub fn new(commonmark: bool, url: Option<std::sync::Arc<Url>>) -> Self {
TableHandler { commonmark, url }
}
}
impl TagHandler for TableHandler {
fn handle(&mut self, tag: &Handle, printer: &mut StructuredPrinter) {
let mut table_markup = String::new();
let any_matcher = |cell: &Handle| match cell.data {
NodeData::Element { ref name, .. } => name.local == TD || name.local == TH,
_ => false,
};
let mut column_widths: Vec<usize> = Vec::new();
let rows = find_children(tag, "tr");
let most_big_row = rows.iter().max_by(|left, right| {
collect_children(left, any_matcher)
.len()
.cmp(&collect_children(right, any_matcher).len())
});
if most_big_row.is_some() {
let column_count = match most_big_row {
Some(tag) => collect_children(tag, any_matcher).len(),
_ => 0,
};
column_widths = vec![3; column_count];
for (idx, row) in rows.iter().enumerate() {
if idx >= TABLE_LIMIT {
break;
}
let cells = collect_children(row, any_matcher);
for index in 0..column_count {
if let Some(cell) = cells.get(index) {
let text = to_text(cell, self.commonmark, &self.url);
column_widths[index] = cmp::max(column_widths[index], text.chars().count());
}
}
}
for (idx, row) in rows.iter().enumerate() {
if idx >= TABLE_LIMIT {
break;
}
table_markup.push('|');
let cells = collect_children(row, any_matcher);
for index in 0..column_count {
let padded_cell_text = pad_cell_text(
&cells.get(index),
column_widths[index],
self.commonmark,
&self.url,
);
table_markup.push_str(&padded_cell_text);
table_markup.push('|');
}
table_markup.push('\n');
if idx == 0 {
table_markup.push('|');
for index in 0..column_count {
let width = column_widths[index];
if width < 3 {
table_markup.push_str(&"-".repeat(width.max(2)));
table_markup.push('|');
continue;
}
table_markup.push('|');
}
table_markup.push('\n');
}
}
tag.children.take();
printer.insert_newline();
printer.insert_newline();
printer.append_str(&table_markup);
}
}
fn after_handle(&mut self, _printer: &mut StructuredPrinter) {}
fn skip_descendants(&self) -> bool {
true
}
}
fn pad_cell_text(
tag: &Option<&Handle>,
column_width: usize,
commonmark: bool,
url: &Option<Arc<Url>>,
) -> String {
let mut result = String::new();
if let Some(cell) = tag {
let text = to_text(cell, commonmark, url);
let len_diff = column_width
.checked_sub(text.chars().count())
.unwrap_or_default();
if len_diff > 0 {
if len_diff > 1 {
result.push(' ');
result.push_str(&text);
result.push(' ');
} else {
result.push_str(&text);
result.push(' ');
}
} else {
result.push_str(&text);
}
} else {
result.push(' ');
}
result
}
fn tag_name(tag: &Handle) -> String {
match tag.data {
NodeData::Element { ref name, .. } => name.local.to_string(),
_ => String::new(),
}
}
fn find_children(tag: &Handle, name: &str) -> Vec<Handle> {
let children = tag.children.borrow();
let mut result: Vec<Handle> = vec![];
for child in children.iter() {
if tag_name(child) == name {
result.push(child.clone());
}
let mut descendants = find_children(child, name);
result.append(&mut descendants);
}
result
}
fn collect_children<P>(tag: &Handle, predicate: P) -> Vec<Handle>
where
P: Fn(&Handle) -> bool,
{
let children = tag.children.borrow();
let mut result: Vec<Handle> = Vec::with_capacity(children.len());
for child in children.iter() {
if predicate(child) {
result.push(child.clone());
}
}
result
}
fn to_text(tag: &Handle, commonmark: bool, url: &Option<Arc<Url>>) -> String {
let mut printer = StructuredPrinter::default();
walk(
&tag,
&mut printer,
&HashMap::default(),
commonmark,
&url,
true,
);
clean_markdown(&printer.data)
}