diff --git a/README.md b/README.md index b448f25..b45ed34 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,59 @@ WordZero 是一个使用 Golang 实现的 Word 文档操作库,提供基础的 - ⚡ **高性能**: 零依赖的纯Go实现,内存占用低 - 🔧 **易于使用**: 简洁的API设计,链式调用支持 +## 安装 + +```bash +go get github.com/ZeroHawkeye/wordZero +``` + +## 项目结构 + +``` +wordZero/ +├── pkg/ # 公共包 +│ ├── document/ # 文档核心操作 +│ │ ├── document.go # 主要文档操作API +│ │ ├── table.go # 表格操作功能 +│ │ ├── errors.go # 错误定义和处理 +│ │ ├── logger.go # 日志系统 +│ │ ├── doc.go # 包文档 +│ │ ├── document_test.go # 文档操作单元测试 +│ │ └── table_test.go # 表格功能单元测试 +│ └── style/ # 样式管理系统 +│ ├── style.go # 样式核心定义 +│ ├── api.go # 快速API接口 +│ ├── predefined.go # 预定义样式常量 +│ ├── api_test.go # API测试 +│ ├── style_test.go # 样式系统测试 +│ └── README.md # 样式系统详细文档 +├── examples/ # 使用示例 +│ ├── basic/ # 基础功能示例 +│ │ └── basic_example.go +│ ├── formatting/ # 格式化示例 +│ │ └── text_formatting_example.go +│ ├── style_demo/ # 样式系统演示 +│ │ └── style_demo.go +│ ├── table/ # 表格功能示例 +│ │ └── table_example.go +│ ├── table_layout/ # 表格布局和尺寸示例 +│ │ └── main.go +│ ├── table_style/ # 表格样式和外观示例 +│ │ └── main.go +│ ├── cell_advanced/ # 单元格高级功能示例 +│ │ └── main.go +│ ├── basic_usage.go # 基础使用示例 +│ └── output/ # 示例输出文件目录 +├── test/ # 集成测试文件 +│ ├── document_test.go # 文档操作集成测试 +│ ├── text_formatting_test.go # 文本格式化集成测试 +│ └── table_style_test.go # 表格样式功能集成测试 +├── .gitignore # Git忽略文件配置 +├── go.mod # Go模块定义 +├── LICENSE # MIT许可证 +└── README.md # 项目说明文档 +``` + ## 功能特性 ### ✅ 已实现功能 @@ -53,8 +106,6 @@ WordZero 是一个使用 Golang 实现的 Word 文档操作库,提供基础的 > **样式数量说明:** 系统内置18个预定义样式(15个段落样式 + 3个字符样式)。演示程序中显示的21个样式是因为动态创建了3个自定义样式进行功能展示。 -### 🚧 规划中功能 - #### 表格功能 ##### 表格基础操作 @@ -85,52 +136,63 @@ WordZero 是一个使用 Golang 实现的 Word 文档操作库,提供基础的 - [x] 单元格文字方向和旋转 - [x] 单元格内边距设置 -##### 表格样式和外观 -- [ ] 表格整体样式 - - [ ] 预定义表格样式模板 - - [ ] 自定义表格样式创建 - - [ ] 表格主题色彩应用 - - [ ] 表格样式继承和覆盖 -- [ ] 表格边框设置 - - [ ] 外边框样式(线型、颜色、粗细) - - [ ] 内边框样式(网格线设置) - - [ ] 单元格边框独立设置 - - [ ] 边框部分应用(顶部、底部、左右) - - [ ] 无边框表格支持 -- [ ] 表格背景和填充 - - [ ] 表格背景色设置 - - [ ] 单元格背景色设置 - - [ ] 奇偶行颜色交替 - - [ ] 渐变背景支持(基础渐变) - - [ ] 图案填充支持 - ##### 表格布局和尺寸 - [x] 表格尺寸控制 - [x] 表格总宽度设置(固定宽度、相对宽度、自动宽度) - [x] 列宽设置(固定宽度、相对宽度、自动调整) - - [ ] 行高设置(固定高度、最小高度、自动调整) + - [x] 行高设置(固定高度、最小高度、自动调整) - [x] 单元格尺寸精确控制 -- [ ] 表格对齐和定位 +- [x] 表格对齐和定位 - [x] 表格页面对齐(左对齐、居中、右对齐) - - [ ] 表格文字环绕设置 - - [ ] 表格相对定位 -- [ ] 表格分页控制 - - [ ] 表格跨页处理 - - [ ] 标题行重复显示 - - [ ] 表格分页符控制 - - [ ] 避免分页的行设置 + - [x] 表格文字环绕设置 + - [x] 表格相对定位 +- [x] 表格分页控制 + - [x] 表格跨页处理 + - [x] 标题行重复显示 + - [x] 表格分页符控制 + - [x] 避免分页的行设置 ##### 表格数据处理 - [x] 数据导入导出 - [x] 二维数组数据绑定 - [x] 表格数据提取为数组 - [x] 批量数据填充 + +##### 表格访问和查询 +- [x] 表格查找和定位 + - [x] 按索引获取表格 + - [x] 表格位置信息获取 +- [x] 单元格访问接口 + - [x] 按行列索引访问 + +##### 表格样式和外观 +- [x] 表格整体样式 + - [x] 预定义表格样式模板 + - [x] 自定义表格样式创建 + - [x] 表格主题色彩应用 + - [x] 表格样式继承和覆盖 +- [x] 表格边框设置 + - [x] 外边框样式(线型、颜色、粗细) + - [x] 内边框样式(网格线设置) + - [x] 单元格边框独立设置 + - [x] 边框部分应用(顶部、底部、左右) + - [x] 无边框表格支持 +- [x] 表格背景和填充 + - [x] 表格背景色设置 + - [x] 单元格背景色设置 + - [x] 奇偶行颜色交替 + - [x] 渐变背景支持(基础渐变) + - [x] 图案填充支持 + +### 🚧 规划中功能 + +#### 表格功能完善 + +##### 高级表格功能 - [ ] 表格排序功能(Word内置排序) - [ ] 单列排序(升序、降序) - [ ] 多列排序 - [ ] 保持标题行不参与排序 - -##### 高级表格功能 - [ ] 表格标题和说明 - [ ] 表格标题设置(表格上方、下方) - [ ] 表格标题编号自动生成 @@ -142,14 +204,8 @@ WordZero 是一个使用 Golang 实现的 Word 文档操作库,提供基础的 - [ ] 常用表格模板库 - [ ] 自定义模板保存 - [ ] 模板快速应用 - -##### 表格访问和查询 -- [x] 表格查找和定位 - - [x] 按索引获取表格 +- [ ] 表格访问增强 - [ ] 按标题查找表格 - - [x] 表格位置信息获取 -- [x] 单元格访问接口 - - [x] 按行列索引访问 - [ ] 按范围批量访问 - [ ] 单元格遍历迭代器 @@ -174,463 +230,52 @@ WordZero 是一个使用 Golang 实现的 Word 文档操作库,提供基础的 - [ ] 列表和编号 - [ ] 脚注和尾注 -## 安装 - -```bash -go get github.com/ZeroHawkeye/wordZero -``` - -## 快速开始 - -### 基础文档创建 - -```go -package main - -import ( - "log" - "github.com/ZeroHawkeye/wordZero/pkg/document" -) - -func main() { - // 创建新文档 - doc := document.New() - - // 添加段落 - doc.AddParagraph("Hello, World!") - doc.AddParagraph("这是使用 WordZero 创建的文档。") - - // 保存文档 - err := doc.Save("output.docx") - if err != nil { - log.Fatal(err) - } -} -``` - -### 使用标题样式(支持导航窗格) - -```go -doc := document.New() - -// 添加文档标题 -doc.AddParagraph("WordZero 使用指南").SetAlignment(document.AlignCenter) - -// 使用标题样式 - 这些标题将出现在Word导航窗格中 -doc.AddHeadingParagraph("第一章:概述", 1) // Heading1 -doc.AddHeadingParagraph("1.1 项目介绍", 2) // Heading2 -doc.AddHeadingParagraph("1.1.1 核心特性", 3) // Heading3 - -// 添加正文内容 -doc.AddParagraph("WordZero是一个功能强大的Word文档操作库...") - -doc.AddHeadingParagraph("第二章:安装和配置", 1) // Heading1 -doc.AddHeadingParagraph("2.1 环境要求", 2) // Heading2 - -doc.Save("guide.docx") -``` - -### 高级文本格式化 - -```go -doc := document.New() - -// 创建格式化标题 -titleFormat := &document.TextFormat{ - Bold: true, - FontSize: 18, - FontColor: "FF0000", // 红色 - FontName: "微软雅黑", -} -title := doc.AddFormattedParagraph("格式化标题", titleFormat) -title.SetAlignment(document.AlignCenter) - -// 设置段落间距 -spacingConfig := &document.SpacingConfig{ - LineSpacing: 1.5, // 1.5倍行距 - BeforePara: 12, // 段前12磅 - AfterPara: 6, // 段后6磅 - FirstLineIndent: 24, // 首行缩进24磅 -} -para := doc.AddParagraph("这个段落有特定的间距设置") -para.SetSpacing(spacingConfig) -para.SetAlignment(document.AlignJustify) // 两端对齐 - -// 混合格式段落 -mixed := doc.AddParagraph("这段文字包含") -mixed.AddFormattedText("粗体蓝色", &document.TextFormat{ - Bold: true, FontColor: "0000FF"}) -mixed.AddFormattedText(",普通文本,", nil) -mixed.AddFormattedText("斜体绿色", &document.TextFormat{ - Italic: true, FontColor: "00FF00"}) - -doc.Save("formatted.docx") -``` - -### 样式系统使用 - -```go -import "github.com/ZeroHawkeye/wordZero/pkg/style" - -doc := document.New() -styleManager := doc.GetStyleManager() -quickAPI := style.NewQuickStyleAPI(styleManager) - -// 查看所有可用样式 -allStyles := quickAPI.GetAllStylesInfo() -for _, styleInfo := range allStyles { - fmt.Printf("样式: %s (%s) - %s\n", - styleInfo.Name, styleInfo.ID, styleInfo.Description) -} - -// 使用预定义样式创建段落 -para := doc.AddParagraph("这是引用文本") -para.SetStyle("Quote") // 应用引用样式 - -// 创建自定义样式 -config := style.QuickStyleConfig{ - ID: "MyCustomStyle", - Name: "我的自定义样式", - Type: style.StyleTypeParagraph, - BasedOn: "Normal", - ParagraphConfig: &style.QuickParagraphConfig{ - Alignment: "center", - LineSpacing: 2.0, - SpaceBefore: 15, - }, - RunConfig: &style.QuickRunConfig{ - FontName: "华文宋体", - FontSize: 14, - FontColor: "2F5496", - Bold: true, - }, -} - -customStyle, err := quickAPI.CreateQuickStyle(config) -if err == nil { - // 应用自定义样式 - customPara := doc.AddParagraph("使用自定义样式的段落") - customPara.SetStyle("MyCustomStyle") -} - -doc.Save("styled.docx") -``` - -### 高级表格功能 - -```go -package main - -import ( - "log" - "github.com/ZeroHawkeye/wordZero/pkg/document" -) - -func main() { - doc := document.New() - - // 1. 创建基础表格 - config := &document.TableConfig{ - Rows: 4, - Cols: 4, - Width: 8000, - Data: [][]string{ - {"学号", "姓名", "语文", "数学"}, - {"001", "张三", "85", "92"}, - {"002", "李四", "78", "88"}, - {"003", "王五", "90", "85"}, - }, - } - - table := doc.AddTable(config) - - // 2. 设置表头格式 - headerFormat := &document.CellFormat{ - TextFormat: &document.TextFormat{ - Bold: true, - FontSize: 14, - FontColor: "FFFFFF", // 白色文字 - FontName: "微软雅黑", - }, - HorizontalAlign: document.CellAlignCenter, - VerticalAlign: document.CellVAlignCenter, - } - - // 为第一行设置表头格式 - for col := 0; col < 4; col++ { - table.SetCellFormat(0, col, headerFormat) - } - - // 3. 单元格富文本 - table.SetCellFormattedText(1, 1, "张三", &document.TextFormat{ - Bold: true, - FontColor: "FF0000", - }) - - // 在同一单元格添加不同格式的文本 - table.AddCellFormattedText(1, 1, " (优秀)", &document.TextFormat{ - Italic: true, - FontColor: "00FF00", - FontSize: 10, - }) - - // 4. 单元格合并 - // 水平合并 - table.MergeCellsHorizontal(0, 2, 3) // 合并表头的"语文"和"数学"列 - table.SetCellText(0, 2, "成绩") - - // 垂直合并 - table.MergeCellsVertical(1, 3, 0) // 合并学号列 - table.SetCellText(1, 0, "2024级") - - // 区域合并(2x2区域) - mergeTable := doc.AddTable(&document.TableConfig{Rows: 4, Cols: 4, Width: 6000}) - mergeTable.MergeCellsRange(1, 2, 1, 2) // 合并中间2x2区域 - mergeTable.SetCellText(1, 1, "合并区域") - - // 5. 检查和取消合并 - isMerged, _ := table.IsCellMerged(0, 2) - if isMerged { - // 获取合并信息 - mergeInfo, _ := table.GetMergedCellInfo(0, 2) - log.Printf("合并信息: %+v", mergeInfo) - - // 可以选择取消合并 - // table.UnmergeCells(0, 2) - } - - // 6. 内容和格式操作 - // 清空内容但保留格式 - table.ClearCellContent(1, 2) - table.SetCellText(1, 2, "90") - - // 清空格式但保留内容 - table.ClearCellFormat(1, 3) - - // 7. 设置单元格内边距 - table.SetCellPadding(0, 0, 10) // 10磅内边距 - - // 8. 设置单元格文字方向 - // 设置垂直文字(从上到下) - table.SetCellTextDirection(1, 0, document.TextDirectionTB) - - // 通过CellFormat设置完整格式,包括文字方向 - verticalFormat := &document.CellFormat{ - TextFormat: &document.TextFormat{ - Bold: true, - FontSize: 14, - }, - HorizontalAlign: document.CellAlignCenter, - VerticalAlign: document.CellVAlignCenter, - TextDirection: document.TextDirectionTB, // 从上到下 - } - table.SetCellFormat(1, 1, verticalFormat) - table.SetCellText(1, 1, "竖排文字") - - doc.Save("advanced_table.docx") -} -``` - -## 项目结构 - -``` -wordZero/ -├── pkg/ # 公共包 -│ ├── document/ # 文档核心操作 -│ │ ├── document.go # 主要文档操作API -│ │ ├── errors.go # 错误定义和处理 -│ │ ├── logger.go # 日志系统 -│ │ ├── doc.go # 包文档 -│ │ └── document_test.go # 单元测试 -│ └── style/ # 样式管理系统 -│ ├── style.go # 样式核心定义 -│ ├── api.go # 快速API接口 -│ ├── predefined.go # 预定义样式常量 -│ ├── api_test.go # API测试 -│ ├── style_test.go # 样式系统测试 -│ └── README.md # 样式系统详细文档 -├── examples/ # 使用示例 -│ ├── basic/ # 基础功能示例 -│ │ └── basic_example.go -│ ├── formatting/ # 格式化示例 -│ ├── style_demo/ # 样式系统演示 -│ │ └── style_demo.go -│ └── output/ # 示例输出文件 -├── test/ # 测试文件 -├── go.mod # Go模块定义 -├── LICENSE # MIT许可证 -└── README.md # 项目说明文档 -``` - ## 使用示例 -### 基础功能演示 +查看 `examples/` 目录下的示例代码: -运行基础示例: +- `examples/basic/` - 基础功能演示 +- `examples/style_demo/` - 样式系统演示 +- `examples/table/` - 表格功能演示 +- `examples/table_layout/` - 表格布局和尺寸演示 +- `examples/formatting/` - 格式化演示 + +运行示例: ```bash -go run ./examples/basic/ -``` - -这个示例展示了: -- 文档和标题创建 -- 各种预定义样式的使用 -- 文本格式化和混合格式 -- 代码块和引用样式 -- 列表段落的创建 - -### 完整样式系统演示 - -运行完整样式演示: -```bash -go run ./examples/style_demo/ -``` - -这个示例展示了: -- 所有18种预定义样式 -- 样式继承机制演示 -- 自定义样式创建 -- 样式查询和管理功能 -- XML转换演示 - -### 读取现有文档 - -```go -doc, err := document.Open("existing.docx") -if err != nil { - log.Fatal(err) -} - -// 读取段落内容 -fmt.Printf("文档包含 %d 个段落\n", len(doc.Body.Paragraphs)) -for i, para := range doc.Body.Paragraphs { - fmt.Printf("段落 %d: ", i+1) - for _, run := range para.Runs { - fmt.Print(run.Text.Content) - } - fmt.Println() -} -``` - -### 命令行使用 - -运行演示程序: -```bash -# 运行完整演示 -go run main.go - # 运行基础功能演示 go run ./examples/basic/ # 运行样式演示 go run ./examples/style_demo/ +# 运行表格演示 +go run ./examples/table/ + +# 运行表格布局和尺寸演示 +go run ./examples/table_layout/ + # 运行格式化演示 go run ./examples/formatting/ ``` -## 测试 +## 开发指南 -### 运行测试 +### 项目开发规则 -```bash -# 运行所有测试 -go test ./... +1. **项目结构**: 符合golang依赖库的项目结构,完善的测试流程 +2. **模块化设计**: 规范的项目结构,避免文件循环依赖和过渡耦合 +3. **测试规范**: 测试用例尽量放在test目录下 +4. **文档维护**: 每次完成一个功能点需更新README文件的待办列表 -# 运行特定包测试 -go test ./pkg/document/ -go test ./pkg/style/ +### 贡献指南 -# 运行测试并显示覆盖率 -go test -cover ./... +欢迎提交 Issue 和 Pull Request!在提交代码前请确保: -# 生成详细的测试报告 -go test -v -coverprofile=coverage.out ./... -go tool cover -html=coverage.out -``` - -### 测试覆盖 - -- **文档操作**: 基础CRUD操作、文本格式化、段落属性 -- **样式系统**: 预定义样式、自定义样式、样式继承 -- **文件处理**: ZIP压缩/解压、XML序列化/反序列化 -- **错误处理**: 各种异常情况和边界条件 - -## API 文档 - -详细的API文档请参考: -- [文档操作API](pkg/document/) - 核心文档操作功能 -- [样式系统API](pkg/style/) - 完整的样式管理系统 - -## 开发进度 - -### 当前版本: v0.3.0 - -#### v0.3.0 新增功能 -- ✅ 完整的标题样式系统(Heading1-9) -- ✅ Word导航窗格支持 -- ✅ 18种预定义样式(系统内置样式) -- ✅ 自定义样式创建和管理 -- ✅ 样式继承机制 -- ✅ 快速样式API - -#### v0.2.0 功能 -- ✅ 基础文档创建和读取 -- ✅ 文本格式化支持 -- ✅ 段落属性设置 -- ✅ 混合格式文本 - -#### v0.1.0 功能 -- ✅ 项目初始化 -- ✅ OOXML基础架构 -- ✅ ZIP文件处理 - -### 下一版本计划: v0.4.0 -- 🚧 表格功能 -- 🚧 图片插入 -- 🚧 列表和编号 -- 🚧 页面设置 - -## 贡献指南 - -欢迎贡献代码!请确保: - -1. 所有新功能都有相应的单元测试 -2. 代码符合Go语言规范 -3. 提交前运行完整测试套件 -4. 更新相关文档 +1. 代码符合 Go 代码规范 +2. 添加必要的测试用例 +3. 更新相关文档 +4. 确保所有测试通过 ## 许可证 -本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件 - -## 更新日志 - -### 2025-05-29 单元格文字方向功能实现 -- ✅ 实现单元格文字方向设置功能,支持6种方向: - - `TextDirectionLR`:从左到右(默认) - - `TextDirectionTB`:从上到下 - - `TextDirectionBT`:从下到上 - - `TextDirectionRL`:从右到左 - - `TextDirectionTBV`:从上到下,垂直显示 - - `TextDirectionBTV`:从下到上,垂直显示 -- ✅ 添加 `SetCellTextDirection()` 和 `GetCellTextDirection()` 方法 -- ✅ 扩展 `CellFormat` 结构支持文字方向属性 -- ✅ 添加完整的测试用例和演示程序 -- ✅ 更新README文档和使用示例 - -### 2025-05-29 测试修复 -- ✅ 修复 `TestComplexDocument` 测试:调整期望段落数量从7改为6,与实际创建的段落数量一致 -- ✅ 修复 `TestErrorHandling` 测试:改进无效路径测试策略,确保在不同操作系统下都能正确测试错误处理 -- ✅ 验证所有测试用例均通过,确保代码质量和功能稳定性 -- ✅ 问题根因:测试用例期望值与实际实现不符,已修正测试逻辑 - -### 测试状态总结 -- **总测试数量**: 20+ 个测试用例 -- **覆盖模块**: document操作、style管理、格式化功能、错误处理 -- **通过率**: 100% -- **测试结论**: 代码实现正确,测试用例已修复 - -## 致谢 - -- Office Open XML 规范 -- Go语言社区的优秀库和工具 \ No newline at end of file +本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。 \ No newline at end of file diff --git a/examples/basic/basic_example.go b/examples/basic/basic_example.go index 86a80ea..06a0681 100644 --- a/examples/basic/basic_example.go +++ b/examples/basic/basic_example.go @@ -111,7 +111,7 @@ doc.Save("example.docx")` stylesInfo.SetStyle(style.StyleNormal) // 确保输出目录存在 - outputFile := "../output/basic_example.docx" + outputFile := "examples/output/basic_example.docx" outputDir := filepath.Dir(outputFile) fmt.Printf("📁 检查输出目录: %s\n", outputDir) diff --git a/examples/basic_usage.go b/examples/basic_usage.go index 46b8c13..353c6ce 100644 --- a/examples/basic_usage.go +++ b/examples/basic_usage.go @@ -26,7 +26,7 @@ func main() { doc.AddParagraph("• 打开现有文档") // 保存文档 - outputFile := "output/example_document.docx" + outputFile := "examples/output/example_document.docx" err := doc.Save(outputFile) if err != nil { log.Fatalf("保存文档失败: %v", err) @@ -41,11 +41,11 @@ func main() { log.Fatalf("打开文档失败: %v", err) } - fmt.Printf("文档包含 %d 个段落\n", len(openedDoc.Body.Paragraphs)) + fmt.Printf("文档包含 %d 个段落\n", len(openedDoc.Body.GetParagraphs())) // 打印所有段落内容 fmt.Println("\n文档内容:") - for i, para := range openedDoc.Body.Paragraphs { + for i, para := range openedDoc.Body.GetParagraphs() { if len(para.Runs) > 0 { fmt.Printf("段落 %d: %s\n", i+1, para.Runs[0].Text.Content) } diff --git a/examples/formatting/text_formatting_example.go b/examples/formatting/text_formatting_example.go index 7d53ef5..dc5e057 100644 --- a/examples/formatting/text_formatting_example.go +++ b/examples/formatting/text_formatting_example.go @@ -66,7 +66,7 @@ func main() { p5.SetAlignment(document.AlignRight) // 保存文档 - err := doc.Save("../output/formatted_document.docx") + err := doc.Save("examples/output/formatted_document.docx") if err != nil { log.Fatalf("保存文档失败: %v", err) } diff --git a/examples/style_demo/style_demo.go b/examples/style_demo/style_demo.go index 993c207..24343f8 100644 --- a/examples/style_demo/style_demo.go +++ b/examples/style_demo/style_demo.go @@ -36,7 +36,7 @@ func main() { demonstrateStyleManagement(quickAPI) // 保存文档 - outputFile := "../output/styled_document_demo.docx" + outputFile := "examples/output/styled_document_demo.docx" err := doc.Save(outputFile) if err != nil { log.Fatalf("保存文档失败: %v", err) diff --git a/examples/table/table_example.go b/examples/table/table_example.go index d20b3de..8c30f25 100644 --- a/examples/table/table_example.go +++ b/examples/table/table_example.go @@ -206,7 +206,7 @@ func demonstrateTableCopyAndClear(doc *document.Document) { copiedTable.SetCellText(1, 1, "复制4") // 将复制的表格添加到文档 - doc.Body.Tables = append(doc.Body.Tables, *copiedTable) + doc.Body.AddElement(copiedTable) fmt.Println(" 复制的表格已添加到文档") } diff --git a/examples/table_layout/main.go b/examples/table_layout/main.go new file mode 100644 index 0000000..b9b8c00 --- /dev/null +++ b/examples/table_layout/main.go @@ -0,0 +1,283 @@ +package main + +import ( + "fmt" + "log" + "path/filepath" + + "github.com/ZeroHawkeye/wordZero/pkg/document" +) + +func main() { + fmt.Println("WordZero 表格布局和尺寸功能演示") + fmt.Println("==============================") + + // 创建新文档 + doc := document.New() + + // 添加文档标题 + title := doc.AddParagraph("表格布局和尺寸功能演示") + title.SetAlignment(document.AlignCenter) + titleFormat := &document.TextFormat{ + Bold: true, + FontSize: 18, + FontColor: "2F5496", + FontName: "微软雅黑", + } + title.AddFormattedText("", titleFormat) + + // 1. 行高设置演示 + fmt.Println("1. 创建行高设置演示表格...") + doc.AddParagraph("1. 行高设置演示").SetStyle("Heading1") + + heightTable := doc.AddTable(&document.TableConfig{ + Rows: 4, + Cols: 3, + Width: 8000, + Data: [][]string{ + {"行类型", "高度设置", "说明"}, + {"自动行高", "默认", "根据内容自动调整高度"}, + {"固定行高", "40磅", "精确的固定高度"}, + {"最小行高", "30磅", "至少30磅,内容多时可以更高"}, + }, + }) + + // 设置表头格式 + headerFormat := &document.CellFormat{ + TextFormat: &document.TextFormat{ + Bold: true, + FontSize: 12, + FontColor: "FFFFFF", + FontName: "微软雅黑", + }, + HorizontalAlign: document.CellAlignCenter, + VerticalAlign: document.CellVAlignCenter, + } + + for col := 0; col < 3; col++ { + heightTable.SetCellFormat(0, col, headerFormat) + } + + // 应用不同的行高设置 + // 第2行:固定高度40磅 + heightTable.SetRowHeight(2, &document.RowHeightConfig{ + Height: 40, + Rule: document.RowHeightExact, + }) + + // 第3行:最小高度30磅 + heightTable.SetRowHeight(3, &document.RowHeightConfig{ + Height: 30, + Rule: document.RowHeightMinimum, + }) + + fmt.Println(" - 设置了不同的行高规则") + + // 2. 表格对齐演示 + fmt.Println("2. 创建表格对齐演示...") + doc.AddParagraph("2. 表格对齐演示").SetStyle("Heading1") + + // 左对齐表格 + doc.AddParagraph("2.1 左对齐表格").SetStyle("Heading2") + leftTable := doc.AddTable(&document.TableConfig{ + Rows: 2, + Cols: 2, + Width: 4000, + Data: [][]string{ + {"左对齐", "表格"}, + {"Left", "Aligned"}, + }, + }) + leftTable.SetTableAlignment(document.TableAlignLeft) + + // 居中对齐表格 + doc.AddParagraph("2.2 居中对齐表格").SetStyle("Heading2") + centerTable := doc.AddTable(&document.TableConfig{ + Rows: 2, + Cols: 2, + Width: 4000, + Data: [][]string{ + {"居中对齐", "表格"}, + {"Center", "Aligned"}, + }, + }) + centerTable.SetTableAlignment(document.TableAlignCenter) + + // 右对齐表格 + doc.AddParagraph("2.3 右对齐表格").SetStyle("Heading2") + rightTable := doc.AddTable(&document.TableConfig{ + Rows: 2, + Cols: 2, + Width: 4000, + Data: [][]string{ + {"右对齐", "表格"}, + {"Right", "Aligned"}, + }, + }) + rightTable.SetTableAlignment(document.TableAlignRight) + + fmt.Println(" - 创建了左对齐、居中、右对齐三种表格") + + // 3. 分页控制演示 + fmt.Println("3. 创建分页控制演示表格...") + doc.AddParagraph("3. 分页控制演示").SetStyle("Heading1") + + pageBreakTable := doc.AddTable(&document.TableConfig{ + Rows: 6, + Cols: 4, + Width: 9000, + Data: [][]string{ + {"序号", "姓名", "部门", "职位"}, + {"001", "张三", "技术部", "工程师"}, + {"002", "李四", "产品部", "产品经理"}, + {"003", "王五", "设计部", "UI设计师"}, + {"004", "赵六", "市场部", "市场专员"}, + {"005", "钱七", "人事部", "HR专员"}, + }, + }) + + // 设置表头格式 + for col := 0; col < 4; col++ { + pageBreakTable.SetCellFormat(0, col, &document.CellFormat{ + TextFormat: &document.TextFormat{ + Bold: true, + FontSize: 12, + FontColor: "FFFFFF", + }, + HorizontalAlign: document.CellAlignCenter, + VerticalAlign: document.CellVAlignCenter, + }) + } + + // 设置第一行为重复的标题行 + pageBreakTable.SetRowAsHeader(0, true) + fmt.Println(" - 设置第一行为重复标题行") + + // 设置某些行禁止跨页分割 + pageBreakTable.SetRowKeepTogether(1, true) + pageBreakTable.SetRowKeepTogether(2, true) + fmt.Println(" - 设置前两个数据行禁止跨页分割") + + // 设置表格分页配置 + pageBreakTable.SetTablePageBreak(&document.TablePageBreakConfig{ + KeepWithNext: false, + KeepLines: true, + PageBreakBefore: false, + WidowControl: true, + }) + + // 4. 复杂布局演示 + fmt.Println("4. 创建复杂布局演示表格...") + doc.AddParagraph("4. 复杂布局演示").SetStyle("Heading1") + + complexTable := doc.AddTable(&document.TableConfig{ + Rows: 5, + Cols: 4, + Width: 9000, + ColWidths: []int{1500, 3000, 2000, 2500}, + Data: [][]string{ + {"项目", "描述", "状态", "负责人"}, + {"WordZero核心", "Word文档操作库核心功能", "进行中", "开发团队"}, + {"表格功能", "完整的表格操作和格式化", "已完成", "张工程师"}, + {"样式系统", "18种预定义样式和自定义样式", "已完成", "李设计师"}, + {"测试套件", "完整的单元测试和集成测试", "进行中", "QA团队"}, + }, + }) + + // 设置表头为重复标题行 + complexTable.SetHeaderRows(0, 0) + + // 设置不同的行高 + complexTable.SetRowHeight(0, &document.RowHeightConfig{ + Height: 35, + Rule: document.RowHeightExact, + }) // 表头固定高度 + + // 批量设置数据行为最小高度 + complexTable.SetRowHeightRange(1, 4, &document.RowHeightConfig{ + Height: 25, + Rule: document.RowHeightMinimum, + }) + + // 设置表头格式 + for col := 0; col < 4; col++ { + complexTable.SetCellFormat(0, col, &document.CellFormat{ + TextFormat: &document.TextFormat{ + Bold: true, + FontSize: 14, + FontColor: "FFFFFF", + FontName: "微软雅黑", + }, + HorizontalAlign: document.CellAlignCenter, + VerticalAlign: document.CellVAlignCenter, + }) + } + + // 设置状态列的特殊格式 + for row := 1; row <= 4; row++ { + status, _ := complexTable.GetCellText(row, 2) + var color string + switch status { + case "已完成": + color = "00AA00" // 绿色 + case "进行中": + color = "FF8800" // 橙色 + default: + color = "666666" // 灰色 + } + + complexTable.SetCellFormat(row, 2, &document.CellFormat{ + TextFormat: &document.TextFormat{ + Bold: true, + FontColor: color, + FontSize: 11, + }, + HorizontalAlign: document.CellAlignCenter, + VerticalAlign: document.CellVAlignCenter, + }) + } + + // 设置表格居中对齐 + complexTable.SetTableAlignment(document.TableAlignCenter) + + fmt.Println(" - 设置了自定义列宽") + fmt.Println(" - 应用了不同的行高规则") + fmt.Println(" - 添加了状态标识颜色") + + // 5. 输出统计信息 + fmt.Println("5. 生成统计信息...") + doc.AddParagraph("5. 表格统计信息").SetStyle("Heading1") + + // 获取分页信息 + breakInfo := pageBreakTable.GetTableBreakInfo() + infoText := fmt.Sprintf("分页控制表格统计:总行数 %d,标题行数 %d,禁止分割行数 %d", + breakInfo["total_rows"], breakInfo["header_rows"], breakInfo["keep_together_rows"]) + doc.AddParagraph(infoText) + + // 获取表格布局信息 + layout := complexTable.GetTableLayout() + layoutText := fmt.Sprintf("复杂表格布局:对齐方式 %s,环绕类型 %s,定位类型 %s", + layout.Alignment, layout.TextWrap, layout.Position) + doc.AddParagraph(layoutText) + + // 验证行高设置 + heightConfig, _ := complexTable.GetRowHeight(0) + heightText := fmt.Sprintf("表头行高设置:%d磅,规则 %s", heightConfig.Height, heightConfig.Rule) + doc.AddParagraph(heightText) + + // 6. 保存文档 + outputPath := filepath.Join("examples", "output", "table_layout_demo.docx") + err := doc.Save(outputPath) + if err != nil { + log.Fatalf("保存文档失败: %v", err) + } + + fmt.Printf("6. 文档已保存到: %s\n", outputPath) + fmt.Println("\n演示完成!") + fmt.Println("\n新实现的功能包括:") + fmt.Println("✅ 行高设置(固定高度、最小高度、自动调整)") + fmt.Println("✅ 表格对齐方式(左对齐、居中、右对齐)") + fmt.Println("✅ 分页控制(标题行重复、禁止跨页分割)") + fmt.Println("✅ 表格布局配置和查询") + fmt.Println("✅ 批量行高设置和统计信息获取") +} diff --git a/examples/table_style/main.go b/examples/table_style/main.go new file mode 100644 index 0000000..80da39e --- /dev/null +++ b/examples/table_style/main.go @@ -0,0 +1,677 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/ZeroHawkeye/wordZero/pkg/document" +) + +func main() { + // 创建输出目录 + if err := os.MkdirAll("examples/output", 0755); err != nil { + log.Fatalf("创建输出目录失败: %v", err) + } + + // 创建文档 + doc := document.New() + if doc == nil { + log.Fatal("创建文档失败") + } + + // 添加标题 + doc.AddParagraph("WordZero - 表格样式和外观功能演示") + doc.AddParagraph("") + + // 演示1:基础表格边框 + demonstrateBorders(doc) + + // 演示2:单元格边框 + demonstrateCellBorders(doc) + + // 演示3:表格背景 + demonstrateTableShading(doc) + + // 演示4:单元格背景 + demonstrateCellShading(doc) + + // 演示5:奇偶行颜色交替 + demonstrateAlternatingRows(doc) + + // 演示6:预定义样式模板 + demonstrateStyleTemplates(doc) + + // 演示7:自定义表格样式 + demonstrateCustomStyle(doc) + + // 演示8:复杂样式组合 + demonstrateComplexStyle(doc) + + // 演示9:无边框表格 + demonstrateNoBorders(doc) + + // 演示10:各种边框样式 + demonstrateBorderStyles(doc) + + // 保存文档 + outputFile := "examples/output/table_style_demo.docx" + if err := doc.Save(outputFile); err != nil { + log.Fatalf("保存文档失败: %v", err) + } + + fmt.Printf("表格样式演示文档已保存到: %s\n", outputFile) +} + +// demonstrateBorders 演示表格边框设置 +func demonstrateBorders(doc *document.Document) { + doc.AddParagraph("1. 表格边框设置演示") + + config := &document.TableConfig{ + Rows: 3, + Cols: 3, + Width: 5000, + Data: [][]string{ + {"姓名", "年龄", "职业"}, + {"张三", "25", "工程师"}, + {"李四", "30", "设计师"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 设置不同的边框样式 + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 12, + Color: "FF0000", // 红色粗上边框 + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "0000FF", // 蓝色左边框 + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 6, + Color: "00FF00", // 绿色双线下边框 + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleDashed, + Width: 6, + Color: "FF00FF", // 紫色虚线右边框 + Space: 0, + }, + InsideH: &document.BorderConfig{ + Style: document.BorderStyleDotted, + Width: 4, + Color: "808080", // 灰色点线内部水平边框 + Space: 0, + }, + InsideV: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "808080", // 灰色内部垂直边框 + Space: 0, + }, + } + + err := table.SetTableBorders(borderConfig) + if err != nil { + log.Fatalf("设置表格边框失败: %v", err) + } + + doc.AddParagraph("") +} + +// demonstrateCellBorders 演示单元格边框设置 +func demonstrateCellBorders(doc *document.Document) { + doc.AddParagraph("2. 单元格边框设置演示") + + config := &document.TableConfig{ + Rows: 2, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"单元格A", "单元格B", "单元格C"}, + {"数据1", "数据2", "数据3"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 为第一个单元格设置特殊边框 + cellBorderConfig := &document.CellBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 8, + Color: "FF0000", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "0000FF", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 6, + Color: "00FF00", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleDashed, + Width: 4, + Color: "FF00FF", + Space: 0, + }, + DiagDown: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 2, + Color: "FFFF00", // 黄色对角线 + Space: 0, + }, + } + + err := table.SetCellBorders(0, 0, cellBorderConfig) + if err != nil { + log.Fatalf("设置单元格边框失败: %v", err) + } + + doc.AddParagraph("") +} + +// demonstrateTableShading 演示表格背景设置 +func demonstrateTableShading(doc *document.Document) { + doc.AddParagraph("3. 表格背景设置演示") + + config := &document.TableConfig{ + Rows: 3, + Cols: 2, + Width: 4000, + Data: [][]string{ + {"产品", "价格"}, + {"产品A", "¥100"}, + {"产品B", "¥200"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 设置表格整体背景 + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternPct25, + ForegroundColor: "000000", + BackgroundColor: "E0E0E0", + } + + err := table.SetTableShading(shadingConfig) + if err != nil { + log.Fatalf("设置表格背景失败: %v", err) + } + + doc.AddParagraph("") +} + +// demonstrateCellShading 演示单元格背景设置 +func demonstrateCellShading(doc *document.Document) { + doc.AddParagraph("4. 单元格背景设置演示") + + config := &document.TableConfig{ + Rows: 3, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"红色", "绿色", "蓝色"}, + {"黄色", "紫色", "青色"}, + {"橙色", "粉色", "灰色"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 为不同单元格设置不同颜色 + colors := [][]string{ + {"FF0000", "00FF00", "0000FF"}, // 红绿蓝 + {"FFFF00", "FF00FF", "00FFFF"}, // 黄紫青 + {"FFA500", "FFC0CB", "808080"}, // 橙粉灰 + } + + patterns := [][]document.ShadingPattern{ + {document.ShadingPatternSolid, document.ShadingPatternPct50, document.ShadingPatternPct25}, + {document.ShadingPatternSolid, document.ShadingPatternPct75, document.ShadingPatternPct10}, + {document.ShadingPatternPct60, document.ShadingPatternPct40, document.ShadingPatternSolid}, + } + + for i := 0; i < 3; i++ { + for j := 0; j < 3; j++ { + shadingConfig := &document.ShadingConfig{ + Pattern: patterns[i][j], + BackgroundColor: colors[i][j], + } + + err := table.SetCellShading(i, j, shadingConfig) + if err != nil { + log.Fatalf("设置单元格(%d,%d)背景失败: %v", i, j, err) + } + } + } + + doc.AddParagraph("") +} + +// demonstrateAlternatingRows 演示奇偶行颜色交替 +func demonstrateAlternatingRows(doc *document.Document) { + doc.AddParagraph("5. 奇偶行颜色交替演示") + + config := &document.TableConfig{ + Rows: 6, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"序号", "姓名", "分数"}, + {"1", "张三", "85"}, + {"2", "李四", "92"}, + {"3", "王五", "78"}, + {"4", "赵六", "95"}, + {"5", "钱七", "88"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 设置奇偶行颜色交替 + err := table.SetAlternatingRowColors("F0F0F0", "FFFFFF") + if err != nil { + log.Fatalf("设置奇偶行颜色交替失败: %v", err) + } + + doc.AddParagraph("") +} + +// demonstrateStyleTemplates 演示预定义样式模板 +func demonstrateStyleTemplates(doc *document.Document) { + doc.AddParagraph("6. 预定义样式模板演示") + + // 演示Grid样式 + config1 := &document.TableConfig{ + Rows: 4, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"项目", "预算", "实际"}, + {"开发", "10000", "9500"}, + {"测试", "5000", "5200"}, + {"总计", "15000", "14700"}, + }, + } + + table1 := doc.AddTable(config1) + if table1 == nil { + log.Fatal("创建表格失败") + } + + styleConfig1 := &document.TableStyleConfig{ + Template: document.TableStyleTemplateGrid, + FirstRowHeader: true, + LastRowTotal: true, + BandedRows: true, + BandedColumns: false, + } + + err := table1.ApplyTableStyle(styleConfig1) + if err != nil { + log.Fatalf("应用Grid样式失败: %v", err) + } + + doc.AddParagraph("") + + // 演示Colorful样式 + config2 := &document.TableConfig{ + Rows: 4, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"部门", "人数", "预算"}, + {"技术部", "20", "500万"}, + {"销售部", "15", "300万"}, + {"市场部", "10", "200万"}, + }, + } + + table2 := doc.AddTable(config2) + if table2 == nil { + log.Fatal("创建表格失败") + } + + styleConfig2 := &document.TableStyleConfig{ + Template: document.TableStyleTemplateColorful1, + FirstRowHeader: true, + FirstColumnHeader: true, + BandedRows: true, + } + + err = table2.ApplyTableStyle(styleConfig2) + if err != nil { + log.Fatalf("应用Colorful样式失败: %v", err) + } + + doc.AddParagraph("") +} + +// demonstrateCustomStyle 演示自定义表格样式 +func demonstrateCustomStyle(doc *document.Document) { + doc.AddParagraph("7. 自定义表格样式演示") + + config := &document.TableConfig{ + Rows: 4, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"功能", "状态", "备注"}, + {"登录", "完成", "已测试"}, + {"注册", "开发中", "50%"}, + {"支付", "计划中", "下个版本"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 创建自定义边框配置 + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 12, + Color: "2E75B6", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "2E75B6", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 12, + Color: "2E75B6", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "2E75B6", + Space: 0, + }, + InsideH: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "D0D0D0", + Space: 0, + }, + InsideV: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "D0D0D0", + Space: 0, + }, + } + + // 创建自定义背景配置 + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternPct10, + BackgroundColor: "E7F3FF", + } + + // 应用自定义样式 + err := table.CreateCustomTableStyle("CustomBlue", "蓝色主题", borderConfig, shadingConfig, true) + if err != nil { + log.Fatalf("创建自定义表格样式失败: %v", err) + } + + doc.AddParagraph("") +} + +// demonstrateComplexStyle 演示复杂样式组合 +func demonstrateComplexStyle(doc *document.Document) { + doc.AddParagraph("8. 复杂样式组合演示") + + config := &document.TableConfig{ + Rows: 5, + Cols: 4, + Width: 6000, + Data: [][]string{ + {"部门", "Q1", "Q2", "Q3"}, + {"销售部", "120万", "135万", "150万"}, + {"技术部", "80万", "90万", "95万"}, + {"市场部", "60万", "70万", "85万"}, + {"总计", "260万", "295万", "330万"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 应用基础样式 + styleConfig := &document.TableStyleConfig{ + Template: document.TableStyleTemplateColorful2, + FirstRowHeader: true, + LastRowTotal: true, + FirstColumnHeader: true, + BandedRows: false, + } + + err := table.ApplyTableStyle(styleConfig) + if err != nil { + log.Fatalf("应用基础样式失败: %v", err) + } + + // 设置特殊边框 + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 8, + Color: "2E75B6", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 8, + Color: "2E75B6", + Space: 0, + }, + InsideH: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "B0B0B0", + Space: 0, + }, + InsideV: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "B0B0B0", + Space: 0, + }, + } + + err = table.SetTableBorders(borderConfig) + if err != nil { + log.Fatalf("设置边框失败: %v", err) + } + + // 为标题行设置特殊背景 + for j := 0; j < 4; j++ { + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternSolid, + BackgroundColor: "2E75B6", + ForegroundColor: "FFFFFF", + } + + err = table.SetCellShading(0, j, shadingConfig) + if err != nil { + log.Fatalf("设置标题行背景失败: %v", err) + } + } + + // 为总计行设置特殊背景 + for j := 0; j < 4; j++ { + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternSolid, + BackgroundColor: "FFFF99", + } + + err = table.SetCellShading(4, j, shadingConfig) + if err != nil { + log.Fatalf("设置总计行背景失败: %v", err) + } + } + + doc.AddParagraph("") +} + +// demonstrateNoBorders 演示无边框表格 +func demonstrateNoBorders(doc *document.Document) { + doc.AddParagraph("9. 无边框表格演示") + + config := &document.TableConfig{ + Rows: 3, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"项目A", "项目B", "项目C"}, + {"说明1", "说明2", "说明3"}, + {"结果1", "结果2", "结果3"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + // 移除所有边框 + err := table.RemoveTableBorders() + if err != nil { + log.Fatalf("移除表格边框失败: %v", err) + } + + // 设置轻微的背景色以区分单元格 + for i := 0; i < 3; i++ { + for j := 0; j < 3; j++ { + var bgColor string + if (i+j)%2 == 0 { + bgColor = "F8F8F8" + } else { + bgColor = "FFFFFF" + } + + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternSolid, + BackgroundColor: bgColor, + } + + err = table.SetCellShading(i, j, shadingConfig) + if err != nil { + log.Fatalf("设置单元格背景失败: %v", err) + } + } + } + + doc.AddParagraph("") +} + +// demonstrateBorderStyles 演示各种边框样式 +func demonstrateBorderStyles(doc *document.Document) { + doc.AddParagraph("10. 各种边框样式演示") + + borderStyles := []struct { + style document.BorderStyle + name string + }{ + {document.BorderStyleSingle, "单线"}, + {document.BorderStyleThick, "粗线"}, + {document.BorderStyleDouble, "双线"}, + {document.BorderStyleDotted, "点线"}, + {document.BorderStyleDashed, "虚线"}, + {document.BorderStyleDotDash, "点划线"}, + {document.BorderStyleWave, "波浪线"}, + } + + for i, styleInfo := range borderStyles { + config := &document.TableConfig{ + Rows: 2, + Cols: 2, + Width: 3000, + Data: [][]string{ + {fmt.Sprintf("样式%d", i+1), styleInfo.name}, + {"演示", "数据"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatalf("创建表格%d失败", i+1) + } + + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: styleInfo.style, + Width: 6, + Color: "000000", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: styleInfo.style, + Width: 6, + Color: "000000", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: styleInfo.style, + Width: 6, + Color: "000000", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: styleInfo.style, + Width: 6, + Color: "000000", + Space: 0, + }, + } + + err := table.SetTableBorders(borderConfig) + if err != nil { + log.Fatalf("设置表格%d边框失败: %v", i+1, err) + } + + if i < len(borderStyles)-1 { + doc.AddParagraph("") + } + } +} diff --git a/go.mod b/go.mod index 3b15204..e3dba08 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,3 @@ module github.com/ZeroHawkeye/wordZero go 1.21 - -require ( -) diff --git a/pkg/document/document.go b/pkg/document/document.go index 59920fb..ff09bcf 100644 --- a/pkg/document/document.go +++ b/pkg/document/document.go @@ -31,9 +31,26 @@ type Document struct { // Body 表示文档主体 type Body struct { - XMLName xml.Name `xml:"w:body"` - Paragraphs []Paragraph `xml:"w:p"` - Tables []Table `xml:"w:tbl"` + XMLName xml.Name `xml:"w:body"` + Elements []interface{} `xml:"-"` // 不序列化此字段,使用自定义方法 +} + +// MarshalXML 自定义XML序列化,按照元素顺序输出 +func (b *Body) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + // 开始元素 + if err := e.EncodeToken(start); err != nil { + return err + } + + // 序列化每个元素,保持顺序 + for _, element := range b.Elements { + if err := e.Encode(element); err != nil { + return err + } + } + + // 结束元素 + return e.EncodeToken(start.End()) } // BodyElement 文档主体元素接口 @@ -218,7 +235,7 @@ func New() *Document { doc := &Document{ Body: &Body{ - Paragraphs: make([]Paragraph, 0), + Elements: make([]interface{}, 0), }, styleManager: style.NewStyleManager(), parts: make(map[string][]byte), @@ -283,6 +300,9 @@ func Open(filename string) (*Document, error) { Debugf("已读取文件部件: %s (%d 字节)", file.Name, len(data)) } + // 初始化样式管理器 + doc.styleManager = style.NewStyleManager() + // 解析主文档 if err := doc.parseDocument(); err != nil { Errorf("解析文档失败: %s", filename) @@ -400,7 +420,7 @@ func (d *Document) Save(filename string) error { // }) func (d *Document) AddParagraph(text string) *Paragraph { Debugf("添加段落: %s", text) - p := Paragraph{ + p := &Paragraph{ Runs: []Run{ { Text: Text{ @@ -411,8 +431,8 @@ func (d *Document) AddParagraph(text string) *Paragraph { }, } - d.Body.Paragraphs = append(d.Body.Paragraphs, p) - return &d.Body.Paragraphs[len(d.Body.Paragraphs)-1] + d.Body.Elements = append(d.Body.Elements, p) + return p } // AddFormattedParagraph 向文档添加一个格式化段落。 @@ -468,7 +488,7 @@ func (d *Document) AddFormattedParagraph(text string, format *TextFormat) *Parag } } - p := Paragraph{ + p := &Paragraph{ Runs: []Run{ { Properties: runProps, @@ -480,8 +500,8 @@ func (d *Document) AddFormattedParagraph(text string, format *TextFormat) *Parag }, } - d.Body.Paragraphs = append(d.Body.Paragraphs, p) - return &d.Body.Paragraphs[len(d.Body.Paragraphs)-1] + d.Body.Elements = append(d.Body.Elements, p) + return p } // SetAlignment 设置段落的对齐方式。 @@ -725,7 +745,7 @@ func (d *Document) AddHeadingParagraph(text string, level int) *Paragraph { } // 创建段落 - p := Paragraph{ + p := &Paragraph{ Properties: paraProps, Runs: []Run{ { @@ -738,8 +758,8 @@ func (d *Document) AddHeadingParagraph(text string, level int) *Paragraph { }, } - d.Body.Paragraphs = append(d.Body.Paragraphs, p) - return &d.Body.Paragraphs[len(d.Body.Paragraphs)-1] + d.Body.Elements = append(d.Body.Elements, p) + return p } // SetStyle 设置段落的样式。 @@ -824,7 +844,11 @@ func (d *Document) parseDocument() error { Paragraphs []struct { XMLName xml.Name `xml:"p"` Properties *struct { - XMLName xml.Name `xml:"pPr"` + XMLName xml.Name `xml:"pPr"` + ParagraphStyle *struct { + XMLName xml.Name `xml:"pStyle"` + Val string `xml:"val,attr"` + } `xml:"pStyle,omitempty"` Spacing *struct { XMLName xml.Name `xml:"spacing"` Before string `xml:"before,attr,omitempty"` @@ -882,18 +906,24 @@ func (d *Document) parseDocument() error { // 转换为内部结构 d.Body = &Body{ - Paragraphs: make([]Paragraph, len(doc.Body.Paragraphs)), + Elements: make([]interface{}, len(doc.Body.Paragraphs)), } for i, p := range doc.Body.Paragraphs { - paragraph := Paragraph{ + paragraph := &Paragraph{ Runs: make([]Run, len(p.Runs)), } - // 复制段落属性 + // 转换段落属性 if p.Properties != nil { paragraph.Properties = &ParagraphProperties{} + if p.Properties.ParagraphStyle != nil { + paragraph.Properties.ParagraphStyle = &ParagraphStyle{ + Val: p.Properties.ParagraphStyle.Val, + } + } + if p.Properties.Spacing != nil { paragraph.Properties.Spacing = &Spacing{ Before: p.Properties.Spacing.Before, @@ -917,16 +947,13 @@ func (d *Document) parseDocument() error { } } - // 复制运行和运行属性 for j, r := range p.Runs { run := Run{ Text: Text{ - Space: r.Text.Space, Content: r.Text.Content, }, } - // 复制运行属性 if r.Properties != nil { run.Properties = &RunProperties{} @@ -960,10 +987,10 @@ func (d *Document) parseDocument() error { paragraph.Runs[j] = run } - d.Body.Paragraphs[i] = paragraph + d.Body.Elements[i] = paragraph } - Infof("解析完成,共 %d 个段落", len(d.Body.Paragraphs)) + Infof("解析完成,共 %d 个元素", len(d.Body.Elements)) return nil } @@ -975,12 +1002,12 @@ func (d *Document) serializeDocument() error { type documentXML struct { XMLName xml.Name `xml:"w:document"` Xmlns string `xml:"xmlns:w,attr"` - Body Body `xml:"w:body"` + Body *Body `xml:"w:body"` } doc := documentXML{ Xmlns: "http://schemas.openxmlformats.org/wordprocessingml/2006/main", - Body: *d.Body, + Body: d.Body, } // 序列化为XML @@ -1118,3 +1145,30 @@ func (d *Document) ToBytes() ([]byte, error) { return buf.Bytes(), nil } + +// GetParagraphs 获取所有段落 +func (b *Body) GetParagraphs() []*Paragraph { + paragraphs := make([]*Paragraph, 0) + for _, element := range b.Elements { + if p, ok := element.(*Paragraph); ok { + paragraphs = append(paragraphs, p) + } + } + return paragraphs +} + +// GetTables 获取所有表格 +func (b *Body) GetTables() []*Table { + tables := make([]*Table, 0) + for _, element := range b.Elements { + if t, ok := element.(*Table); ok { + tables = append(tables, t) + } + } + return tables +} + +// AddElement 添加元素到文档主体 +func (b *Body) AddElement(element interface{}) { + b.Elements = append(b.Elements, element) +} diff --git a/pkg/document/document_test.go b/pkg/document/document_test.go index 3143194..1ae1a0e 100644 --- a/pkg/document/document_test.go +++ b/pkg/document/document_test.go @@ -25,8 +25,8 @@ func TestNewDocument(t *testing.T) { } // 验证初始状态 - if len(doc.Body.Paragraphs) != 0 { - t.Errorf("Expected 0 paragraphs, got %d", len(doc.Body.Paragraphs)) + if len(doc.Body.GetParagraphs()) != 0 { + t.Errorf("Expected 0 paragraphs, got %d", len(doc.Body.GetParagraphs())) } // 验证样式管理器初始化 @@ -44,8 +44,8 @@ func TestAddParagraph(t *testing.T) { para := doc.AddParagraph(text) // 验证段落添加 - if len(doc.Body.Paragraphs) != 1 { - t.Errorf("Expected 1 paragraph, got %d", len(doc.Body.Paragraphs)) + if len(doc.Body.GetParagraphs()) != 1 { + t.Errorf("Expected 1 paragraph, got %d", len(doc.Body.GetParagraphs())) } // 验证段落内容 @@ -58,7 +58,8 @@ func TestAddParagraph(t *testing.T) { } // 验证返回的指针是否正确 - if &doc.Body.Paragraphs[0] != para { + paragraphs := doc.Body.GetParagraphs() + if paragraphs[0] != para { t.Error("Returned paragraph pointer is incorrect") } } @@ -135,7 +136,7 @@ func TestAddFormattedParagraph(t *testing.T) { para := doc.AddFormattedParagraph(text, format) // 验证段落添加 - if len(doc.Body.Paragraphs) != 1 { + if len(doc.Body.GetParagraphs()) != 1 { t.Error("Failed to add formatted paragraph") } @@ -379,8 +380,8 @@ func TestComplexDocument(t *testing.T) { mixed.AddFormattedText("文本。", nil) // 验证文档结构 - if len(doc.Body.Paragraphs) != 6 { - t.Errorf("Expected 6 paragraphs, got %d", len(doc.Body.Paragraphs)) + if len(doc.Body.GetParagraphs()) != 6 { + t.Errorf("Expected 6 paragraphs, got %d", len(doc.Body.GetParagraphs())) } // 保存并验证 @@ -416,13 +417,13 @@ func TestDocumentOpen(t *testing.T) { } // 验证文档内容 - if len(loadedDoc.Body.Paragraphs) != 3 { - t.Errorf("Expected 3 paragraphs, got %d", len(loadedDoc.Body.Paragraphs)) + if len(loadedDoc.Body.GetParagraphs()) != 3 { + t.Errorf("Expected 3 paragraphs, got %d", len(loadedDoc.Body.GetParagraphs())) } // 验证第一段内容 - if len(loadedDoc.Body.Paragraphs[0].Runs) > 0 { - content := loadedDoc.Body.Paragraphs[0].Runs[0].Text.Content + if len(loadedDoc.Body.GetParagraphs()[0].Runs) > 0 { + content := loadedDoc.Body.GetParagraphs()[0].Runs[0].Text.Content if content != "第一段" { t.Errorf("Expected '第一段', got '%s'", content) } @@ -565,8 +566,8 @@ func TestMemoryUsage(t *testing.T) { doc.AddParagraph("内存测试段落") } - if len(doc.Body.Paragraphs) != numParagraphs { - t.Errorf("Expected %d paragraphs, got %d", numParagraphs, len(doc.Body.Paragraphs)) + if len(doc.Body.GetParagraphs()) != numParagraphs { + t.Errorf("Expected %d paragraphs, got %d", numParagraphs, len(doc.Body.GetParagraphs())) } // 测试保存大文档 diff --git a/pkg/document/table.go b/pkg/document/table.go index 17fc4fa..3322062 100644 --- a/pkg/document/table.go +++ b/pkg/document/table.go @@ -16,10 +16,16 @@ type Table struct { // TableProperties 表格属性 type TableProperties struct { - XMLName xml.Name `xml:"w:tblPr"` - TableW *TableWidth `xml:"w:tblW,omitempty"` - TableJc *TableJc `xml:"w:jc,omitempty"` - TableLook *TableLook `xml:"w:tblLook,omitempty"` + XMLName xml.Name `xml:"w:tblPr"` + TableW *TableWidth `xml:"w:tblW,omitempty"` + TableJc *TableJc `xml:"w:jc,omitempty"` + TableLook *TableLook `xml:"w:tblLook,omitempty"` + TableStyle *TableStyle `xml:"w:tblStyle,omitempty"` // 表格样式 + TableBorders *TableBorders `xml:"w:tblBorders,omitempty"` // 表格边框 + Shd *TableShading `xml:"w:shd,omitempty"` // 表格底纹/背景 + TableCellMar *TableCellMargins `xml:"w:tblCellMar,omitempty"` // 表格单元格边距 + TableLayout *TableLayoutType `xml:"w:tblLayout,omitempty"` // 表格布局类型 + TableInd *TableIndentation `xml:"w:tblInd,omitempty"` // 表格缩进 } // TableWidth 表格宽度 @@ -70,6 +76,8 @@ type TableRow struct { type TableRowProperties struct { XMLName xml.Name `xml:"w:trPr"` TableRowH *TableRowH `xml:"w:trHeight,omitempty"` + CantSplit *CantSplit `xml:"w:cantSplit,omitempty"` // 禁止跨页分割 + TblHeader *TblHeader `xml:"w:tblHeader,omitempty"` // 标题行重复 } // TableRowH 表格行高 @@ -88,12 +96,32 @@ type TableCell struct { // TableCellProperties 表格单元格属性 type TableCellProperties struct { - XMLName xml.Name `xml:"w:tcPr"` - TableCellW *TableCellW `xml:"w:tcW,omitempty"` - VAlign *VAlign `xml:"w:vAlign,omitempty"` - GridSpan *GridSpan `xml:"w:gridSpan,omitempty"` - VMerge *VMerge `xml:"w:vMerge,omitempty"` - TextDirection *TextDirection `xml:"w:textDirection,omitempty"` + XMLName xml.Name `xml:"w:tcPr"` + TableCellW *TableCellW `xml:"w:tcW,omitempty"` + VAlign *VAlign `xml:"w:vAlign,omitempty"` + GridSpan *GridSpan `xml:"w:gridSpan,omitempty"` + VMerge *VMerge `xml:"w:vMerge,omitempty"` + TextDirection *TextDirection `xml:"w:textDirection,omitempty"` + Shd *TableCellShading `xml:"w:shd,omitempty"` // 单元格背景 + TcBorders *TableCellBorders `xml:"w:tcBorders,omitempty"` // 单元格边框 + TcMar *TableCellMarginsCell `xml:"w:tcMar,omitempty"` // 单元格边距 + NoWrap *NoWrap `xml:"w:noWrap,omitempty"` // 禁止换行 + HideMark *HideMark `xml:"w:hideMark,omitempty"` // 隐藏标记 +} + +// TableCellMarginsCell 单元格边距(与表格边距不同的XML结构) +type TableCellMarginsCell struct { + XMLName xml.Name `xml:"w:tcMar"` + Top *TableCellSpaceCell `xml:"w:top,omitempty"` + Left *TableCellSpaceCell `xml:"w:left,omitempty"` + Bottom *TableCellSpaceCell `xml:"w:bottom,omitempty"` + Right *TableCellSpaceCell `xml:"w:right,omitempty"` +} + +// TableCellSpaceCell 单元格空间设置 +type TableCellSpaceCell struct { + W string `xml:"w:w,attr"` + Type string `xml:"w:type,attr"` } // TableCellW 单元格宽度 @@ -234,9 +262,9 @@ func (d *Document) AddTable(config *TableConfig) *Table { } // 将表格添加到文档主体中 - d.Body.Tables = append(d.Body.Tables, *table) + d.Body.Elements = append(d.Body.Elements, table) - Info(fmt.Sprintf("表格已添加到文档,当前文档包含%d个表格", len(d.Body.Tables))) + Info(fmt.Sprintf("表格已添加到文档,当前文档包含%d个表格", len(d.Body.GetTables()))) return table } @@ -1268,3 +1296,974 @@ type TextDirection struct { XMLName xml.Name `xml:"w:textDirection"` Val string `xml:"w:val,attr"` } + +// RowHeightRule 行高规则 +type RowHeightRule string + +const ( + // RowHeightAuto 自动调整行高 + RowHeightAuto RowHeightRule = "auto" + // RowHeightMinimum 最小行高 + RowHeightMinimum RowHeightRule = "atLeast" + // RowHeightExact 固定行高 + RowHeightExact RowHeightRule = "exact" +) + +// RowHeightConfig 行高配置 +type RowHeightConfig struct { + Height int // 行高值(磅,1磅=20twips) + Rule RowHeightRule // 行高规则 +} + +// SetRowHeight 设置行高 +func (t *Table) SetRowHeight(rowIndex int, config *RowHeightConfig) error { + if rowIndex < 0 || rowIndex >= len(t.Rows) { + return fmt.Errorf("行索引无效:%d,表格共有%d行", rowIndex, len(t.Rows)) + } + + row := &t.Rows[rowIndex] + if row.Properties == nil { + row.Properties = &TableRowProperties{} + } + + // 设置行高属性 + row.Properties.TableRowH = &TableRowH{ + Val: fmt.Sprintf("%d", config.Height*20), // 转换为twips (1磅=20twips) + HRule: string(config.Rule), + } + + Info(fmt.Sprintf("设置第%d行高度为%d磅,规则为%s", rowIndex, config.Height, config.Rule)) + return nil +} + +// GetRowHeight 获取行高配置 +func (t *Table) GetRowHeight(rowIndex int) (*RowHeightConfig, error) { + if rowIndex < 0 || rowIndex >= len(t.Rows) { + return nil, fmt.Errorf("行索引无效:%d,表格共有%d行", rowIndex, len(t.Rows)) + } + + row := &t.Rows[rowIndex] + if row.Properties == nil || row.Properties.TableRowH == nil { + // 返回默认自动高度 + return &RowHeightConfig{ + Height: 0, + Rule: RowHeightAuto, + }, nil + } + + height := 0 + if row.Properties.TableRowH.Val != "" { + fmt.Sscanf(row.Properties.TableRowH.Val, "%d", &height) + height /= 20 // 转换为磅 + } + + rule := RowHeightAuto + if row.Properties.TableRowH.HRule != "" { + rule = RowHeightRule(row.Properties.TableRowH.HRule) + } + + return &RowHeightConfig{ + Height: height, + Rule: rule, + }, nil +} + +// SetRowHeightRange 批量设置行高 +func (t *Table) SetRowHeightRange(startRow, endRow int, config *RowHeightConfig) error { + if startRow < 0 || endRow >= len(t.Rows) || startRow > endRow { + return fmt.Errorf("行索引范围无效:[%d, %d],表格共有%d行", startRow, endRow, len(t.Rows)) + } + + for i := startRow; i <= endRow; i++ { + err := t.SetRowHeight(i, config) + if err != nil { + return fmt.Errorf("设置第%d行高度失败:%v", i, err) + } + } + + Info(fmt.Sprintf("批量设置第%d到%d行高度成功", startRow, endRow)) + return nil +} + +// TableTextWrap 表格文字环绕类型 +type TableTextWrap string + +const ( + // TextWrapNone 无环绕(默认) + TextWrapNone TableTextWrap = "none" + // TextWrapAround 环绕表格 + TextWrapAround TableTextWrap = "around" +) + +// TablePosition 表格定位类型 +type TablePosition string + +const ( + // PositionInline 行内定位(默认) + PositionInline TablePosition = "inline" + // PositionFloating 浮动定位 + PositionFloating TablePosition = "floating" +) + +// TableAlignment 表格对齐类型 +type TableAlignment string + +const ( + // TableAlignLeft 左对齐 + TableAlignLeft TableAlignment = "left" + // TableAlignCenter 居中对齐 + TableAlignCenter TableAlignment = "center" + // TableAlignRight 右对齐 + TableAlignRight TableAlignment = "right" + // TableAlignInside 内侧对齐 + TableAlignInside TableAlignment = "inside" + // TableAlignOutside 外侧对齐 + TableAlignOutside TableAlignment = "outside" +) + +// TablePositioning 表格定位配置 +type TablePositioning struct { + XMLName xml.Name `xml:"w:tblpPr"` + LeftFromText string `xml:"w:leftFromText,attr,omitempty"` // 距离左侧文字的距离 + RightFromText string `xml:"w:rightFromText,attr,omitempty"` // 距离右侧文字的距离 + TopFromText string `xml:"w:topFromText,attr,omitempty"` // 距离上方文字的距离 + BottomFromText string `xml:"w:bottomFromText,attr,omitempty"` // 距离下方文字的距离 + VertAnchor string `xml:"w:vertAnchor,attr,omitempty"` // 垂直锚点 + HorzAnchor string `xml:"w:horzAnchor,attr,omitempty"` // 水平锚点 + TblpXSpec string `xml:"w:tblpXSpec,attr,omitempty"` // 水平对齐规格 + TblpYSpec string `xml:"w:tblpYSpec,attr,omitempty"` // 垂直对齐规格 + TblpX string `xml:"w:tblpX,attr,omitempty"` // X坐标 + TblpY string `xml:"w:tblpY,attr,omitempty"` // Y坐标 +} + +// TableLayoutConfig 表格布局配置 +type TableLayoutConfig struct { + Alignment TableAlignment // 表格对齐方式 + TextWrap TableTextWrap // 文字环绕类型 + Position TablePosition // 定位类型 + Positioning *TablePositioning // 定位详细配置(仅在Position为Floating时有效) +} + +// SetTableLayout 设置表格布局和定位 +func (t *Table) SetTableLayout(config *TableLayoutConfig) error { + if t.Properties == nil { + t.Properties = &TableProperties{} + } + + // 设置表格对齐 + if config.Alignment != "" { + t.Properties.TableJc = &TableJc{ + Val: string(config.Alignment), + } + } + + // 设置定位属性(仅在浮动定位时生效) + if config.Position == PositionFloating && config.Positioning != nil { + // 在OOXML中,浮动表格定位需要特殊的TablePositioning属性 + // 这里将配置信息存储到表格属性中 + Info("设置表格为浮动定位模式") + // 注意:完整的浮动定位实现需要更复杂的XML结构支持 + } + + Info(fmt.Sprintf("设置表格布局:对齐=%s,环绕=%s,定位=%s", + config.Alignment, config.TextWrap, config.Position)) + return nil +} + +// GetTableLayout 获取表格布局配置 +func (t *Table) GetTableLayout() *TableLayoutConfig { + config := &TableLayoutConfig{ + Alignment: TableAlignLeft, // 默认值 + TextWrap: TextWrapNone, + Position: PositionInline, + } + + if t.Properties != nil && t.Properties.TableJc != nil { + config.Alignment = TableAlignment(t.Properties.TableJc.Val) + } + + return config +} + +// SetTableAlignment 设置表格对齐方式(快捷方法) +func (t *Table) SetTableAlignment(alignment TableAlignment) error { + return t.SetTableLayout(&TableLayoutConfig{ + Alignment: alignment, + TextWrap: TextWrapNone, + Position: PositionInline, + }) +} + +// TableBreakRule 表格分页规则 +type TableBreakRule string + +const ( + // BreakAuto 自动分页(默认) + BreakAuto TableBreakRule = "auto" + // BreakPage 强制分页 + BreakPage TableBreakRule = "page" + // BreakColumn 强制分栏 + BreakColumn TableBreakRule = "column" +) + +// RowBreakConfig 行分页配置 +type RowBreakConfig struct { + XMLName xml.Name `xml:"w:trPr"` + CantSplit *CantSplit `xml:"w:cantSplit,omitempty"` // 禁止跨页分割 + TrHeight *TableRowH `xml:"w:trHeight,omitempty"` // 行高 + TblHeader *TblHeader `xml:"w:tblHeader,omitempty"` // 标题行重复 +} + +// CantSplit 禁止分割 +type CantSplit struct { + XMLName xml.Name `xml:"w:cantSplit"` + Val string `xml:"w:val,attr,omitempty"` +} + +// TblHeader 表格标题行 +type TblHeader struct { + XMLName xml.Name `xml:"w:tblHeader"` + Val string `xml:"w:val,attr,omitempty"` +} + +// SetRowKeepTogether 设置行禁止跨页分割 +func (t *Table) SetRowKeepTogether(rowIndex int, keepTogether bool) error { + if rowIndex < 0 || rowIndex >= len(t.Rows) { + return fmt.Errorf("行索引无效:%d,表格共有%d行", rowIndex, len(t.Rows)) + } + + row := &t.Rows[rowIndex] + if row.Properties == nil { + row.Properties = &TableRowProperties{} + } + + if keepTogether { + row.Properties.CantSplit = &CantSplit{ + Val: "1", + } + } else { + row.Properties.CantSplit = nil + } + + Info(fmt.Sprintf("设置第%d行跨页分割为:%t", rowIndex, !keepTogether)) + return nil +} + +// SetRowAsHeader 设置行为重复的标题行 +func (t *Table) SetRowAsHeader(rowIndex int, isHeader bool) error { + if rowIndex < 0 || rowIndex >= len(t.Rows) { + return fmt.Errorf("行索引无效:%d,表格共有%d行", rowIndex, len(t.Rows)) + } + + row := &t.Rows[rowIndex] + if row.Properties == nil { + row.Properties = &TableRowProperties{} + } + + if isHeader { + row.Properties.TblHeader = &TblHeader{ + Val: "1", + } + } else { + row.Properties.TblHeader = nil + } + + Info(fmt.Sprintf("设置第%d行为标题行:%t", rowIndex, isHeader)) + return nil +} + +// SetHeaderRows 设置表格标题行范围 +func (t *Table) SetHeaderRows(startRow, endRow int) error { + if startRow < 0 || endRow >= len(t.Rows) || startRow > endRow { + return fmt.Errorf("行索引范围无效:[%d, %d],表格共有%d行", startRow, endRow, len(t.Rows)) + } + + // 清除所有现有的标题行设置 + for i := range t.Rows { + if t.Rows[i].Properties != nil { + t.Rows[i].Properties.TblHeader = nil + } + } + + // 设置指定范围为标题行 + for i := startRow; i <= endRow; i++ { + err := t.SetRowAsHeader(i, true) + if err != nil { + return fmt.Errorf("设置第%d行为标题行失败:%v", i, err) + } + } + + Info(fmt.Sprintf("设置第%d到%d行为标题行", startRow, endRow)) + return nil +} + +// IsRowHeader 检查行是否为标题行 +func (t *Table) IsRowHeader(rowIndex int) (bool, error) { + if rowIndex < 0 || rowIndex >= len(t.Rows) { + return false, fmt.Errorf("行索引无效:%d,表格共有%d行", rowIndex, len(t.Rows)) + } + + row := &t.Rows[rowIndex] + if row.Properties != nil && row.Properties.TblHeader != nil { + return row.Properties.TblHeader.Val == "1", nil + } + + return false, nil +} + +// IsRowKeepTogether 检查行是否禁止跨页分割 +func (t *Table) IsRowKeepTogether(rowIndex int) (bool, error) { + if rowIndex < 0 || rowIndex >= len(t.Rows) { + return false, fmt.Errorf("行索引无效:%d,表格共有%d行", rowIndex, len(t.Rows)) + } + + row := &t.Rows[rowIndex] + if row.Properties != nil && row.Properties.CantSplit != nil { + return row.Properties.CantSplit.Val == "1", nil + } + + return false, nil +} + +// TablePageBreakConfig 表格分页配置 +type TablePageBreakConfig struct { + KeepWithNext bool // 与下一段落保持在一起 + KeepLines bool // 保持行在一起 + PageBreakBefore bool // 段落前分页 + WidowControl bool // 孤行控制 +} + +// SetTablePageBreak 设置表格分页控制 +func (t *Table) SetTablePageBreak(config *TablePageBreakConfig) error { + // 表格级别的分页控制通常在表格属性中设置 + // 这里先记录配置,实际XML输出时需要相应的实现 + Info(fmt.Sprintf("设置表格分页控制:保持与下一段落=%t,保持行=%t,段前分页=%t,孤行控制=%t", + config.KeepWithNext, config.KeepLines, config.PageBreakBefore, config.WidowControl)) + return nil +} + +// SetRowKeepWithNext 设置行与下一行保持在同一页 +func (t *Table) SetRowKeepWithNext(rowIndex int, keepWithNext bool) error { + if rowIndex < 0 || rowIndex >= len(t.Rows) { + return fmt.Errorf("行索引无效:%d,表格共有%d行", rowIndex, len(t.Rows)) + } + + // 这个功能需要在行属性中设置特定的分页属性 + // 实际实现时需要扩展TableRowProperties结构 + Info(fmt.Sprintf("设置第%d行与下一行保持在同一页:%t", rowIndex, keepWithNext)) + return nil +} + +// GetTableBreakInfo 获取表格分页信息 +func (t *Table) GetTableBreakInfo() map[string]interface{} { + info := make(map[string]interface{}) + + headerRowCount := 0 + keepTogetherCount := 0 + + for i := range t.Rows { + isHeader, _ := t.IsRowHeader(i) + if isHeader { + headerRowCount++ + } + + keepTogether, _ := t.IsRowKeepTogether(i) + if keepTogether { + keepTogetherCount++ + } + } + + info["total_rows"] = len(t.Rows) + info["header_rows"] = headerRowCount + info["keep_together_rows"] = keepTogetherCount + + return info +} + +// 扩展TableRowProperties以支持分页控制 +type TableRowPropertiesExtended struct { + XMLName xml.Name `xml:"w:trPr"` + TableRowH *TableRowH `xml:"w:trHeight,omitempty"` + CantSplit *CantSplit `xml:"w:cantSplit,omitempty"` + TblHeader *TblHeader `xml:"w:tblHeader,omitempty"` + KeepNext *KeepNext `xml:"w:keepNext,omitempty"` + KeepLines *KeepLines `xml:"w:keepLines,omitempty"` +} + +// KeepNext 与下一段落保持在一起 +type KeepNext struct { + XMLName xml.Name `xml:"w:keepNext"` + Val string `xml:"w:val,attr,omitempty"` +} + +// KeepLines 保持行在一起 +type KeepLines struct { + XMLName xml.Name `xml:"w:keepLines"` + Val string `xml:"w:val,attr,omitempty"` +} + +// 扩展现有的TableRowProperties结构 +func (trp *TableRowProperties) SetCantSplit(cantSplit bool) { + if cantSplit { + trp.CantSplit = &CantSplit{Val: "1"} + } else { + trp.CantSplit = nil + } +} + +func (trp *TableRowProperties) SetTblHeader(isHeader bool) { + if isHeader { + trp.TblHeader = &TblHeader{Val: "1"} + } else { + trp.TblHeader = nil + } +} + +// TableStyle 表格样式引用 +type TableStyle struct { + XMLName xml.Name `xml:"w:tblStyle"` + Val string `xml:"w:val,attr"` +} + +// TableBorders 表格边框 +type TableBorders struct { + XMLName xml.Name `xml:"w:tblBorders"` + Top *TableBorder `xml:"w:top,omitempty"` // 上边框 + Left *TableBorder `xml:"w:left,omitempty"` // 左边框 + Bottom *TableBorder `xml:"w:bottom,omitempty"` // 下边框 + Right *TableBorder `xml:"w:right,omitempty"` // 右边框 + InsideH *TableBorder `xml:"w:insideH,omitempty"` // 内部水平边框 + InsideV *TableBorder `xml:"w:insideV,omitempty"` // 内部垂直边框 +} + +// TableBorder 边框定义 +type TableBorder struct { + Val string `xml:"w:val,attr"` // 边框样式 + Sz string `xml:"w:sz,attr"` // 边框粗细(1/8磅) + Space string `xml:"w:space,attr"` // 边框间距 + Color string `xml:"w:color,attr"` // 边框颜色 + ThemeColor string `xml:"w:themeColor,attr,omitempty"` // 主题颜色 +} + +// TableShading 表格底纹/背景 +type TableShading struct { + XMLName xml.Name `xml:"w:shd"` + Val string `xml:"w:val,attr"` // 底纹样式 + Color string `xml:"w:color,attr,omitempty"` // 前景色 + Fill string `xml:"w:fill,attr,omitempty"` // 背景色 + ThemeFill string `xml:"w:themeFill,attr,omitempty"` // 主题填充色 +} + +// TableCellMargins 表格单元格边距 +type TableCellMargins struct { + XMLName xml.Name `xml:"w:tblCellMar"` + Top *TableCellSpace `xml:"w:top,omitempty"` + Left *TableCellSpace `xml:"w:left,omitempty"` + Bottom *TableCellSpace `xml:"w:bottom,omitempty"` + Right *TableCellSpace `xml:"w:right,omitempty"` +} + +// TableCellSpace 表格单元格空间 +type TableCellSpace struct { + W string `xml:"w:w,attr"` + Type string `xml:"w:type,attr"` +} + +// TableLayoutType 表格布局类型 +type TableLayoutType struct { + XMLName xml.Name `xml:"w:tblLayout"` + Type string `xml:"w:type,attr"` // fixed, autofit +} + +// TableIndentation 表格缩进 +type TableIndentation struct { + XMLName xml.Name `xml:"w:tblInd"` + W string `xml:"w:w,attr"` + Type string `xml:"w:type,attr"` +} + +// TableCellShading 单元格背景 +type TableCellShading struct { + XMLName xml.Name `xml:"w:shd"` + Val string `xml:"w:val,attr"` // 底纹样式 + Color string `xml:"w:color,attr,omitempty"` // 前景色 + Fill string `xml:"w:fill,attr,omitempty"` // 背景色 + ThemeFill string `xml:"w:themeFill,attr,omitempty"` // 主题填充色 +} + +// TableCellBorders 单元格边框 +type TableCellBorders struct { + XMLName xml.Name `xml:"w:tcBorders"` + Top *TableCellBorder `xml:"w:top,omitempty"` // 上边框 + Left *TableCellBorder `xml:"w:left,omitempty"` // 左边框 + Bottom *TableCellBorder `xml:"w:bottom,omitempty"` // 下边框 + Right *TableCellBorder `xml:"w:right,omitempty"` // 右边框 + InsideH *TableCellBorder `xml:"w:insideH,omitempty"` // 内部水平边框 + InsideV *TableCellBorder `xml:"w:insideV,omitempty"` // 内部垂直边框 + TL2BR *TableCellBorder `xml:"w:tl2br,omitempty"` // 左上到右下对角线 + TR2BL *TableCellBorder `xml:"w:tr2bl,omitempty"` // 右上到左下对角线 +} + +// TableCellBorder 单元格边框定义 +type TableCellBorder struct { + Val string `xml:"w:val,attr"` // 边框样式 + Sz string `xml:"w:sz,attr"` // 边框粗细(1/8磅) + Space string `xml:"w:space,attr"` // 边框间距 + Color string `xml:"w:color,attr"` // 边框颜色 + ThemeColor string `xml:"w:themeColor,attr,omitempty"` // 主题颜色 +} + +// NoWrap 禁止换行 +type NoWrap struct { + XMLName xml.Name `xml:"w:noWrap"` + Val string `xml:"w:val,attr,omitempty"` +} + +// HideMark 隐藏标记 +type HideMark struct { + XMLName xml.Name `xml:"w:hideMark"` + Val string `xml:"w:val,attr,omitempty"` +} + +// ============== 表格样式和外观功能 ============== + +// BorderStyle 边框样式常量 +type BorderStyle string + +const ( + BorderStyleNone BorderStyle = "none" // 无边框 + BorderStyleSingle BorderStyle = "single" // 单线 + BorderStyleThick BorderStyle = "thick" // 粗线 + BorderStyleDouble BorderStyle = "double" // 双线 + BorderStyleDotted BorderStyle = "dotted" // 点线 + BorderStyleDashed BorderStyle = "dashed" // 虚线 + BorderStyleDotDash BorderStyle = "dotDash" // 点划线 + BorderStyleDotDotDash BorderStyle = "dotDotDash" // 双点划线 + BorderStyleTriple BorderStyle = "triple" // 三线 + BorderStyleThinThickSmallGap BorderStyle = "thinThickSmallGap" // 细粗细线(小间距) + BorderStyleThickThinSmallGap BorderStyle = "thickThinSmallGap" // 粗细粗线(小间距) + BorderStyleThinThickThinSmallGap BorderStyle = "thinThickThinSmallGap" // 细粗细线(小间距) + BorderStyleThinThickMediumGap BorderStyle = "thinThickMediumGap" // 细粗细线(中间距) + BorderStyleThickThinMediumGap BorderStyle = "thickThinMediumGap" // 粗细粗线(中间距) + BorderStyleThinThickThinMediumGap BorderStyle = "thinThickThinMediumGap" // 细粗细线(中间距) + BorderStyleThinThickLargeGap BorderStyle = "thinThickLargeGap" // 细粗细线(大间距) + BorderStyleThickThinLargeGap BorderStyle = "thickThinLargeGap" // 粗细粗线(大间距) + BorderStyleThinThickThinLargeGap BorderStyle = "thinThickThinLargeGap" // 细粗细线(大间距) + BorderStyleWave BorderStyle = "wave" // 波浪线 + BorderStyleDoubleWave BorderStyle = "doubleWave" // 双波浪线 + BorderStyleDashSmallGap BorderStyle = "dashSmallGap" // 虚线(小间距) + BorderStyleDashDotStroked BorderStyle = "dashDotStroked" // 划点线 + BorderStyleThreeDEmboss BorderStyle = "threeDEmboss" // 3D浮雕 + BorderStyleThreeDEngrave BorderStyle = "threeDEngrave" // 3D雕刻 + BorderStyleOutset BorderStyle = "outset" // 外凸 + BorderStyleInset BorderStyle = "inset" // 内凹 +) + +// ShadingPattern 底纹图案常量 +type ShadingPattern string + +const ( + ShadingPatternClear ShadingPattern = "clear" // 透明 + ShadingPatternSolid ShadingPattern = "solid" // 实色 + ShadingPatternPct5 ShadingPattern = "pct5" // 5% + ShadingPatternPct10 ShadingPattern = "pct10" // 10% + ShadingPatternPct20 ShadingPattern = "pct20" // 20% + ShadingPatternPct25 ShadingPattern = "pct25" // 25% + ShadingPatternPct30 ShadingPattern = "pct30" // 30% + ShadingPatternPct40 ShadingPattern = "pct40" // 40% + ShadingPatternPct50 ShadingPattern = "pct50" // 50% + ShadingPatternPct60 ShadingPattern = "pct60" // 60% + ShadingPatternPct70 ShadingPattern = "pct70" // 70% + ShadingPatternPct75 ShadingPattern = "pct75" // 75% + ShadingPatternPct80 ShadingPattern = "pct80" // 80% + ShadingPatternPct90 ShadingPattern = "pct90" // 90% + ShadingPatternHorzStripe ShadingPattern = "horzStripe" // 水平条纹 + ShadingPatternVertStripe ShadingPattern = "vertStripe" // 垂直条纹 + ShadingPatternReverseDiagStripe ShadingPattern = "reverseDiagStripe" // 反对角条纹 + ShadingPatternDiagStripe ShadingPattern = "diagStripe" // 对角条纹 + ShadingPatternHorzCross ShadingPattern = "horzCross" // 水平十字 + ShadingPatternDiagCross ShadingPattern = "diagCross" // 对角十字 +) + +// TableStyleTemplate 表格样式模板 +type TableStyleTemplate string + +const ( + TableStyleTemplateNormal TableStyleTemplate = "TableNormal" // 普通表格 + TableStyleTemplateGrid TableStyleTemplate = "TableGrid" // 网格表格 + TableStyleTemplateList TableStyleTemplate = "TableList" // 列表表格 + TableStyleTemplateColorful1 TableStyleTemplate = "TableColorful1" // 彩色表格1 + TableStyleTemplateColorful2 TableStyleTemplate = "TableColorful2" // 彩色表格2 + TableStyleTemplateColorful3 TableStyleTemplate = "TableColorful3" // 彩色表格3 + TableStyleTemplateColumns1 TableStyleTemplate = "TableColumns1" // 列样式1 + TableStyleTemplateColumns2 TableStyleTemplate = "TableColumns2" // 列样式2 + TableStyleTemplateColumns3 TableStyleTemplate = "TableColumns3" // 列样式3 + TableStyleTemplateRows1 TableStyleTemplate = "TableRows1" // 行样式1 + TableStyleTemplateRows2 TableStyleTemplate = "TableRows2" // 行样式2 + TableStyleTemplateRows3 TableStyleTemplate = "TableRows3" // 行样式3 + TableStyleTemplatePlain1 TableStyleTemplate = "TablePlain1" // 简洁表格1 + TableStyleTemplatePlain2 TableStyleTemplate = "TablePlain2" // 简洁表格2 + TableStyleTemplatePlain3 TableStyleTemplate = "TablePlain3" // 简洁表格3 +) + +// TableStyleConfig 表格样式配置 +type TableStyleConfig struct { + Template TableStyleTemplate // 样式模板 + StyleID string // 自定义样式ID + FirstRowHeader bool // 首行作为标题 + LastRowTotal bool // 最后一行作为总计 + FirstColumnHeader bool // 首列作为标题 + LastColumnTotal bool // 最后一列作为总计 + BandedRows bool // 交替行颜色 + BandedColumns bool // 交替列颜色 +} + +// BorderConfig 边框配置 +type BorderConfig struct { + Style BorderStyle // 边框样式 + Width int // 边框宽度(1/8磅) + Color string // 边框颜色(十六进制,如 "FF0000") + Space int // 边框间距 +} + +// ShadingConfig 底纹配置 +type ShadingConfig struct { + Pattern ShadingPattern // 底纹图案 + ForegroundColor string // 前景色(十六进制) + BackgroundColor string // 背景色(十六进制) +} + +// TableBorderConfig 表格边框配置 +type TableBorderConfig struct { + Top *BorderConfig // 上边框 + Left *BorderConfig // 左边框 + Bottom *BorderConfig // 下边框 + Right *BorderConfig // 右边框 + InsideH *BorderConfig // 内部水平边框 + InsideV *BorderConfig // 内部垂直边框 +} + +// CellBorderConfig 单元格边框配置 +type CellBorderConfig struct { + Top *BorderConfig // 上边框 + Left *BorderConfig // 左边框 + Bottom *BorderConfig // 下边框 + Right *BorderConfig // 右边框 + DiagDown *BorderConfig // 左上到右下对角线 + DiagUp *BorderConfig // 右上到左下对角线 +} + +// ApplyTableStyle 应用表格样式 +func (t *Table) ApplyTableStyle(config *TableStyleConfig) error { + if t.Properties == nil { + t.Properties = &TableProperties{} + } + + // 设置样式模板 + if config.Template != "" { + t.Properties.TableStyle = &TableStyle{ + Val: string(config.Template), + } + } else if config.StyleID != "" { + t.Properties.TableStyle = &TableStyle{ + Val: config.StyleID, + } + } + + // 设置表格外观选项 + if t.Properties.TableLook == nil { + t.Properties.TableLook = &TableLook{} + } + + // 构建TableLook值 + lookVal := "0000" + if config.FirstRowHeader { + t.Properties.TableLook.FirstRow = "1" + lookVal = "0400" + } else { + t.Properties.TableLook.FirstRow = "0" + } + + if config.LastRowTotal { + t.Properties.TableLook.LastRow = "1" + if lookVal == "0400" { + lookVal = "0440" + } else { + lookVal = "0040" + } + } else { + t.Properties.TableLook.LastRow = "0" + } + + if config.FirstColumnHeader { + t.Properties.TableLook.FirstCol = "1" + switch lookVal { + case "0400": + lookVal = "0500" + case "0040": + lookVal = "0140" + case "0440": + lookVal = "0540" + default: + lookVal = "0100" + } + } else { + t.Properties.TableLook.FirstCol = "0" + } + + if config.LastColumnTotal { + t.Properties.TableLook.LastCol = "1" + } else { + t.Properties.TableLook.LastCol = "0" + } + + if config.BandedRows { + t.Properties.TableLook.NoHBand = "0" + } else { + t.Properties.TableLook.NoHBand = "1" + } + + if config.BandedColumns { + t.Properties.TableLook.NoVBand = "0" + } else { + t.Properties.TableLook.NoVBand = "1" + } + + t.Properties.TableLook.Val = lookVal + + Info(fmt.Sprintf("应用表格样式成功:%s", config.Template)) + return nil +} + +// SetTableBorders 设置表格边框 +func (t *Table) SetTableBorders(config *TableBorderConfig) error { + if t.Properties == nil { + t.Properties = &TableProperties{} + } + + t.Properties.TableBorders = &TableBorders{} + + if config.Top != nil { + t.Properties.TableBorders.Top = createTableBorder(config.Top) + } + if config.Left != nil { + t.Properties.TableBorders.Left = createTableBorder(config.Left) + } + if config.Bottom != nil { + t.Properties.TableBorders.Bottom = createTableBorder(config.Bottom) + } + if config.Right != nil { + t.Properties.TableBorders.Right = createTableBorder(config.Right) + } + if config.InsideH != nil { + t.Properties.TableBorders.InsideH = createTableBorder(config.InsideH) + } + if config.InsideV != nil { + t.Properties.TableBorders.InsideV = createTableBorder(config.InsideV) + } + + Info("设置表格边框成功") + return nil +} + +// SetTableShading 设置表格背景 +func (t *Table) SetTableShading(config *ShadingConfig) error { + if t.Properties == nil { + t.Properties = &TableProperties{} + } + + t.Properties.Shd = &TableShading{ + Val: string(config.Pattern), + Color: config.ForegroundColor, + Fill: config.BackgroundColor, + } + + Info("设置表格背景成功") + return nil +} + +// SetCellBorders 设置单元格边框 +func (t *Table) SetCellBorders(row, col int, config *CellBorderConfig) error { + cell, err := t.GetCell(row, col) + if err != nil { + return err + } + + if cell.Properties == nil { + cell.Properties = &TableCellProperties{} + } + + cell.Properties.TcBorders = &TableCellBorders{} + + if config.Top != nil { + cell.Properties.TcBorders.Top = createTableCellBorder(config.Top) + } + if config.Left != nil { + cell.Properties.TcBorders.Left = createTableCellBorder(config.Left) + } + if config.Bottom != nil { + cell.Properties.TcBorders.Bottom = createTableCellBorder(config.Bottom) + } + if config.Right != nil { + cell.Properties.TcBorders.Right = createTableCellBorder(config.Right) + } + if config.DiagDown != nil { + cell.Properties.TcBorders.TL2BR = createTableCellBorder(config.DiagDown) + } + if config.DiagUp != nil { + cell.Properties.TcBorders.TR2BL = createTableCellBorder(config.DiagUp) + } + + Info(fmt.Sprintf("设置单元格(%d,%d)边框成功", row, col)) + return nil +} + +// SetCellShading 设置单元格背景 +func (t *Table) SetCellShading(row, col int, config *ShadingConfig) error { + cell, err := t.GetCell(row, col) + if err != nil { + return err + } + + if cell.Properties == nil { + cell.Properties = &TableCellProperties{} + } + + cell.Properties.Shd = &TableCellShading{ + Val: string(config.Pattern), + Color: config.ForegroundColor, + Fill: config.BackgroundColor, + } + + Info(fmt.Sprintf("设置单元格(%d,%d)背景成功", row, col)) + return nil +} + +// SetAlternatingRowColors 设置奇偶行颜色交替 +func (t *Table) SetAlternatingRowColors(evenRowColor, oddRowColor string) error { + for i := range t.Rows { + var bgColor string + if i%2 == 0 { + bgColor = evenRowColor + } else { + bgColor = oddRowColor + } + + // 为该行的所有单元格设置背景色 + for j := range t.Rows[i].Cells { + err := t.SetCellShading(i, j, &ShadingConfig{ + Pattern: ShadingPatternSolid, + BackgroundColor: bgColor, + }) + if err != nil { + return fmt.Errorf("设置第%d行第%d列背景色失败: %v", i, j, err) + } + } + } + + Info("设置奇偶行颜色交替成功") + return nil +} + +// RemoveTableBorders 移除表格边框 +func (t *Table) RemoveTableBorders() error { + if t.Properties == nil { + t.Properties = &TableProperties{} + } + + // 设置所有边框为无 + noBorderConfig := &BorderConfig{ + Style: BorderStyleNone, + Width: 0, + Color: "auto", + Space: 0, + } + + borderConfig := &TableBorderConfig{ + Top: noBorderConfig, + Left: noBorderConfig, + Bottom: noBorderConfig, + Right: noBorderConfig, + InsideH: noBorderConfig, + InsideV: noBorderConfig, + } + + return t.SetTableBorders(borderConfig) +} + +// RemoveCellBorders 移除单元格边框 +func (t *Table) RemoveCellBorders(row, col int) error { + noBorderConfig := &BorderConfig{ + Style: BorderStyleNone, + Width: 0, + Color: "auto", + Space: 0, + } + + cellBorderConfig := &CellBorderConfig{ + Top: noBorderConfig, + Left: noBorderConfig, + Bottom: noBorderConfig, + Right: noBorderConfig, + } + + return t.SetCellBorders(row, col, cellBorderConfig) +} + +// CreateCustomTableStyle 创建自定义表格样式 +func (t *Table) CreateCustomTableStyle(styleID, styleName string, + borderConfig *TableBorderConfig, + shadingConfig *ShadingConfig, + firstRowBold bool) error { + + // 应用样式到表格 + config := &TableStyleConfig{ + StyleID: styleID, + FirstRowHeader: firstRowBold, + BandedRows: shadingConfig != nil, + } + + err := t.ApplyTableStyle(config) + if err != nil { + return err + } + + // 设置边框 + if borderConfig != nil { + err = t.SetTableBorders(borderConfig) + if err != nil { + return err + } + } + + // 设置背景 + if shadingConfig != nil { + err = t.SetTableShading(shadingConfig) + if err != nil { + return err + } + } + + Info(fmt.Sprintf("创建自定义表格样式成功:%s", styleID)) + return nil +} + +// 辅助函数:创建表格边框 +func createTableBorder(config *BorderConfig) *TableBorder { + return &TableBorder{ + Val: string(config.Style), + Sz: fmt.Sprintf("%d", config.Width), + Space: fmt.Sprintf("%d", config.Space), + Color: config.Color, + } +} + +// 辅助函数:创建单元格边框 +func createTableCellBorder(config *BorderConfig) *TableCellBorder { + return &TableCellBorder{ + Val: string(config.Style), + Sz: fmt.Sprintf("%d", config.Width), + Space: fmt.Sprintf("%d", config.Space), + Color: config.Color, + } +} diff --git a/pkg/document/table_test.go b/pkg/document/table_test.go index 219e76d..6cb2fe8 100644 --- a/pkg/document/table_test.go +++ b/pkg/document/table_test.go @@ -100,15 +100,15 @@ func TestAddTable(t *testing.T) { Width: 6000, } - initialTableCount := len(doc.Body.Tables) + initialTableCount := len(doc.Body.GetTables()) table := doc.AddTable(config) if table == nil { t.Fatal("添加表格失败") } - if len(doc.Body.Tables) != initialTableCount+1 { - t.Errorf("期望表格数量%d,实际%d", initialTableCount+1, len(doc.Body.Tables)) + if len(doc.Body.GetTables()) != initialTableCount+1 { + t.Errorf("期望表格数量%d,实际%d", initialTableCount+1, len(doc.Body.GetTables())) } } @@ -1221,3 +1221,292 @@ func TestTextDirectionConstants(t *testing.T) { } } } + +// TestRowHeight 测试行高设置功能 +func TestRowHeight(t *testing.T) { + doc := New() + + config := &TableConfig{ + Rows: 3, + Cols: 2, + Width: 4000, + } + + table := doc.CreateTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置固定行高 + heightConfig := &RowHeightConfig{ + Height: 30, + Rule: RowHeightExact, + } + + err := table.SetRowHeight(0, heightConfig) + if err != nil { + t.Errorf("设置行高失败: %v", err) + } + + // 测试获取行高 + retrievedConfig, err := table.GetRowHeight(0) + if err != nil { + t.Errorf("获取行高失败: %v", err) + } + + if retrievedConfig.Height != 30 { + t.Errorf("期望行高30,实际%d", retrievedConfig.Height) + } + + if retrievedConfig.Rule != RowHeightExact { + t.Errorf("期望行高规则%s,实际%s", RowHeightExact, retrievedConfig.Rule) + } + + // 测试批量设置行高 + batchConfig := &RowHeightConfig{ + Height: 25, + Rule: RowHeightMinimum, + } + + err = table.SetRowHeightRange(1, 2, batchConfig) + if err != nil { + t.Errorf("批量设置行高失败: %v", err) + } + + // 验证批量设置结果 + for i := 1; i <= 2; i++ { + config, err := table.GetRowHeight(i) + if err != nil { + t.Errorf("获取第%d行高度失败: %v", i, err) + } + if config.Height != 25 { + t.Errorf("第%d行期望高度25,实际%d", i, config.Height) + } + if config.Rule != RowHeightMinimum { + t.Errorf("第%d行期望规则%s,实际%s", i, RowHeightMinimum, config.Rule) + } + } + + // 测试无效索引 + err = table.SetRowHeight(10, heightConfig) + if err == nil { + t.Error("期望设置无效行索引失败,但成功了") + } + + _, err = table.GetRowHeight(10) + if err == nil { + t.Error("期望获取无效行索引失败,但成功了") + } +} + +// TestTableLayout 测试表格布局和定位功能 +func TestTableLayout(t *testing.T) { + doc := New() + + config := &TableConfig{ + Rows: 2, + Cols: 2, + Width: 4000, + } + + table := doc.CreateTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置表格布局 + layoutConfig := &TableLayoutConfig{ + Alignment: TableAlignCenter, + TextWrap: TextWrapNone, + Position: PositionInline, + } + + err := table.SetTableLayout(layoutConfig) + if err != nil { + t.Errorf("设置表格布局失败: %v", err) + } + + // 测试获取表格布局 + retrievedLayout := table.GetTableLayout() + if retrievedLayout.Alignment != TableAlignCenter { + t.Errorf("期望对齐方式%s,实际%s", TableAlignCenter, retrievedLayout.Alignment) + } + + // 测试快捷方法设置对齐 + err = table.SetTableAlignment(TableAlignRight) + if err != nil { + t.Errorf("设置表格对齐失败: %v", err) + } + + retrievedLayout = table.GetTableLayout() + if retrievedLayout.Alignment != TableAlignRight { + t.Errorf("期望对齐方式%s,实际%s", TableAlignRight, retrievedLayout.Alignment) + } +} + +// TestTablePageBreak 测试表格分页控制功能 +func TestTablePageBreak(t *testing.T) { + doc := New() + + config := &TableConfig{ + Rows: 4, + Cols: 2, + Width: 4000, + } + + table := doc.CreateTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置行禁止跨页分割 + err := table.SetRowKeepTogether(0, true) + if err != nil { + t.Errorf("设置行禁止跨页分割失败: %v", err) + } + + // 测试检查行是否禁止跨页分割 + keepTogether, err := table.IsRowKeepTogether(0) + if err != nil { + t.Errorf("检查行跨页分割设置失败: %v", err) + } + if !keepTogether { + t.Error("期望行禁止跨页分割为true,实际为false") + } + + // 测试设置标题行 + err = table.SetRowAsHeader(0, true) + if err != nil { + t.Errorf("设置标题行失败: %v", err) + } + + // 测试检查是否为标题行 + isHeader, err := table.IsRowHeader(0) + if err != nil { + t.Errorf("检查标题行设置失败: %v", err) + } + if !isHeader { + t.Error("期望第0行为标题行,实际不是") + } + + // 测试设置标题行范围 + err = table.SetHeaderRows(0, 1) + if err != nil { + t.Errorf("设置标题行范围失败: %v", err) + } + + // 验证标题行范围设置 + for i := 0; i <= 1; i++ { + isHeader, err := table.IsRowHeader(i) + if err != nil { + t.Errorf("检查第%d行标题行设置失败: %v", i, err) + } + if !isHeader { + t.Errorf("期望第%d行为标题行,实际不是", i) + } + } + + // 测试表格分页信息 + breakInfo := table.GetTableBreakInfo() + if breakInfo["total_rows"] != 4 { + t.Errorf("期望总行数4,实际%v", breakInfo["total_rows"]) + } + if breakInfo["header_rows"] != 2 { + t.Errorf("期望标题行数2,实际%v", breakInfo["header_rows"]) + } + + // 测试表格分页配置 + pageBreakConfig := &TablePageBreakConfig{ + KeepWithNext: true, + KeepLines: true, + PageBreakBefore: false, + WidowControl: true, + } + + err = table.SetTablePageBreak(pageBreakConfig) + if err != nil { + t.Errorf("设置表格分页配置失败: %v", err) + } + + // 测试行与下一行保持在同一页 + err = table.SetRowKeepWithNext(1, true) + if err != nil { + t.Errorf("设置行与下一行保持在同一页失败: %v", err) + } + + // 测试无效索引 + err = table.SetRowKeepTogether(10, true) + if err == nil { + t.Error("期望设置无效行索引失败,但成功了") + } + + err = table.SetRowAsHeader(10, true) + if err == nil { + t.Error("期望设置无效行索引失败,但成功了") + } + + _, err = table.IsRowHeader(10) + if err == nil { + t.Error("期望检查无效行索引失败,但成功了") + } + + _, err = table.IsRowKeepTogether(10) + if err == nil { + t.Error("期望检查无效行索引失败,但成功了") + } +} + +// TestRowHeightConstants 测试行高规则常量 +func TestRowHeightConstants(t *testing.T) { + // 验证行高规则常量定义正确 + if RowHeightAuto != "auto" { + t.Errorf("期望RowHeightAuto为'auto',实际'%s'", RowHeightAuto) + } + if RowHeightMinimum != "atLeast" { + t.Errorf("期望RowHeightMinimum为'atLeast',实际'%s'", RowHeightMinimum) + } + if RowHeightExact != "exact" { + t.Errorf("期望RowHeightExact为'exact',实际'%s'", RowHeightExact) + } +} + +// TestTableAlignmentConstants 测试表格对齐常量 +func TestTableAlignmentConstants(t *testing.T) { + // 验证表格对齐常量定义正确 + if TableAlignLeft != "left" { + t.Errorf("期望TableAlignLeft为'left',实际'%s'", TableAlignLeft) + } + if TableAlignCenter != "center" { + t.Errorf("期望TableAlignCenter为'center',实际'%s'", TableAlignCenter) + } + if TableAlignRight != "right" { + t.Errorf("期望TableAlignRight为'right',实际'%s'", TableAlignRight) + } +} + +// TestTableRowPropertiesExtensions 测试TableRowProperties扩展方法 +func TestTableRowPropertiesExtensions(t *testing.T) { + trp := &TableRowProperties{} + + // 测试SetCantSplit方法 + trp.SetCantSplit(true) + if trp.CantSplit == nil || trp.CantSplit.Val != "1" { + t.Error("设置CantSplit失败") + } + + trp.SetCantSplit(false) + if trp.CantSplit != nil { + t.Error("清除CantSplit失败") + } + + // 测试SetTblHeader方法 + trp.SetTblHeader(true) + if trp.TblHeader == nil || trp.TblHeader.Val != "1" { + t.Error("设置TblHeader失败") + } + + trp.SetTblHeader(false) + if trp.TblHeader != nil { + t.Error("清除TblHeader失败") + } +} diff --git a/test/document_test.go b/test/document_test.go index 0b90a50..5f6dee3 100644 --- a/test/document_test.go +++ b/test/document_test.go @@ -50,11 +50,12 @@ func TestOpenDocument(t *testing.T) { } // 验证内容 - if len(openedDoc.Body.Paragraphs) != 1 { - t.Fatalf("Expected 1 paragraph, got %d", len(openedDoc.Body.Paragraphs)) + paragraphs := openedDoc.Body.GetParagraphs() + if len(paragraphs) != 1 { + t.Fatalf("Expected 1 paragraph, got %d", len(paragraphs)) } - if openedDoc.Body.Paragraphs[0].Runs[0].Text.Content != "Test paragraph" { + if paragraphs[0].Runs[0].Text.Content != "Test paragraph" { t.Fatalf("Paragraph content mismatch") } diff --git a/test/table_style_test.go b/test/table_style_test.go new file mode 100644 index 0000000..7b89481 --- /dev/null +++ b/test/table_style_test.go @@ -0,0 +1,779 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/ZeroHawkeye/wordZero/pkg/document" +) + +// TestTableBorders 测试表格边框功能 +func TestTableBorders(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 3, + Cols: 3, + Width: 5000, + Data: [][]string{ + {"姓名", "年龄", "职业"}, + {"张三", "25", "工程师"}, + {"李四", "30", "设计师"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置表格边框 + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 12, + Color: "FF0000", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "0000FF", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 6, + Color: "00FF00", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleDashed, + Width: 6, + Color: "FF00FF", + Space: 0, + }, + InsideH: &document.BorderConfig{ + Style: document.BorderStyleDotted, + Width: 4, + Color: "808080", + Space: 0, + }, + InsideV: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "808080", + Space: 0, + }, + } + + err := table.SetTableBorders(borderConfig) + if err != nil { + t.Fatalf("设置表格边框失败: %v", err) + } + + t.Log("表格边框设置成功") +} + +// TestCellBorders 测试单元格边框功能 +func TestCellBorders(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 2, + Cols: 2, + Width: 3000, + Data: [][]string{ + {"A1", "B1"}, + {"A2", "B2"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置单元格边框 + cellBorderConfig := &document.CellBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 8, + Color: "FF0000", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "0000FF", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 6, + Color: "00FF00", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleDashed, + Width: 4, + Color: "FF00FF", + Space: 0, + }, + DiagDown: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 2, + Color: "FFFF00", + Space: 0, + }, + } + + err := table.SetCellBorders(0, 0, cellBorderConfig) + if err != nil { + t.Fatalf("设置单元格边框失败: %v", err) + } + + t.Log("单元格边框设置成功") +} + +// TestTableShading 测试表格背景功能 +func TestTableShading(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 3, + Cols: 2, + Width: 4000, + Data: [][]string{ + {"产品", "价格"}, + {"产品A", "100"}, + {"产品B", "200"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置表格背景 + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternPct25, + ForegroundColor: "000000", + BackgroundColor: "E0E0E0", + } + + err := table.SetTableShading(shadingConfig) + if err != nil { + t.Fatalf("设置表格背景失败: %v", err) + } + + t.Log("表格背景设置成功") +} + +// TestCellShading 测试单元格背景功能 +func TestCellShading(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 3, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"A", "B", "C"}, + {"1", "2", "3"}, + {"X", "Y", "Z"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置单元格背景 + testCases := []struct { + row int + col int + backgroundColor string + pattern document.ShadingPattern + }{ + {0, 0, "FF0000", document.ShadingPatternSolid}, // 红色实色 + {0, 1, "00FF00", document.ShadingPatternPct50}, // 绿色50% + {0, 2, "0000FF", document.ShadingPatternPct25}, // 蓝色25% + {1, 0, "FFFF00", document.ShadingPatternSolid}, // 黄色实色 + {1, 1, "FF00FF", document.ShadingPatternPct75}, // 紫色75% + {1, 2, "00FFFF", document.ShadingPatternPct10}, // 青色10% + } + + for _, tc := range testCases { + shadingConfig := &document.ShadingConfig{ + Pattern: tc.pattern, + BackgroundColor: tc.backgroundColor, + } + + err := table.SetCellShading(tc.row, tc.col, shadingConfig) + if err != nil { + t.Fatalf("设置单元格(%d,%d)背景失败: %v", tc.row, tc.col, err) + } + } + + t.Log("单元格背景设置成功") +} + +// TestAlternatingRowColors 测试奇偶行颜色交替 +func TestAlternatingRowColors(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 5, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"序号", "姓名", "分数"}, + {"1", "张三", "85"}, + {"2", "李四", "92"}, + {"3", "王五", "78"}, + {"4", "赵六", "95"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试设置奇偶行颜色交替 + err := table.SetAlternatingRowColors("F0F0F0", "FFFFFF") + if err != nil { + t.Fatalf("设置奇偶行颜色交替失败: %v", err) + } + + t.Log("奇偶行颜色交替设置成功") +} + +// TestTableStyleTemplates 测试表格样式模板 +func TestTableStyleTemplates(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 4, + Cols: 3, + Width: 5000, + Data: [][]string{ + {"项目", "预算", "实际"}, + {"开发", "10000", "9500"}, + {"测试", "5000", "5200"}, + {"总计", "15000", "14700"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试应用表格样式模板 + styleConfig := &document.TableStyleConfig{ + Template: document.TableStyleTemplateGrid, + FirstRowHeader: true, + LastRowTotal: true, + BandedRows: true, + BandedColumns: false, + } + + err := table.ApplyTableStyle(styleConfig) + if err != nil { + t.Fatalf("应用表格样式模板失败: %v", err) + } + + t.Log("表格样式模板应用成功") +} + +// TestCustomTableStyle 测试自定义表格样式 +func TestCustomTableStyle(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 3, + Cols: 3, + Width: 4500, + Data: [][]string{ + {"功能", "状态", "备注"}, + {"登录", "完成", "已测试"}, + {"注册", "开发中", "50%"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 创建自定义边框配置 + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 12, + Color: "2E75B6", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "2E75B6", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleThick, + Width: 12, + Color: "2E75B6", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "2E75B6", + Space: 0, + }, + InsideH: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "D0D0D0", + Space: 0, + }, + InsideV: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "D0D0D0", + Space: 0, + }, + } + + // 创建自定义背景配置 + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternPct10, + BackgroundColor: "E7F3FF", + } + + // 测试创建自定义表格样式 + err := table.CreateCustomTableStyle("CustomStyle1", "自定义样式1", borderConfig, shadingConfig, true) + if err != nil { + t.Fatalf("创建自定义表格样式失败: %v", err) + } + + t.Log("自定义表格样式创建成功") +} + +// TestRemoveBorders 测试移除边框功能 +func TestRemoveBorders(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 2, + Cols: 2, + Width: 3000, + Data: [][]string{ + {"A", "B"}, + {"C", "D"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 先设置边框 + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + } + + err := table.SetTableBorders(borderConfig) + if err != nil { + t.Fatalf("设置表格边框失败: %v", err) + } + + // 测试移除表格边框 + err = table.RemoveTableBorders() + if err != nil { + t.Fatalf("移除表格边框失败: %v", err) + } + + // 测试移除单元格边框 + err = table.RemoveCellBorders(0, 0) + if err != nil { + t.Fatalf("移除单元格边框失败: %v", err) + } + + t.Log("移除边框功能测试成功") +} + +// TestComplexTableStyle 测试复杂表格样式组合 +func TestComplexTableStyle(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 5, + Cols: 4, + Width: 6000, + Data: [][]string{ + {"部门", "Q1", "Q2", "Q3"}, + {"销售部", "120", "135", "150"}, + {"技术部", "80", "90", "95"}, + {"市场部", "60", "70", "85"}, + {"总计", "260", "295", "330"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 应用样式模板 + styleConfig := &document.TableStyleConfig{ + Template: document.TableStyleTemplateColorful1, + FirstRowHeader: true, + LastRowTotal: true, + FirstColumnHeader: true, + BandedRows: true, + } + + err := table.ApplyTableStyle(styleConfig) + if err != nil { + t.Fatalf("应用表格样式失败: %v", err) + } + + // 设置自定义边框 + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 8, + Color: "2E75B6", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleDouble, + Width: 8, + Color: "2E75B6", + Space: 0, + }, + InsideH: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "B0B0B0", + Space: 0, + }, + InsideV: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "B0B0B0", + Space: 0, + }, + } + + err = table.SetTableBorders(borderConfig) + if err != nil { + t.Fatalf("设置表格边框失败: %v", err) + } + + // 为标题行设置特殊背景 + for j := 0; j < 4; j++ { + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternSolid, + BackgroundColor: "2E75B6", + ForegroundColor: "FFFFFF", + } + + err = table.SetCellShading(0, j, shadingConfig) + if err != nil { + t.Fatalf("设置标题行背景失败: %v", err) + } + } + + // 为总计行设置特殊背景 + for j := 0; j < 4; j++ { + shadingConfig := &document.ShadingConfig{ + Pattern: document.ShadingPatternSolid, + BackgroundColor: "FFFF99", + } + + err = table.SetCellShading(4, j, shadingConfig) + if err != nil { + t.Fatalf("设置总计行背景失败: %v", err) + } + } + + t.Log("复杂表格样式组合测试成功") +} + +// BenchmarkTableStyleOperations 表格样式操作性能测试 +func BenchmarkTableStyleOperations(b *testing.B) { + // 创建文档 + doc := document.New() + if doc == nil { + b.Fatal("创建文档失败") + } + + // 创建表格 + config := &document.TableConfig{ + Rows: 10, + Cols: 5, + Width: 7500, + } + + table := doc.AddTable(config) + if table == nil { + b.Fatal("创建表格失败") + } + + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 6, + Color: "000000", + Space: 0, + }, + InsideH: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "C0C0C0", + Space: 0, + }, + InsideV: &document.BorderConfig{ + Style: document.BorderStyleSingle, + Width: 4, + Color: "C0C0C0", + Space: 0, + }, + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + err := table.SetTableBorders(borderConfig) + if err != nil { + b.Fatalf("设置表格边框失败: %v", err) + } + } +} + +// TestBorderStyles 测试各种边框样式 +func TestBorderStyles(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 测试边框样式 + borderStyles := []document.BorderStyle{ + document.BorderStyleNone, + document.BorderStyleSingle, + document.BorderStyleThick, + document.BorderStyleDouble, + document.BorderStyleDotted, + document.BorderStyleDashed, + document.BorderStyleDotDash, + document.BorderStyleWave, + } + + for i, style := range borderStyles { + // 为每种样式创建一个表格 + config := &document.TableConfig{ + Rows: 2, + Cols: 2, + Width: 2000, + Data: [][]string{ + {fmt.Sprintf("样式%d", i+1), string(style)}, + {"测试", "数据"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatalf("创建表格%d失败", i+1) + } + + borderConfig := &document.TableBorderConfig{ + Top: &document.BorderConfig{ + Style: style, + Width: 6, + Color: "000000", + Space: 0, + }, + Left: &document.BorderConfig{ + Style: style, + Width: 6, + Color: "000000", + Space: 0, + }, + Bottom: &document.BorderConfig{ + Style: style, + Width: 6, + Color: "000000", + Space: 0, + }, + Right: &document.BorderConfig{ + Style: style, + Width: 6, + Color: "000000", + Space: 0, + }, + } + + err := table.SetTableBorders(borderConfig) + if err != nil { + t.Fatalf("设置表格%d边框失败: %v", i+1, err) + } + } + + t.Log("边框样式测试完成") +} + +// TestShadingPatterns 测试各种底纹图案 +func TestShadingPatterns(t *testing.T) { + // 创建文档 + doc := document.New() + if doc == nil { + t.Fatal("创建文档失败") + } + + // 测试底纹图案 + patterns := []document.ShadingPattern{ + document.ShadingPatternClear, + document.ShadingPatternSolid, + document.ShadingPatternPct25, + document.ShadingPatternPct50, + document.ShadingPatternPct75, + document.ShadingPatternHorzStripe, + document.ShadingPatternVertStripe, + document.ShadingPatternDiagStripe, + } + + // 创建表格 + config := &document.TableConfig{ + Rows: len(patterns), + Cols: 2, + Width: 3000, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + for i, pattern := range patterns { + // 设置单元格内容 + err := table.SetCellText(i, 0, fmt.Sprintf("图案%d", i+1)) + if err != nil { + t.Fatalf("设置单元格文本失败: %v", err) + } + + err = table.SetCellText(i, 1, string(pattern)) + if err != nil { + t.Fatalf("设置单元格文本失败: %v", err) + } + + // 设置单元格背景 + shadingConfig := &document.ShadingConfig{ + Pattern: pattern, + BackgroundColor: "C0C0C0", + ForegroundColor: "000000", + } + + err = table.SetCellShading(i, 1, shadingConfig) + if err != nil { + t.Fatalf("设置单元格背景失败: %v", err) + } + } + + t.Log("底纹图案测试完成") +} diff --git a/test/text_formatting_test.go b/test/text_formatting_test.go index 80d5930..8fc3d58 100644 --- a/test/text_formatting_test.go +++ b/test/text_formatting_test.go @@ -30,15 +30,16 @@ func TestTextFormatting(t *testing.T) { } // 检查段落是否被正确添加 - if len(doc.Body.Paragraphs) != 1 { - t.Errorf("预期1个段落,但得到了 %d 个", len(doc.Body.Paragraphs)) + paragraphs := doc.Body.GetParagraphs() + if len(paragraphs) != 1 { + t.Errorf("预期1个段落,但得到了 %d 个", len(paragraphs)) } // 检查运行属性 - if len(doc.Body.Paragraphs[0].Runs) == 0 { + if len(paragraphs[0].Runs) == 0 { t.Error("段落中没有运行") } else { - run := doc.Body.Paragraphs[0].Runs[0] + run := paragraphs[0].Runs[0] if run.Properties == nil { t.Error("运行属性为空") } else { @@ -199,12 +200,13 @@ func TestDocumentSaveAndOpen(t *testing.T) { } // 验证内容 - if len(openedDoc.Body.Paragraphs) != 1 { - t.Errorf("预期1个段落,但得到了 %d 个", len(openedDoc.Body.Paragraphs)) + paragraphs := openedDoc.Body.GetParagraphs() + if len(paragraphs) != 1 { + t.Errorf("预期1个段落,但得到了 %d 个", len(paragraphs)) } - if len(openedDoc.Body.Paragraphs) > 0 { - para := openedDoc.Body.Paragraphs[0] + if len(paragraphs) > 0 { + para := paragraphs[0] // 检查对齐方式 if para.Properties == nil || para.Properties.Justification == nil { diff --git a/test_runner.go b/test_runner.go new file mode 100644 index 0000000..b5a907f --- /dev/null +++ b/test_runner.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + "os/exec" +) + +func main() { + fmt.Println("Running tests for WordZero project...") + + // 运行pkg包的测试 + fmt.Println("\n=== Testing pkg packages ===") + cmd := exec.Command("go", "test", "./pkg/...", "-v") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Printf("pkg tests failed: %v\n", err) + } else { + fmt.Println("pkg tests passed") + } + + // 运行test包的测试 + fmt.Println("\n=== Testing test package ===") + cmd = exec.Command("go", "test", "./test", "-v") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + fmt.Printf("test package failed: %v\n", err) + } else { + fmt.Println("test package passed") + } + + // 运行所有测试 + fmt.Println("\n=== Running all tests ===") + cmd = exec.Command("go", "test", "./...", "-cover") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + fmt.Printf("Overall tests failed: %v\n", err) + os.Exit(1) + } else { + fmt.Println("All tests passed!") + } +}