Skip to main content

easydoc_writer/
table.rs

1//! 从类型化数据构建的表格。
2//!
3//! 对应 Java: `com.alibaba.excel.write.metadata.Sheet` / `EasyExcel.write().head(RowClass.class)`
4
5use easydoc_core::{CellData, DocxRow, TableStyle};
6
7/// 从类型化数据构建的表格。
8///
9/// 对应 Java: `com.alibaba.excel.write.metadata.Sheet` / `EasyExcel.write().head(RowClass.class)`
10pub struct Table {
11    headers: Vec<String>,
12    rows: Vec<Vec<CellData>>,
13    style: Option<TableStyle>,
14}
15
16impl Table {
17    /// 从实现 `DocxRow` 的类型切片创建表格。
18    #[must_use]
19    pub fn from_data<T: DocxRow>(data: &[T]) -> Self {
20        let headers = T::schema()
21            .iter()
22            .filter(|c| !c.ignored)
23            .map(|c| c.name.clone())
24            .collect();
25
26        let rows = data.iter().filter_map(|item| item.to_row().ok()).collect();
27
28        Self {
29            headers,
30            rows,
31            style: None,
32        }
33    }
34
35    /// 设置表格样式。
36    #[must_use]
37    pub fn header_style(mut self, style: TableStyle) -> Self {
38        self.style = Some(style);
39        self
40    }
41
42    /// 启用斑马条纹。
43    #[must_use]
44    pub fn banded_rows(mut self, enabled: bool) -> Self {
45        self.style.get_or_insert_default().banded_rows = enabled;
46        self
47    }
48
49    /// 启用自动列宽。
50    #[must_use]
51    pub fn auto_width(mut self) -> Self {
52        self.style.get_or_insert_default().auto_width = true;
53        self
54    }
55
56    pub(crate) fn headers(&self) -> &[String] {
57        &self.headers
58    }
59
60    pub(crate) fn rows(&self) -> &[Vec<CellData>] {
61        &self.rows
62    }
63
64    #[allow(dead_code)]
65    pub(crate) fn table_style(&self) -> Option<&TableStyle> {
66        self.style.as_ref()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    /// 测试辅助结构体,仅用于表格测试。
75    #[derive(Debug, Clone)]
76    struct TestUser {
77        name: String,
78        age: u32,
79        email: String,
80    }
81
82    impl DocxRow for TestUser {
83        fn schema() -> &'static [easydoc_core::metadata::TableColumn] {
84            static SCHEMA: std::sync::LazyLock<Vec<easydoc_core::metadata::TableColumn>> =
85                std::sync::LazyLock::new(|| {
86                    vec![
87                        easydoc_core::metadata::TableColumn::new("Name", "name", 0),
88                        easydoc_core::metadata::TableColumn::new("Age", "age", 1),
89                        easydoc_core::metadata::TableColumn::new("Email", "email", 2),
90                    ]
91                });
92            &SCHEMA
93        }
94
95        fn from_row(_row: &easydoc_core::RowData) -> easydoc_core::Result<Self> {
96            unimplemented!()
97        }
98        fn from_row_with_converters(
99            _row: &easydoc_core::RowData,
100            _registry: &easydoc_core::ConverterRegistry,
101        ) -> easydoc_core::Result<Self> {
102            unimplemented!()
103        }
104        fn to_row(&self) -> easydoc_core::Result<Vec<easydoc_core::CellData>> {
105            Ok(vec![
106                easydoc_core::CellData::new(self.name.clone()),
107                easydoc_core::CellData::new(i64::from(self.age)),
108                easydoc_core::CellData::new(self.email.clone()),
109            ])
110        }
111        fn to_row_with_converters(
112            &self,
113            _registry: &easydoc_core::ConverterRegistry,
114        ) -> easydoc_core::Result<Vec<easydoc_core::CellData>> {
115            self.to_row()
116        }
117    }
118
119    #[test]
120    fn table_from_data_empty() {
121        let users: Vec<TestUser> = vec![];
122        let t = Table::from_data(&users);
123        assert!(t.rows().is_empty());
124        assert!(!t.headers().is_empty());
125    }
126
127    #[test]
128    fn table_from_data_with_rows() {
129        let users = vec![
130            TestUser {
131                name: "Alice".into(),
132                age: 30,
133                email: "a@b.com".into(),
134            },
135            TestUser {
136                name: "Bob".into(),
137                age: 25,
138                email: "b@c.com".into(),
139            },
140        ];
141        let t = Table::from_data(&users);
142        assert_eq!(t.rows().len(), 2);
143        assert_eq!(t.headers().len(), 3);
144    }
145
146    #[test]
147    fn table_builder_methods() {
148        let t = Table::from_data::<TestUser>(&[])
149            .banded_rows(true)
150            .auto_width();
151        assert!(t.style.is_some());
152        assert!(t.style.as_ref().unwrap().banded_rows);
153        assert!(t.style.as_ref().unwrap().auto_width);
154    }
155}