use anyhow::Result;
use serde_json::{from_str, Value};
use crate::get_font_for_locale;
#[derive(Debug)]
pub struct TableCell {
content: String,
is_header: bool,
}
impl TableCell {
pub fn new(content: &str) -> Self {
Self {
content: content.to_string(),
is_header: false,
}
}
pub fn header(content: &str) -> Self {
Self {
content: content.to_string(),
is_header: true,
}
}
}
#[derive(Debug)]
pub struct TableRow {
cells: Vec<TableCell>,
}
impl TableRow {
pub fn new() -> Self {
Self { cells: Vec::new() }
}
pub fn add_cell(&mut self, cell: TableCell) {
self.cells.push(cell);
}
}
#[derive(Debug)]
pub struct Table {
rows: Vec<TableRow>,
width: u32,
default_font: String,
font_size: u32,
font_color: String,
font_bold: bool,
has_custom_typography: bool,
locale: String,
}
impl Table {
pub fn new() -> Self {
Self {
rows: Vec::new(),
width: 0,
default_font: "Arial".to_owned(),
font_size: 10,
font_color: "000000".to_string(),
font_bold: false,
has_custom_typography: false,
locale: "en".to_string(),
}
}
pub fn with_locale(mut self, locale: String) -> Self {
self.locale = locale.clone();
if !self.has_custom_typography {
self.default_font = get_font_for_locale(&locale);
}
self
}
pub fn with_font(mut self, font: String) -> Self {
self.default_font = font;
self
}
pub fn with_typography(mut self, font: String, size: u32, color: String, bold: bool) -> Self {
self.default_font = font;
self.font_size = size;
self.font_color = color.trim_start_matches('#').to_owned();
self.font_bold = bold;
self.has_custom_typography = true;
self
}
pub fn add_row(&mut self, row: TableRow) {
if self.width == 0 && !row.cells.is_empty() {
self.width = row.cells.len() as u32;
}
self.rows.push(row);
}
pub fn generate_table_xml(&self) -> String {
let mut xml = String::new();
xml.push_str("<w:tbl>");
xml.push_str("<w:tblPr>");
xml.push_str("<w:tblStyle w:val=\"TableGrid\"/>");
xml.push_str("<w:tblW w:w=\"5000\" w:type=\"pct\"/>");
xml.push_str("<w:tblLook w:val=\"04A0\" w:firstRow=\"1\" w:lastRow=\"0\" w:firstColumn=\"1\" w:lastColumn=\"0\" w:noHBand=\"0\" w:noVBand=\"1\"/>");
xml.push_str("</w:tblPr>");
xml.push_str("<w:tblGrid>");
for _ in 0..self.width {
xml.push_str("<w:gridCol w:w=\"2500\"/>");
}
xml.push_str("</w:tblGrid>");
for row in &self.rows {
xml.push_str("<w:tr>");
for cell in &row.cells {
xml.push_str("<w:tc>");
xml.push_str("<w:tcPr>");
if cell.is_header {
xml.push_str("<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"E7E6E6\"/>");
}
xml.push_str("</w:tcPr>");
xml.push_str("<w:p>");
xml.push_str("<w:pPr>");
if cell.is_header {
xml.push_str("<w:b/>");
}
xml.push_str("</w:pPr>");
xml.push_str("<w:r>");
xml.push_str("<w:rPr>");
xml.push_str(&format!(
"<w:rFonts w:ascii=\"{}\" w:hAnsi=\"{}\" w:eastAsia=\"{}\"/>",
self.default_font, self.default_font, self.default_font
));
let half_points = self.font_size.saturating_mul(2);
xml.push_str(&format!("<w:sz w:val=\"{}\"/>", half_points));
xml.push_str(&format!("<w:szCs w:val=\"{}\"/>", half_points));
xml.push_str(&format!("<w:color w:val=\"{}\"/>", self.font_color));
if cell.is_header || self.font_bold {
xml.push_str("<w:b/>");
}
xml.push_str("</w:rPr>");
xml.push_str("<w:t>");
let escaped_content = self.escape_xml(&cell.content);
xml.push_str(&escaped_content);
xml.push_str("</w:t>");
xml.push_str("</w:r>");
xml.push_str("</w:p>");
xml.push_str("</w:tc>");
}
xml.push_str("</w:tr>");
}
xml.push_str("</w:tbl>");
xml
}
fn escape_xml(&self, text: &str) -> String {
text.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
}
pub fn from_json(data: &Value, with_headers: bool) -> Result<Self> {
if !data.is_array() {
return Err(anyhow::anyhow!("JSON数据必须是数组"));
}
let array = data.as_array().unwrap(); if array.is_empty() {
return Ok(Self::new());
}
let mut table = Self::new();
if let Some(first_item) = array.first() {
if first_item.is_object() {
let obj = first_item.as_object().unwrap();
if with_headers {
let mut header_row = TableRow::new();
for key in obj.keys() {
header_row.add_cell(TableCell::header(key));
}
table.add_row(header_row);
}
for item in array {
if let Some(obj) = item.as_object() {
let mut row = TableRow::new();
if let Some(first_obj) = array.first().and_then(|v| v.as_object()) {
for key in first_obj.keys() {
if let Some(value) = obj.get(key) {
let cell_value = match value {
Value::Null => "".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
Value::Array(_) | Value::Object(_) => {
serde_json::to_string(value)
.unwrap_or_else(|_| "".to_string())
}
};
row.add_cell(TableCell::new(&cell_value));
} else {
row.add_cell(TableCell::new(""));
}
}
}
table.add_row(row);
}
}
} else if first_item.is_array() {
let start_index = if with_headers { 1 } else { 0 };
if with_headers && array.len() > 0 {
if let Some(header_array) = array[0].as_array() {
let mut header_row = TableRow::new();
for header in header_array {
let header_text = match header {
Value::String(s) => s.clone(),
_ => header.to_string(),
};
header_row.add_cell(TableCell::header(&header_text));
}
table.add_row(header_row);
}
}
for i in start_index..array.len() {
if let Some(row_array) = array[i].as_array() {
let mut row = TableRow::new();
for cell in row_array {
let cell_value = match cell {
Value::Null => "".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
Value::Array(_) | Value::Object(_) => {
serde_json::to_string(cell).unwrap_or_else(|_| "".to_string())
}
};
row.add_cell(TableCell::new(&cell_value));
}
table.add_row(row);
}
}
}
}
Ok(table)
}
}