use sxd_xpath::nodeset::Node; use crate::Error; pub struct Table { size: (usize, usize), cells: Vec>, } impl Table { 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>> { 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 Table where T: Clone, { pub fn new(size: (usize, usize)) -> Self { Self { size, cells: vec![None; size.0 * size.1], } } pub fn map(&self, f: impl Fn(usize, usize, &T) -> T2) -> Table where T2: Clone, { map_table(self, f) } } fn map_table(table: &Table, f: F) -> Table 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> { pub fn to_string_table(&self) -> Table { 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 Table 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 { 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) } }