use crate::{Html, HtmlChild, HtmlContainer, HtmlElement, HtmlTag};
use std::fmt::{self, Display, Formatter};
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
pub enum TableCellType {
#[default]
Data,
Header,
}
impl From<TableCellType> for HtmlTag {
fn from(value: TableCellType) -> Self {
match value {
TableCellType::Data => HtmlTag::TableCell,
TableCellType::Header => HtmlTag::TableHeaderCell,
}
}
}
impl Display for TableCellType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
HtmlTag::from(*self).fmt(f)
}
}
#[derive(Debug)]
pub struct TableCell(HtmlElement);
impl Default for TableCell {
fn default() -> Self {
Self::new(Default::default())
}
}
impl Html for TableCell {
fn to_html_string(&self) -> String {
self.0.to_html_string()
}
}
impl HtmlContainer for TableCell {
fn add_html<H: Html>(&mut self, html: H) {
self.0.add_child(HtmlChild::Raw(html.to_html_string()));
}
}
impl TableCell {
pub fn new(cell_type: TableCellType) -> Self {
Self(HtmlElement::new(cell_type.into()))
}
pub fn with_attributes<A, S>(mut self, attributes: A) -> Self
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
for (k, v) in attributes {
self.0.add_attribute(k, v);
}
self
}
}
#[derive(Debug)]
pub struct TableRow(HtmlElement);
impl Default for TableRow {
fn default() -> Self {
Self::new()
}
}
impl Html for TableRow {
fn to_html_string(&self) -> String {
self.0.to_html_string()
}
}
impl<T> From<T> for TableRow
where
T: IntoIterator,
T::Item: Display,
{
fn from(elements: T) -> Self {
elements.into_iter().fold(Self::new(), |a, n| {
a.with_cell(TableCell::default().with_raw(n))
})
}
}
impl TableRow {
pub fn new() -> Self {
Self(HtmlElement::new(HtmlTag::TableRow))
}
pub fn with_attributes<A, S>(mut self, attributes: A) -> Self
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
for (k, v) in attributes {
self.0.add_attribute(k, v);
}
self
}
pub fn add_cell(&mut self, cell: TableCell) {
self.0.add_child(cell.0.into())
}
pub fn with_cell(mut self, cell: TableCell) -> Self {
self.add_cell(cell);
self
}
}
#[derive(Debug)]
pub struct Table {
table: HtmlElement,
thead: HtmlElement,
tbody: HtmlElement,
tfoot: HtmlElement,
caption: Option<HtmlElement>,
}
impl Default for Table {
fn default() -> Self {
Self::new()
}
}
impl Html for Table {
fn to_html_string(&self) -> String {
let mut table = self
.table
.clone()
.with_child(self.thead.clone().into())
.with_child(self.tbody.clone().into());
if !self.tfoot.children.is_empty() || !self.tfoot.attributes.is_empty() {
table.add_child(self.tfoot.clone().into());
}
if let Some(caption) = self.caption.as_ref() {
table.add_child(caption.clone().into());
}
table.to_html_string()
}
}
impl<T> From<T> for Table
where
T: IntoIterator,
T::Item: IntoIterator,
<<T as std::iter::IntoIterator>::Item as IntoIterator>::Item: Display,
{
fn from(source: T) -> Self {
source
.into_iter()
.fold(Table::new(), |a, n| a.with_body_row(n))
}
}
impl Table {
pub fn new() -> Self {
Self {
table: HtmlElement::new(HtmlTag::Table),
thead: HtmlElement::new(HtmlTag::TableHeader),
tbody: HtmlElement::new(HtmlTag::TableBody),
tfoot: HtmlElement::new(HtmlTag::TableFooter),
caption: None,
}
}
pub fn add_attributes<A, S>(&mut self, attributes: A)
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
for (k, v) in attributes {
self.table.add_attribute(k, v);
}
}
pub fn with_attributes<A, S>(mut self, attributes: A) -> Self
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
self.add_attributes(attributes);
self
}
pub fn add_caption<H: Html>(&mut self, caption: H) {
self.caption = Some(HtmlElement::new(HtmlTag::TableCaption).with_html(caption));
}
pub fn with_caption<H: Html>(mut self, caption: H) -> Self {
self.add_caption(caption);
self
}
pub fn add_thead_attributes<A, S>(&mut self, attributes: A)
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
for (k, v) in attributes {
self.thead.add_attribute(k, v);
}
}
pub fn with_thead_attributes<A, S>(mut self, attributes: A) -> Self
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
self.add_thead_attributes(attributes);
self
}
pub fn add_tbody_attributes<A, S>(&mut self, attributes: A)
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
for (k, v) in attributes {
self.tbody.add_attribute(k, v);
}
}
pub fn with_tbody_attributes<A, S>(mut self, attributes: A) -> Self
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
self.add_tbody_attributes(attributes);
self
}
pub fn add_tfoot_attributes<A, S>(&mut self, attributes: A)
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
for (k, v) in attributes {
self.tfoot.add_attribute(k, v);
}
}
pub fn with_tfoot_attributes<A, S>(mut self, attributes: A) -> Self
where
A: IntoIterator<Item = (S, S)>,
S: ToString,
{
self.add_tfoot_attributes(attributes);
self
}
pub fn add_header_row<T>(&mut self, row: T)
where
T: IntoIterator,
T::Item: Display,
{
self.add_custom_header_row(row.into_iter().fold(TableRow::new(), |a, n| {
a.with_cell(TableCell::new(TableCellType::Header).with_raw(n))
}))
}
pub fn with_header_row<T>(mut self, row: T) -> Self
where
T: IntoIterator,
T::Item: Display,
{
self.add_header_row(row);
self
}
pub fn add_custom_header_row(&mut self, row: TableRow) {
self.thead.add_child(row.0.into());
}
pub fn with_custom_header_row(mut self, row: TableRow) -> Self {
self.add_custom_header_row(row);
self
}
pub fn add_body_row<T>(&mut self, row: T)
where
T: IntoIterator,
T::Item: Display,
{
self.add_custom_body_row(row.into_iter().fold(TableRow::new(), |a, n| {
a.with_cell(TableCell::default().with_raw(n))
}))
}
pub fn with_body_row<T>(mut self, row: T) -> Self
where
T: IntoIterator,
T::Item: Display,
{
self.add_body_row(row);
self
}
pub fn add_custom_body_row(&mut self, row: TableRow) {
self.tbody.add_child(row.0.into());
}
pub fn with_custom_body_row(mut self, row: TableRow) -> Self {
self.add_custom_body_row(row);
self
}
pub fn add_footer_row<T>(&mut self, row: T)
where
T: IntoIterator,
T::Item: Display,
{
self.add_custom_footer_row(row.into_iter().fold(TableRow::new(), |a, n| {
a.with_cell(TableCell::new(TableCellType::Header).with_raw(n))
}))
}
pub fn with_footer_row<T>(mut self, row: T) -> Self
where
T: IntoIterator,
T::Item: Display,
{
self.add_footer_row(row);
self
}
pub fn add_custom_footer_row(&mut self, row: TableRow) {
self.tfoot.add_child(row.0.into());
}
pub fn with_custom_footer_row(mut self, row: TableRow) -> Self {
self.add_custom_footer_row(row);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Html, HtmlContainer, HtmlElement, HtmlTag};
#[test]
fn test_from_arr() {
let arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
let result = Table::from(arr).to_html_string();
assert_eq!(
result,
concat!(
"<table><thead/><tbody>",
"<tr><td>1</td><td>2</td><td>3</td></tr>",
"<tr><td>4</td><td>5</td><td>6</td></tr>",
"<tr><td>7</td><td>8</td><td>9</td></tr>",
"</tbody></table>"
)
)
}
#[test]
fn test_from_vec() {
let arr = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let result = Table::from(&arr).to_html_string();
assert_eq!(
result,
concat!(
"<table><thead/><tbody>",
"<tr><td>1</td><td>2</td><td>3</td></tr>",
"<tr><td>4</td><td>5</td><td>6</td></tr>",
"<tr><td>7</td><td>8</td><td>9</td></tr>",
"</tbody></table>"
)
)
}
#[test]
fn test_inner_html() {
let table = Table::from([
[
HtmlElement::new(HtmlTag::Div)
.with_paragraph("This_is_column_one")
.to_html_string(),
HtmlElement::new(HtmlTag::Article)
.with_paragraph("This_is_column_two")
.to_html_string(),
],
[
HtmlElement::new(HtmlTag::Div).to_html_string(),
HtmlElement::new(HtmlTag::Div)
.with_table(Table::from([[1, 2], [3, 4]]))
.to_html_string(),
],
]);
let expected = "<table>
<thead/>
<tbody>
<tr>
<td><div><p>This_is_column_one</p></div></td>
<td><article><p>This_is_column_two</p></article></td>
</tr>
<tr>
<td><div/></td>
<td><div><table>
<thead/>
<tbody>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
</tbody>
</table></div></td>
</tr>
</tbody>
</table>";
assert_eq!(
table.to_html_string(),
expected
.chars()
.filter(|x| !x.is_ascii_whitespace())
.collect::<String>()
);
}
}