sxd_html_table 0.1.0

A HTML table parser based on sxd_html.
Documentation
use sxd_xpath::nodeset::Node;

use crate::Error;

pub struct Table<T> {
    size: (usize, usize),
    cells: Vec<Option<T>>,
}

impl<T> Table<T> {
    pub fn set(&mut self, row: usize, col: usize, item: T) {
        self.cells[row * self.size.1 + col] = Some(item);
    }

    pub fn rows(&self) -> Vec<Vec<Option<&T>>> {
        let mut rows = vec![];
        for i in 0..self.size.0 {
            let mut row = vec![];
            for j in 0..self.size.1 {
                row.push(self.cells[i * self.size.1 + j].as_ref());
            }
            rows.push(row);
        }
        rows
    }
}

impl<T> Table<T>
where
    T: Clone,
{
    pub fn new(size: (usize, usize)) -> Self {
        Self {
            size,
            cells: vec![None; size.0 * size.1],
        }
    }

    pub fn map<T2>(&self, f: impl Fn(usize, usize, &T) -> T2) -> Table<T2>
    where
        T2: Clone,
    {
        map_table(self, f)
    }
}

fn map_table<S, T, F>(table: &Table<T>, f: F) -> Table<S>
where
    F: Fn(usize, usize, &T) -> S,
    S: Clone,
{
    let mut new_table = Table::new(table.size);
    for i in 0..table.size.0 {
        for j in 0..table.size.1 {
            if let Some(item) = &table.cells[i * table.size.1 + j] {
                new_table.set(i, j, f(i, j, item));
            }
        }
    }
    new_table
}

impl Table<Node<'_>> {
    pub fn to_string_table(&self) -> Table<String> {
        self.map(|_, _, node| node.string_value())
    }

    pub fn to_string_table_with_header(&self) -> Table<(String, bool)> {
        self.map(|_, _, node| {
            let Some(element) = node.element() else {
                return (node.string_value(), false);
            };
            let is_header = element.name() == "th".into();
            (node.string_value(), is_header)
        })
    }
}

impl<T> Table<T>
where
    T: std::fmt::Display,
{
    pub fn write_csv(&self, writer: &mut impl std::io::Write) -> Result<(), Error> {
        let mut writer = csv::Writer::from_writer(writer);
        for row in &self.rows() {
            let mut record = csv::StringRecord::new();
            for cell in row {
                if let Some(item) = cell {
                    record.push_field(&item.to_string());
                } else {
                    record.push_field("");
                }
            }
            writer
                .write_record(&record)
                .map_err(|_| Error::FailedToConvertToCSV)?;
        }
        writer.flush().map_err(|_| Error::FailedToConvertToCSV)?;
        Ok(())
    }

    pub fn to_csv(&self) -> Result<String, Error> {
        let mut buf = std::io::BufWriter::new(Vec::new());
        self.write_csv(&mut buf)?;
        let bytes = buf.into_inner().map_err(|_| Error::FailedToConvertToCSV)?;
        String::from_utf8(bytes).map_err(|_| Error::FailedToConvertToCSV)
    }
}