mirror of
https://github.com/ZeroHawkeye/wordZero.git
synced 2025-09-26 20:01:17 +08:00
- 增加基准测试
- 修复模板继承问题 - 优化wiki文档
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -46,4 +46,10 @@ Thumbs.db
|
|||||||
# 构建输出
|
# 构建输出
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
|
benchmark/results/golang
|
||||||
|
benchmark/results/javascript
|
||||||
|
benchmark/results/python
|
||||||
|
benchmark/python/venv
|
||||||
|
benchmark/javascript/node_modules
|
||||||
|
benchmark/javascript/package-lock.json
|
499
README.md
499
README.md
@@ -3,6 +3,8 @@
|
|||||||
[](https://golang.org)
|
[](https://golang.org)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](#测试)
|
[](#测试)
|
||||||
|
[](wordZero.wiki/13-性能基准测试)
|
||||||
|
[](wordZero.wiki/13-性能基准测试)
|
||||||
[](https://deepwiki.com/ZeroHawkeye/wordZero)
|
[](https://deepwiki.com/ZeroHawkeye/wordZero)
|
||||||
|
|
||||||
## 项目介绍
|
## 项目介绍
|
||||||
@@ -16,7 +18,11 @@ WordZero 是一个使用 Golang 实现的 Word 文档操作库,提供基础的
|
|||||||
- 📝 **文本格式化**: 字体、大小、颜色、粗体、斜体等完整支持
|
- 📝 **文本格式化**: 字体、大小、颜色、粗体、斜体等完整支持
|
||||||
- 📐 **段落格式**: 对齐、间距、缩进等段落属性设置
|
- 📐 **段落格式**: 对齐、间距、缩进等段落属性设置
|
||||||
- 🏷️ **标题导航**: 完整支持Heading1-9样式,可被Word导航窗格识别
|
- 🏷️ **标题导航**: 完整支持Heading1-9样式,可被Word导航窗格识别
|
||||||
- ⚡ **高性能**: 零依赖的纯Go实现,内存占用低
|
- 📊 **表格功能**: 完整的表格创建、编辑、样式设置和迭代器支持
|
||||||
|
- 📄 **页面设置**: 页面尺寸、边距、页眉页脚等专业排版功能
|
||||||
|
- 🔧 **高级功能**: 目录生成、脚注尾注、列表编号、模板引擎等
|
||||||
|
- 🎯 **模板继承**: 支持基础模板和块重写机制,实现模板复用和扩展
|
||||||
|
- ⚡ **卓越性能**: 零依赖的纯Go实现,平均2.62ms处理速度,比JavaScript快3.7倍,比Python快21倍
|
||||||
- 🔧 **易于使用**: 简洁的API设计,链式调用支持
|
- 🔧 **易于使用**: 简洁的API设计,链式调用支持
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
@@ -37,353 +43,111 @@ go get github.com/ZeroHawkeye/wordZero@latest
|
|||||||
go get github.com/ZeroHawkeye/wordZero@v0.4.0
|
go get github.com/ZeroHawkeye/wordZero@v0.4.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## 项目结构
|
## 快速开始
|
||||||
|
|
||||||
```
|
```go
|
||||||
wordZero/
|
package main
|
||||||
├── pkg/ # 公共包
|
|
||||||
│ ├── document/ # 文档核心操作
|
import (
|
||||||
│ │ ├── document.go # 主要文档操作API
|
"log"
|
||||||
│ │ ├── table.go # 表格操作功能
|
"github.com/ZeroHawkeye/wordZero/pkg/document"
|
||||||
│ │ ├── page.go # 页面设置功能 ✨ 新增
|
"github.com/ZeroHawkeye/wordZero/pkg/style"
|
||||||
│ │ ├── header_footer.go # 页眉页脚功能 ✨ 新增
|
)
|
||||||
│ │ ├── toc.go # 目录生成功能 ✨ 新增
|
|
||||||
│ │ ├── footnotes.go # 脚注尾注功能 ✨ 新增
|
func main() {
|
||||||
│ │ ├── numbering.go # 列表编号功能 ✨ 新增
|
// 创建新文档
|
||||||
│ │ ├── sdt.go # 结构化文档标签 ✨ 新增
|
doc := document.New()
|
||||||
│ │ ├── field.go # 域字段功能 ✨ 新增
|
|
||||||
│ │ ├── properties.go # 文档属性管理 ✨ 新增
|
// 添加标题
|
||||||
│ │ ├── template.go # 模板功能 ✨ 新增
|
titlePara := doc.AddParagraph("WordZero 使用示例")
|
||||||
│ │ ├── errors.go # 错误定义和处理
|
titlePara.SetStyle(style.StyleHeading1)
|
||||||
│ │ ├── logger.go # 日志系统
|
|
||||||
│ │ ├── doc.go # 包文档
|
// 添加正文段落
|
||||||
│ │ ├── document_test.go # 文档操作单元测试
|
para := doc.AddParagraph("这是一个使用 WordZero 创建的文档示例。")
|
||||||
│ │ ├── table_test.go # 表格功能单元测试
|
para.SetFontFamily("宋体")
|
||||||
│ │ └── page_test.go # 页面设置单元测试 ✨ 新增
|
para.SetFontSize(12)
|
||||||
│ └── style/ # 样式管理系统
|
para.SetColor("333333")
|
||||||
│ ├── style.go # 样式核心定义
|
|
||||||
│ ├── api.go # 快速API接口
|
// 创建表格
|
||||||
│ ├── predefined.go # 预定义样式常量
|
tableConfig := &document.TableConfig{
|
||||||
│ ├── api_test.go # API测试
|
Rows: 3,
|
||||||
│ ├── style_test.go # 样式系统测试
|
Columns: 3,
|
||||||
│ └── README.md # 样式系统详细文档
|
}
|
||||||
├── examples/ # 使用示例
|
table := doc.AddTable(tableConfig)
|
||||||
│ ├── basic/ # 基础功能示例
|
table.SetCellText(0, 0, "表头1")
|
||||||
│ │ └── basic_example.go
|
table.SetCellText(0, 1, "表头2")
|
||||||
│ ├── formatting/ # 格式化示例
|
table.SetCellText(0, 2, "表头3")
|
||||||
│ │ └── text_formatting_example.go
|
|
||||||
│ ├── style_demo/ # 样式系统演示
|
// 保存文档
|
||||||
│ │ └── style_demo.go
|
if err := doc.Save("example.docx"); err != nil {
|
||||||
│ ├── table/ # 表格功能示例
|
log.Fatal(err)
|
||||||
│ │ └── table_example.go
|
}
|
||||||
│ ├── table_layout/ # 表格布局和尺寸示例
|
}
|
||||||
│ │ └── main.go
|
|
||||||
│ ├── table_style/ # 表格样式和外观示例
|
|
||||||
│ │ └── main.go
|
|
||||||
│ ├── cell_advanced/ # 单元格高级功能示例
|
|
||||||
│ │ └── main.go
|
|
||||||
│ ├── page_settings/ # 页面设置示例 ✨ 新增
|
|
||||||
│ │ └── main.go
|
|
||||||
│ ├── advanced_features/ # 高级功能综合示例 ✨ 新增
|
|
||||||
│ │ └── main.go
|
|
||||||
│ ├── template_demo/ # 模板功能演示 ✨ 规划中
|
|
||||||
│ │ └── main.go
|
|
||||||
│ ├── basic_usage.go # 基础使用示例
|
|
||||||
│ └── output/ # 示例输出文件目录
|
|
||||||
├── test/ # 集成测试文件
|
|
||||||
│ ├── document_test.go # 文档操作集成测试
|
|
||||||
│ ├── text_formatting_test.go # 文本格式化集成测试
|
|
||||||
│ ├── table_style_test.go # 表格样式功能集成测试
|
|
||||||
│ └── template_test.go # 模板功能集成测试 ✨ 新增
|
|
||||||
├── .gitignore # Git忽略文件配置
|
|
||||||
├── go.mod # Go模块定义
|
|
||||||
├── LICENSE # MIT许可证
|
|
||||||
└── README.md # 项目说明文档
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 功能特性
|
### 模板继承功能示例
|
||||||
|
|
||||||
### ✅ 已实现功能
|
```go
|
||||||
|
// 创建基础模板
|
||||||
|
engine := document.NewTemplateEngine()
|
||||||
|
baseTemplate := `{{companyName}} 工作报告
|
||||||
|
|
||||||
#### 文档基础操作
|
{{#block "summary"}}
|
||||||
- [x] 创建新的 Word 文档
|
默认摘要内容
|
||||||
- [x] 读取和解析现有文档 ✨ **重大改进**
|
{{/block}}
|
||||||
- [x] **动态元素解析**: 支持段落、表格、节属性等多种元素类型
|
|
||||||
- [x] **结构化解析**: 保持文档元素的原始顺序和层次结构
|
|
||||||
- [x] **完整XML解析**: 使用流式解析,支持复杂的嵌套结构
|
|
||||||
- [x] **错误恢复**: 智能跳过未知元素,确保解析稳定性
|
|
||||||
- [x] **性能优化**: 内存友好的增量解析,适用于大型文档
|
|
||||||
- [x] 文档保存和压缩
|
|
||||||
- [x] ZIP文件处理和OOXML结构解析
|
|
||||||
|
|
||||||
#### 文本和段落操作
|
{{#block "content"}}
|
||||||
- [x] 文本内容的添加和修改
|
默认主要内容
|
||||||
- [x] 段落创建和管理
|
{{/block}}`
|
||||||
- [x] 文本格式化(字体、大小、颜色、粗体、斜体)
|
|
||||||
- [x] 段落对齐(左对齐、居中、右对齐、两端对齐)
|
|
||||||
- [x] 行间距和段间距设置
|
|
||||||
- [x] 首行缩进和左右缩进
|
|
||||||
- [x] 混合格式文本(一个段落中多种格式)
|
|
||||||
|
|
||||||
#### 样式管理系统
|
engine.LoadTemplate("base_report", baseTemplate)
|
||||||
- [x] **预定义样式库**: 18种Word内置样式
|
|
||||||
- [x] 标题样式(Heading1-Heading9)- 支持导航窗格识别
|
|
||||||
- [x] 正文样式(Normal)
|
|
||||||
- [x] 文档标题和副标题(Title、Subtitle)
|
|
||||||
- [x] 引用样式(Quote)
|
|
||||||
- [x] 列表段落样式(ListParagraph)
|
|
||||||
- [x] 代码相关样式(CodeBlock、CodeChar)
|
|
||||||
- [x] 字符样式(Emphasis、Strong)
|
|
||||||
- [x] **样式继承机制**: 完整的样式继承和属性合并
|
|
||||||
- [x] **自定义样式**: 快速创建和应用自定义样式
|
|
||||||
- [x] **样式查询API**: 按类型查询、样式验证、批量操作
|
|
||||||
- [x] **快速应用API**: 便捷的样式操作接口
|
|
||||||
|
|
||||||
#### 页面设置功能 ✨ 新增
|
// 创建扩展模板,重写特定块
|
||||||
- [x] 页面大小设置(A4、Letter、Legal、A3、A5等标准尺寸)
|
salesTemplate := `{{extends "base_report"}}
|
||||||
- [x] 自定义页面尺寸(毫米单位,支持任意尺寸)
|
|
||||||
- [x] 页面方向设置(纵向/横向)
|
|
||||||
- [x] 页面边距设置(上下左右边距,毫米单位)
|
|
||||||
- [x] 页眉页脚距离设置
|
|
||||||
- [x] 装订线宽度设置
|
|
||||||
- [x] 完整页面设置API和配置结构
|
|
||||||
- [x] 页面设置验证和错误处理
|
|
||||||
- [x] 页面设置的保存和加载支持
|
|
||||||
|
|
||||||
#### 表格功能
|
{{#block "summary"}}
|
||||||
|
销售业绩摘要:本月达成 {{achievement}}%
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
##### 表格基础操作
|
{{#block "content"}}
|
||||||
- [x] 表格创建和初始化
|
销售详情:
|
||||||
- [x] 创建指定行列数的表格
|
- 总销售额:{{totalSales}}
|
||||||
- [x] 设置表格初始数据
|
- 新增客户:{{newCustomers}}
|
||||||
- [x] 表格插入到文档指定位置
|
{{/block}}`
|
||||||
- [x] **新增**:默认表格样式(参考Word标准格式)
|
|
||||||
- [x] 默认单线边框(single边框样式)
|
|
||||||
- [x] 标准边框粗细(4 * 1/8磅)
|
|
||||||
- [x] 自动调整布局(autofit)
|
|
||||||
- [x] 标准单元格边距(108 dxa)
|
|
||||||
- [x] 支持样式覆盖和自定义
|
|
||||||
- [x] 表格结构管理
|
|
||||||
- [x] 插入行(指定位置、末尾、开头)
|
|
||||||
- [x] 删除行(单行、多行、指定范围)
|
|
||||||
- [x] 表格复制和剪切
|
|
||||||
- [x] 表格删除和清空
|
|
||||||
|
|
||||||
##### 单元格操作
|
engine.LoadTemplate("sales_report", salesTemplate)
|
||||||
- [x] 单元格内容管理
|
|
||||||
- [x] 单元格文本设置和获取
|
|
||||||
- [x] 单元格富文本格式支持
|
|
||||||
- [x] 单元格内容清空和重置
|
|
||||||
- [x] 单元格合并功能
|
|
||||||
- [x] 横向合并(合并列)
|
|
||||||
- [x] 纵向合并(合并行)
|
|
||||||
- [x] 区域合并(多行多列)
|
|
||||||
- [x] 取消合并操作
|
|
||||||
- [x] 合并状态查询
|
|
||||||
- [x] 单元格格式设置
|
|
||||||
- [x] 单元格文字格式(字体、大小、颜色)
|
|
||||||
- [x] 单元格对齐方式(水平、垂直对齐)
|
|
||||||
- [x] 单元格文字方向和旋转
|
|
||||||
- [x] 单元格内边距设置
|
|
||||||
|
|
||||||
##### 表格布局和尺寸
|
// 渲染模板
|
||||||
- [x] 表格尺寸控制
|
data := document.NewTemplateData()
|
||||||
- [x] 表格总宽度设置(固定宽度、相对宽度、自动宽度)
|
data.SetVariable("companyName", "WordZero科技")
|
||||||
- [x] 列宽设置(固定宽度、相对宽度、自动调整)
|
data.SetVariable("achievement", "125")
|
||||||
- [x] 行高设置(固定高度、最小高度、自动调整)
|
data.SetVariable("totalSales", "1,850,000")
|
||||||
- [x] 单元格尺寸精确控制
|
data.SetVariable("newCustomers", "45")
|
||||||
- [x] 表格对齐和定位
|
|
||||||
- [x] 表格页面对齐(左对齐、居中、右对齐)
|
|
||||||
- [x] 表格文字环绕设置
|
|
||||||
- [x] 表格相对定位
|
|
||||||
- [x] 表格分页控制
|
|
||||||
- [x] 表格跨页处理
|
|
||||||
- [x] 标题行重复显示
|
|
||||||
- [x] 表格分页符控制
|
|
||||||
- [x] 避免分页的行设置
|
|
||||||
|
|
||||||
##### 表格数据处理
|
doc, _ := engine.RenderToDocument("sales_report", data)
|
||||||
- [x] 数据导入导出
|
doc.Save("sales_report.docx")
|
||||||
- [x] 二维数组数据绑定
|
```
|
||||||
- [x] 表格数据提取为数组
|
|
||||||
- [x] 批量数据填充
|
|
||||||
|
|
||||||
##### 表格访问和查询
|
## 文档和示例
|
||||||
- [x] 表格查找和定位
|
|
||||||
- [x] 按索引获取表格
|
|
||||||
- [x] 表格位置信息获取
|
|
||||||
- [x] 单元格访问接口
|
|
||||||
- [x] 按行列索引访问
|
|
||||||
- [x] **单元格遍历迭代器** ✨ **新实现**
|
|
||||||
- [x] 单元格迭代器(CellIterator)
|
|
||||||
- [x] 按顺序遍历所有单元格
|
|
||||||
- [x] 迭代器重置和位置跟踪
|
|
||||||
- [x] 迭代进度计算
|
|
||||||
- [x] 单元格信息结构(CellInfo)
|
|
||||||
- [x] ForEach批量处理方法
|
|
||||||
- [x] 按行遍历(ForEachInRow)
|
|
||||||
- [x] 按列遍历(ForEachInColumn)
|
|
||||||
- [x] 获取单元格范围(GetCellRange)
|
|
||||||
- [x] 条件查找单元格(FindCells)
|
|
||||||
- [x] 按文本查找单元格(FindCellsByText)
|
|
||||||
- [x] 精确匹配和模糊匹配支持
|
|
||||||
|
|
||||||
##### 表格样式和外观
|
### 📚 完整文档
|
||||||
- [x] 表格整体样式
|
- [**📖 Wiki文档**](wordZero.wiki/) - 完整的使用文档和API参考
|
||||||
- [x] 预定义表格样式模板
|
- [**🚀 快速开始**](wordZero.wiki/01-快速开始) - 新手入门指南
|
||||||
- [x] 自定义表格样式创建
|
- [**⚡ 功能特性详览**](wordZero.wiki/14-功能特性详览) - 所有功能的详细说明
|
||||||
- [x] 表格主题色彩应用
|
- [**📊 性能基准测试**](wordZero.wiki/13-性能基准测试) - 跨语言性能对比分析
|
||||||
- [x] 表格样式继承和覆盖
|
- [**🏗️ 项目结构详解**](wordZero.wiki/15-项目结构详解) - 项目架构和代码组织
|
||||||
- [x] 表格边框设置
|
|
||||||
- [x] 外边框样式(线型、颜色、粗细)
|
|
||||||
- [x] 内边框样式(网格线设置)
|
|
||||||
- [x] 单元格边框独立设置
|
|
||||||
- [x] 边框部分应用(顶部、底部、左右)
|
|
||||||
- [x] 无边框表格支持
|
|
||||||
- [x] 表格背景和填充
|
|
||||||
- [x] 表格背景色设置
|
|
||||||
- [x] 单元格背景色设置
|
|
||||||
- [x] 奇偶行颜色交替
|
|
||||||
- [x] 渐变背景支持(基础渐变)
|
|
||||||
- [x] 图案填充支持
|
|
||||||
|
|
||||||
#### 图片功能
|
|
||||||
- [x] 图片插入
|
|
||||||
- [x] 图片大小调整
|
|
||||||
- [x] 图片位置设置
|
|
||||||
- [x] 多种图片格式支持(JPG、PNG、GIF)
|
|
||||||
|
|
||||||
### 🚧 规划中功能
|
|
||||||
|
|
||||||
#### 表格功能完善
|
|
||||||
|
|
||||||
##### 高级表格功能
|
|
||||||
- [ ] 表格排序功能(Word内置排序)
|
|
||||||
- [ ] 单列排序(升序、降序)
|
|
||||||
- [ ] 多列排序
|
|
||||||
- [ ] 保持标题行不参与排序
|
|
||||||
- [ ] 表格标题和说明
|
|
||||||
- [ ] 表格标题设置(表格上方、下方)
|
|
||||||
- [ ] 表格标题编号自动生成
|
|
||||||
- [ ] 表格描述和备注
|
|
||||||
- [ ] 嵌套表格支持
|
|
||||||
- [ ] 单元格内嵌套表格
|
|
||||||
- [ ] 嵌套表格独立样式
|
|
||||||
- [ ] 表格模板功能
|
|
||||||
- [ ] 常用表格模板库
|
|
||||||
- [ ] 自定义模板保存
|
|
||||||
- [ ] 模板快速应用
|
|
||||||
- [ ] 表格访问增强
|
|
||||||
- [ ] 按标题查找表格
|
|
||||||
- [ ] 按范围批量访问
|
|
||||||
|
|
||||||
#### 高级功能 ✨ **已实现**
|
|
||||||
- [x] **页眉页脚** ✨ **新实现**
|
|
||||||
- [x] 默认页眉页脚设置
|
|
||||||
- [x] 首页不同页眉页脚
|
|
||||||
- [x] 奇偶页不同页眉页脚
|
|
||||||
- [x] 页眉页脚中的页码显示
|
|
||||||
- [x] 页眉页脚的格式化支持
|
|
||||||
- [x] 页眉页脚的文本内容设置
|
|
||||||
- [x] 页眉页脚引用和关系管理
|
|
||||||
- [x] **文档属性设置** ✨ **新实现**
|
|
||||||
- [x] 标题、作者、主题设置
|
|
||||||
- [x] 关键字、描述、类别设置
|
|
||||||
- [x] 创建时间、修改时间管理
|
|
||||||
- [x] 文档统计信息(字数、段落数等)
|
|
||||||
- [x] 自动统计信息更新
|
|
||||||
- [x] **列表和编号** ✨ **新实现**
|
|
||||||
- [x] 无序列表(多种项目符号:圆点、空心圆、方块、短横线、箭头)
|
|
||||||
- [x] 有序列表(数字、字母、罗马数字)
|
|
||||||
- [x] 多级列表支持(最多9级)
|
|
||||||
- [x] 自定义列表样式
|
|
||||||
- [x] 列表编号重新开始
|
|
||||||
- [x] 多级列表批量创建
|
|
||||||
- [x] 列表缩进级别控制
|
|
||||||
- [x] **目录生成** ✨ **新实现**
|
|
||||||
- [x] 自动生成目录
|
|
||||||
- [x] 基于标题样式的目录条目
|
|
||||||
- [x] 目录级别控制(1-9级)
|
|
||||||
- [x] 页码显示和超链接支持
|
|
||||||
- [x] 目录更新功能
|
|
||||||
- [x] 带书签的标题支持
|
|
||||||
- [x] 目录样式自定义
|
|
||||||
- [x] 标题统计和列表功能
|
|
||||||
- [x] **脚注和尾注** ✨ **新实现**
|
|
||||||
- [x] 脚注添加和管理
|
|
||||||
- [x] 尾注添加和管理
|
|
||||||
- [x] 多种编号格式支持(数字、字母、罗马数字、符号)
|
|
||||||
- [x] 脚注/尾注的删除和更新
|
|
||||||
- [x] 自定义脚注配置
|
|
||||||
- [x] 脚注位置设置(页面底部、文本下方、节末尾、文档末尾)
|
|
||||||
- [x] 脚注重启规则(连续、每节重启、每页重启)
|
|
||||||
- [x] 脚注数量统计
|
|
||||||
- [x] **结构化文档标签(SDT)** ✨ **新实现**
|
|
||||||
- [x] 目录SDT结构创建
|
|
||||||
- [x] SDT属性和内容管理
|
|
||||||
- [x] SDT占位符和文档部件支持
|
|
||||||
- [x] 目录条目SDT嵌套
|
|
||||||
- [x] **域字段功能** ✨ **新实现**
|
|
||||||
- [x] 超链接域字段
|
|
||||||
- [x] 页码引用域字段
|
|
||||||
- [x] 域字符和指令文本
|
|
||||||
- [x] 域字段的开始、分隔、结束标记
|
|
||||||
- [x] **页码设置**(已集成到页眉页脚功能中)
|
|
||||||
|
|
||||||
#### 模板功能 ✨ **新实现**
|
|
||||||
- [x] **基础模板引擎** ✨ **新实现**
|
|
||||||
- [x] 变量替换:`{{变量名}}` 语法支持
|
|
||||||
- [x] 条件语句:`{{#if 条件}}...{{/if}}` 支持
|
|
||||||
- [x] 循环语句:`{{#each 列表}}...{{/each}}` 支持
|
|
||||||
- [x] 模板继承:基础模板和子模板扩展
|
|
||||||
- [x] 循环上下文变量:`{{@index}}`、`{{@first}}`、`{{@last}}`、`{{this}}`
|
|
||||||
- [x] 嵌套模板语法支持
|
|
||||||
- [x] **模板操作** ✨ **新实现**
|
|
||||||
- [x] 从字符串加载模板
|
|
||||||
- [x] 从现有文档创建模板
|
|
||||||
- [x] 模板渲染和变量填充
|
|
||||||
- [x] 模板验证和错误处理
|
|
||||||
- [x] 模板缓存机制
|
|
||||||
- [x] 模板数据绑定和管理
|
|
||||||
- [x] **数据绑定** ✨ **新实现**
|
|
||||||
- [x] 基础数据类型支持(字符串、数字、布尔值)
|
|
||||||
- [x] 复杂数据结构支持(map、slice)
|
|
||||||
- [x] 结构体自动绑定
|
|
||||||
- [x] 模板数据合并和清空
|
|
||||||
- [x] 批量变量设置
|
|
||||||
- [x] **模板语法** ✨ **新实现**
|
|
||||||
- [x] 正则表达式解析引擎
|
|
||||||
- [x] 语法验证和错误检查
|
|
||||||
- [x] 嵌套条件和循环支持
|
|
||||||
- [x] 模板块管理和组织
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
|
### 💡 使用示例
|
||||||
查看 `examples/` 目录下的示例代码:
|
查看 `examples/` 目录下的示例代码:
|
||||||
|
|
||||||
- `examples/basic/` - 基础功能演示
|
- `examples/basic/` - 基础功能演示
|
||||||
- `examples/style_demo/` - 样式系统演示
|
- `examples/style_demo/` - 样式系统演示
|
||||||
- `examples/table/` - 表格功能演示
|
- `examples/table/` - 表格功能演示
|
||||||
- `examples/table_layout/` - 表格布局和尺寸演示
|
|
||||||
- `examples/table_style/` - 表格样式和外观演示
|
|
||||||
- `examples/cell_advanced/` - 单元格高级功能演示
|
|
||||||
- `examples/cell_iterator/` - **单元格迭代器功能演示** ✨ **新增**
|
|
||||||
- `examples/formatting/` - 格式化演示
|
- `examples/formatting/` - 格式化演示
|
||||||
- `examples/page_settings/` - **页面设置演示** ✨ **新增**
|
- `examples/page_settings/` - 页面设置演示
|
||||||
- `examples/advanced_features/` - **高级功能综合演示** ✨ **新增**
|
- `examples/advanced_features/` - 高级功能综合演示
|
||||||
- 页眉页脚功能演示
|
- `examples/template_demo/` - 模板功能演示
|
||||||
- 目录生成和管理演示
|
- `examples/template_inheritance_demo/` - 模板继承功能演示 ✨ **新增**
|
||||||
- 脚注尾注功能演示
|
|
||||||
- 列表和编号演示
|
|
||||||
- 结构化文档标签演示
|
|
||||||
- `examples/template_demo/` - **模板功能演示** ✨ **新增**
|
|
||||||
- 基础变量替换演示
|
|
||||||
- 条件语句功能演示
|
|
||||||
- 循环语句功能演示
|
|
||||||
- 模板继承功能演示
|
|
||||||
- 复杂模板综合应用演示
|
|
||||||
- 从现有文档创建模板演示
|
|
||||||
- 结构体数据绑定演示
|
|
||||||
|
|
||||||
运行示例:
|
运行示例:
|
||||||
```bash
|
```bash
|
||||||
@@ -396,31 +160,58 @@ go run ./examples/style_demo/
|
|||||||
# 运行表格演示
|
# 运行表格演示
|
||||||
go run ./examples/table/
|
go run ./examples/table/
|
||||||
|
|
||||||
# 运行表格布局和尺寸演示
|
# 运行模板继承演示
|
||||||
go run ./examples/table_layout/
|
go run ./examples/template_inheritance_demo/
|
||||||
|
|
||||||
# 运行表格样式演示
|
|
||||||
go run ./examples/table_style/
|
|
||||||
|
|
||||||
# 运行单元格高级功能演示
|
|
||||||
go run ./examples/cell_advanced/
|
|
||||||
|
|
||||||
# 运行单元格迭代器功能演示
|
|
||||||
go run ./examples/cell_iterator/
|
|
||||||
|
|
||||||
# 运行格式化演示
|
|
||||||
go run ./examples/formatting/
|
|
||||||
|
|
||||||
# 运行页面设置演示
|
|
||||||
go run ./examples/page_settings/
|
|
||||||
|
|
||||||
# 运行高级功能综合演示
|
|
||||||
go run ./examples/advanced_features/
|
|
||||||
|
|
||||||
# 运行模板功能演示
|
|
||||||
go run ./examples/template_demo/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### ✅ 已实现功能
|
||||||
|
- **文档操作**: 创建、读取、保存、解析DOCX文档
|
||||||
|
- **文本格式化**: 字体、大小、颜色、粗体、斜体等
|
||||||
|
- **样式系统**: 18种预定义样式 + 自定义样式支持
|
||||||
|
- **段落格式**: 对齐、间距、缩进等完整支持
|
||||||
|
- **表格功能**: 完整的表格操作、样式设置、单元格迭代器
|
||||||
|
- **页面设置**: 页面尺寸、边距、页眉页脚等
|
||||||
|
- **高级功能**: 目录生成、脚注尾注、列表编号、模板引擎(含模板继承)
|
||||||
|
- **图片功能**: 图片插入、大小调整、位置设置
|
||||||
|
|
||||||
|
### 🚧 规划中功能
|
||||||
|
- 表格排序和高级操作
|
||||||
|
- 书签和交叉引用
|
||||||
|
- 文档批注和修订
|
||||||
|
- 图形绘制功能
|
||||||
|
- 多语言和国际化支持
|
||||||
|
|
||||||
|
👉 **查看完整功能列表**: [功能特性详览](wordZero.wiki/14-功能特性详览)
|
||||||
|
|
||||||
|
## 性能表现
|
||||||
|
|
||||||
|
WordZero 在性能方面表现卓越,通过完整的基准测试验证:
|
||||||
|
|
||||||
|
| 语言 | 平均执行时间 | 相对性能 |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| **Golang** | **2.62ms** | **1.00×** |
|
||||||
|
| JavaScript | 9.63ms | 3.67× |
|
||||||
|
| Python | 55.98ms | 21.37× |
|
||||||
|
|
||||||
|
👉 **查看详细性能分析**: [性能基准测试](wordZero.wiki/13-性能基准测试)
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
wordZero/
|
||||||
|
├── pkg/ # 核心库代码
|
||||||
|
│ ├── document/ # 文档操作功能
|
||||||
|
│ └── style/ # 样式管理系统
|
||||||
|
├── examples/ # 使用示例
|
||||||
|
├── test/ # 集成测试
|
||||||
|
├── benchmark/ # 性能基准测试
|
||||||
|
└── wordZero.wiki/ # 完整文档
|
||||||
|
```
|
||||||
|
|
||||||
|
👉 **查看详细结构说明**: [项目结构详解](wordZero.wiki/15-项目结构详解)
|
||||||
|
|
||||||
## 贡献指南
|
## 贡献指南
|
||||||
|
|
||||||
欢迎提交 Issue 和 Pull Request!在提交代码前请确保:
|
欢迎提交 Issue 和 Pull Request!在提交代码前请确保:
|
||||||
@@ -432,4 +223,12 @@ go run ./examples/template_demo/
|
|||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
|
本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**更多资源**:
|
||||||
|
- 📖 [完整文档](wordZero.wiki/)
|
||||||
|
- 🔧 [API参考](wordZero.wiki/10-API参考)
|
||||||
|
- 💡 [最佳实践](wordZero.wiki/09-最佳实践)
|
||||||
|
- 📝 [更新日志](CHANGELOG.md)
|
BIN
basic_image.png
BIN
basic_image.png
Binary file not shown.
Before Width: | Height: | Size: 417 B |
150
benchmark/README.md
Normal file
150
benchmark/README.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# WordZero 性能基准测试
|
||||||
|
|
||||||
|
对Golang WordZero库、JavaScript docx库和Python python-docx库进行Word文档操作性能测试和对比分析。
|
||||||
|
|
||||||
|
## 测试配置统一化
|
||||||
|
|
||||||
|
为了确保公平对比,所有语言的测试都使用相同的迭代次数:
|
||||||
|
|
||||||
|
| 测试项目 | 迭代次数 | 说明 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 基础文档创建 | 50次 | 创建包含基本文本的文档 |
|
||||||
|
| 复杂格式化 | 30次 | 创建包含多种格式化的文档 |
|
||||||
|
| 表格操作 | 20次 | 创建10行5列的表格 |
|
||||||
|
| 大表格处理 | 10次 | 创建100行10列的大表格 |
|
||||||
|
| 大型文档 | 5次 | 创建包含1000个段落+表格的文档 |
|
||||||
|
| 内存使用测试 | 10次 | 测试内存使用情况 |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 运行完整的性能对比测试(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装所有依赖
|
||||||
|
task setup
|
||||||
|
|
||||||
|
# 运行固定迭代次数的对比测试
|
||||||
|
task benchmark-all-fixed
|
||||||
|
|
||||||
|
# 生成对比报告
|
||||||
|
task compare
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行单独的语言测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Golang 固定迭代次数测试(推荐用于对比)
|
||||||
|
task benchmark-golang-fixed
|
||||||
|
|
||||||
|
# Golang 标准基准测试(迭代次数由Go框架决定)
|
||||||
|
task benchmark-golang
|
||||||
|
|
||||||
|
# JavaScript 测试
|
||||||
|
task benchmark-js
|
||||||
|
|
||||||
|
# Python 测试
|
||||||
|
task benchmark-python
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试类型说明
|
||||||
|
|
||||||
|
### 1. Golang 测试
|
||||||
|
|
||||||
|
**固定迭代次数测试(推荐)**:
|
||||||
|
- 命令: `task benchmark-golang-fixed`
|
||||||
|
- 运行: `TestFixedIterationsPerformance`
|
||||||
|
- 与其他语言使用相同的迭代次数,确保公平对比
|
||||||
|
|
||||||
|
**Go标准基准测试**:
|
||||||
|
- 命令: `task benchmark-golang`
|
||||||
|
- 运行: `BenchmarkXXX` 系列函数
|
||||||
|
- 迭代次数由Go测试框架自动决定(通常是数千次)
|
||||||
|
|
||||||
|
### 2. JavaScript 测试
|
||||||
|
|
||||||
|
- 使用 `docx` 库
|
||||||
|
- 固定迭代次数,与其他语言保持一致
|
||||||
|
- 输出JSON格式的性能报告
|
||||||
|
|
||||||
|
### 3. Python 测试
|
||||||
|
|
||||||
|
- 使用 `python-docx` 库
|
||||||
|
- 固定迭代次数,与其他语言保持一致
|
||||||
|
- 支持pytest-benchmark和自定义测试
|
||||||
|
|
||||||
|
## 输出文件
|
||||||
|
|
||||||
|
测试完成后,会在 `results/` 目录下生成:
|
||||||
|
|
||||||
|
```
|
||||||
|
results/
|
||||||
|
├── golang/
|
||||||
|
│ ├── performance_report.json # 固定迭代测试报告
|
||||||
|
│ ├── fixed_benchmark_output.txt # 固定迭代测试日志
|
||||||
|
│ ├── benchmark_output.txt # Go标准基准测试日志
|
||||||
|
│ └── *.docx # 生成的测试文档
|
||||||
|
├── javascript/
|
||||||
|
│ ├── performance_report.json # JavaScript测试报告
|
||||||
|
│ └── *.docx # 生成的测试文档
|
||||||
|
├── python/
|
||||||
|
│ ├── performance_report.json # Python测试报告
|
||||||
|
│ └── *.docx # 生成的测试文档
|
||||||
|
├── charts/ # 性能对比图表
|
||||||
|
├── detailed_comparison_report.md # 详细对比报告
|
||||||
|
└── performance_comparison.json # 对比数据
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能指标
|
||||||
|
|
||||||
|
每个测试都会记录:
|
||||||
|
- **平均耗时** (avgTime): 所有迭代的平均执行时间
|
||||||
|
- **最小耗时** (minTime): 最快的一次执行时间
|
||||||
|
- **最大耗时** (maxTime): 最慢的一次执行时间
|
||||||
|
- **迭代次数** (iterations): 测试运行的次数
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
### Golang
|
||||||
|
- Go 1.19+
|
||||||
|
- WordZero库依赖
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
- Node.js 16+
|
||||||
|
- docx 库
|
||||||
|
|
||||||
|
### Python
|
||||||
|
- Python 3.8+
|
||||||
|
- python-docx 库
|
||||||
|
- 自动创建虚拟环境
|
||||||
|
|
||||||
|
## 测试最佳实践
|
||||||
|
|
||||||
|
1. **使用固定迭代次数测试进行对比**: `task benchmark-all-fixed`
|
||||||
|
2. **确保系统负载一致**: 测试期间避免运行其他大型程序
|
||||||
|
3. **多次运行取平均值**: 可以多次运行测试以获得更稳定的结果
|
||||||
|
4. **关注相对性能**: 重点关注各语言间的相对性能差异
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### Python SSL证书问题
|
||||||
|
```bash
|
||||||
|
task setup-python-ssl-fix
|
||||||
|
```
|
||||||
|
|
||||||
|
### 清理生成的文件
|
||||||
|
```bash
|
||||||
|
task clean
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整重置环境
|
||||||
|
```bash
|
||||||
|
task clean-all
|
||||||
|
task setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试说明
|
||||||
|
|
||||||
|
- 所有测试都包含文档创建和文件保存操作
|
||||||
|
- 测试文件保存在各自的results目录中
|
||||||
|
- 内存测试会输出内存使用情况
|
||||||
|
- 每种语言使用其生态系统中最流行的Word操作库
|
307
benchmark/Taskfile.yml
Normal file
307
benchmark/Taskfile.yml
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# WordZero 性能基准测试 Taskfile
|
||||||
|
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
env:
|
||||||
|
BENCHMARK_TIMEOUT: 30m
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
desc: "运行完整的性能基准测试流程"
|
||||||
|
deps: [setup]
|
||||||
|
cmds:
|
||||||
|
- task: benchmark-all
|
||||||
|
- task: compare
|
||||||
|
|
||||||
|
help:
|
||||||
|
desc: "显示帮助信息"
|
||||||
|
cmds:
|
||||||
|
- echo "WordZero 性能基准测试"
|
||||||
|
- echo ""
|
||||||
|
- echo '可用命令'
|
||||||
|
- echo " task setup - 安装所有依赖(自动创建Python虚拟环境)"
|
||||||
|
- echo " task setup-python-ssl-fix - 设置Python环境(解决SSL证书问题)"
|
||||||
|
- echo " task benchmark-all - 运行所有平台的性能测试(Go标准基准测试)"
|
||||||
|
- echo " task benchmark-all-fixed - 运行所有平台的固定迭代次数测试(推荐用于对比)"
|
||||||
|
- echo " task benchmark-golang - 运行Golang性能测试(Go标准基准测试)"
|
||||||
|
- echo " task benchmark-golang-fixed - 运行Golang固定迭代次数测试"
|
||||||
|
- echo " task benchmark-js - 运行JavaScript性能测试"
|
||||||
|
- echo " task benchmark-python - 运行Python性能测试(使用虚拟环境)"
|
||||||
|
- echo " task compare - 生成性能对比分析"
|
||||||
|
- echo " task clean - 清理生成的文件"
|
||||||
|
- echo " task clean-all - 完整清理(包括依赖和虚拟环境)"
|
||||||
|
- echo " task benchmark-quick - 运行快速性能测试"
|
||||||
|
- echo " task benchmark-memory - 运行内存使用测试"
|
||||||
|
- echo " task report - 生成测试报告"
|
||||||
|
- echo " task help - 显示帮助信息"
|
||||||
|
- echo ""
|
||||||
|
- echo '注意 - Python 测试使用虚拟环境,避免与系统 Python 冲突'
|
||||||
|
- echo 'SSL问题 - 如果遇到SSL证书错误,请使用 task setup-python-ssl-fix'
|
||||||
|
|
||||||
|
# 环境设置任务
|
||||||
|
setup:
|
||||||
|
desc: "安装所有依赖"
|
||||||
|
deps: [setup-golang, setup-js, setup-python]
|
||||||
|
cmds:
|
||||||
|
- echo "所有环境设置完成"
|
||||||
|
|
||||||
|
setup-golang:
|
||||||
|
desc: "设置Golang环境"
|
||||||
|
dir: golang
|
||||||
|
cmds:
|
||||||
|
- echo "设置Golang环境..."
|
||||||
|
- go mod tidy
|
||||||
|
- cmd: mkdir -p ../results/golang
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: mkdir "..\results\golang" 2>nul || exit /b 0
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
setup-js:
|
||||||
|
desc: "设置JavaScript环境"
|
||||||
|
dir: javascript
|
||||||
|
cmds:
|
||||||
|
- echo "设置JavaScript环境..."
|
||||||
|
- npm install
|
||||||
|
- cmd: mkdir -p ../results/javascript
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: mkdir "..\results\javascript" 2>nul || exit /b 0
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
setup-python:
|
||||||
|
desc: "设置Python环境(创建虚拟环境并安装依赖)"
|
||||||
|
dir: python
|
||||||
|
cmds:
|
||||||
|
- echo "设置Python环境..."
|
||||||
|
- echo "创建虚拟环境..."
|
||||||
|
- python -m venv venv
|
||||||
|
- echo "激活虚拟环境并安装依赖..."
|
||||||
|
- cmd: |
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org -r requirements.txt
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: |
|
||||||
|
venv\Scripts\python.exe -m pip install --upgrade pip
|
||||||
|
venv\Scripts\pip.exe install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org -r requirements.txt
|
||||||
|
platforms: [windows]
|
||||||
|
- cmd: mkdir -p ../results/python
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: mkdir "..\results\python" 2>nul || exit /b 0
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
setup-python-ssl-fix:
|
||||||
|
desc: "设置Python环境(解决SSL证书问题)"
|
||||||
|
dir: python
|
||||||
|
cmds:
|
||||||
|
- echo "设置Python环境(SSL证书修复模式)..."
|
||||||
|
- echo "创建虚拟环境..."
|
||||||
|
- python -m venv venv
|
||||||
|
- echo "激活虚拟环境并安装依赖(跳过SSL验证)..."
|
||||||
|
- cmd: |
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install --upgrade pip --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org
|
||||||
|
pip config set global.trusted-host "pypi.org files.pythonhosted.org pypi.python.org"
|
||||||
|
pip install -r requirements.txt
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: |
|
||||||
|
venv\Scripts\python.exe -m pip install --upgrade pip --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org
|
||||||
|
venv\Scripts\pip.exe config set global.trusted-host "pypi.org files.pythonhosted.org pypi.python.org"
|
||||||
|
venv\Scripts\pip.exe install -r requirements.txt
|
||||||
|
platforms: [windows]
|
||||||
|
- cmd: mkdir -p ../results/python
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: mkdir "..\results\python" 2>nul || exit /b 0
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
# 性能测试任务
|
||||||
|
benchmark-all:
|
||||||
|
desc: "运行所有平台的性能测试"
|
||||||
|
deps: [benchmark-golang, benchmark-js, benchmark-python]
|
||||||
|
cmds:
|
||||||
|
- echo "所有性能测试完成"
|
||||||
|
|
||||||
|
# 固定迭代次数的对比测试(推荐使用)
|
||||||
|
benchmark-all-fixed:
|
||||||
|
desc: "运行所有平台的固定迭代次数性能测试(推荐用于对比)"
|
||||||
|
deps: [benchmark-golang-fixed, benchmark-js, benchmark-python]
|
||||||
|
cmds:
|
||||||
|
- echo "所有固定迭代次数性能测试完成"
|
||||||
|
|
||||||
|
benchmark-golang:
|
||||||
|
desc: "运行Golang性能测试(Go标准基准测试)"
|
||||||
|
dir: golang
|
||||||
|
cmds:
|
||||||
|
- echo "运行Golang性能测试..."
|
||||||
|
- cmd: go test -bench=. -benchmem -timeout={{.BENCHMARK_TIMEOUT}} | tee ../results/golang/benchmark_output.txt
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: go test -bench=. -benchmem -timeout={{.BENCHMARK_TIMEOUT}} > ..\results\golang\benchmark_output.txt
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
benchmark-golang-fixed:
|
||||||
|
desc: "运行Golang固定迭代次数性能测试(与其他语言保持一致)"
|
||||||
|
dir: golang
|
||||||
|
cmds:
|
||||||
|
- echo "运行Golang固定迭代次数性能测试..."
|
||||||
|
- cmd: go test -run=TestFixedIterationsPerformance -timeout={{.BENCHMARK_TIMEOUT}} | tee ../results/golang/fixed_benchmark_output.txt
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: go test -run=TestFixedIterationsPerformance -timeout={{.BENCHMARK_TIMEOUT}} > ..\results\golang\fixed_benchmark_output.txt
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
benchmark-js:
|
||||||
|
desc: "运行JavaScript性能测试"
|
||||||
|
dir: javascript
|
||||||
|
cmds:
|
||||||
|
- echo "运行JavaScript性能测试..."
|
||||||
|
- npm test
|
||||||
|
|
||||||
|
benchmark-python:
|
||||||
|
desc: "运行Python性能测试"
|
||||||
|
dir: python
|
||||||
|
cmds:
|
||||||
|
- echo "运行Python性能测试..."
|
||||||
|
- cmd: |
|
||||||
|
source venv/bin/activate
|
||||||
|
python benchmark_test.py
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: |
|
||||||
|
./venv/Scripts/python.exe benchmark_test.py
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
# 分析和报告任务
|
||||||
|
compare:
|
||||||
|
desc: "生成性能对比分析"
|
||||||
|
dir: python
|
||||||
|
cmds:
|
||||||
|
- echo "生成性能对比分析..."
|
||||||
|
- cmd: |
|
||||||
|
source venv/bin/activate
|
||||||
|
python ../compare_results.py
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: |
|
||||||
|
./venv/Scripts/python.exe ../compare_results.py
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
report:
|
||||||
|
desc: "生成测试报告"
|
||||||
|
deps: [compare]
|
||||||
|
cmds:
|
||||||
|
- echo '性能测试报告已生成'
|
||||||
|
- echo '- 详细报告 -> results/detailed_comparison_report.md'
|
||||||
|
- echo '- 图表目录 -> results/charts/'
|
||||||
|
- echo '- 各平台结果 -> results/*/performance_report.json'
|
||||||
|
|
||||||
|
# 快速测试任务
|
||||||
|
benchmark-quick:
|
||||||
|
desc: "运行快速性能测试(少量迭代)"
|
||||||
|
cmds:
|
||||||
|
- echo "运行快速性能测试..."
|
||||||
|
- task: benchmark-quick-golang
|
||||||
|
- task: benchmark-quick-js
|
||||||
|
- task: benchmark-quick-python
|
||||||
|
|
||||||
|
benchmark-quick-golang:
|
||||||
|
desc: "运行Golang快速性能测试"
|
||||||
|
dir: golang
|
||||||
|
cmds:
|
||||||
|
- go test -bench=BenchmarkCreateBasicDocument -benchtime=10x
|
||||||
|
|
||||||
|
benchmark-quick-js:
|
||||||
|
desc: "运行JavaScript快速性能测试"
|
||||||
|
dir: javascript
|
||||||
|
cmds:
|
||||||
|
- node -e "require('./benchmark.js').testBasicDocumentCreation(0)"
|
||||||
|
|
||||||
|
benchmark-quick-python:
|
||||||
|
desc: "运行Python快速性能测试"
|
||||||
|
dir: python
|
||||||
|
cmds:
|
||||||
|
- cmd: |
|
||||||
|
source venv/bin/activate
|
||||||
|
python -c "from benchmark_test import test_basic_document_creation; test_basic_document_creation(0)"
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: |
|
||||||
|
./venv/Scripts/python.exe -c "from benchmark_test import test_basic_document_creation; test_basic_document_creation(0)"
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
# 内存测试任务
|
||||||
|
benchmark-memory:
|
||||||
|
desc: "运行内存使用测试"
|
||||||
|
cmds:
|
||||||
|
- echo "运行内存使用测试..."
|
||||||
|
- task: benchmark-memory-golang
|
||||||
|
- task: benchmark-memory-js
|
||||||
|
- task: benchmark-memory-python
|
||||||
|
|
||||||
|
benchmark-memory-golang:
|
||||||
|
desc: "运行Golang内存测试"
|
||||||
|
dir: golang
|
||||||
|
cmds:
|
||||||
|
- go test -bench=BenchmarkMemoryUsage -benchmem
|
||||||
|
|
||||||
|
benchmark-memory-js:
|
||||||
|
desc: "运行JavaScript内存测试"
|
||||||
|
dir: javascript
|
||||||
|
cmds:
|
||||||
|
- node -e "require('./benchmark.js').testMemoryUsage(0)"
|
||||||
|
|
||||||
|
benchmark-memory-python:
|
||||||
|
desc: "运行Python内存测试"
|
||||||
|
dir: python
|
||||||
|
cmds:
|
||||||
|
- cmd: |
|
||||||
|
source venv/bin/activate
|
||||||
|
python -c "from benchmark_test import test_memory_usage; test_memory_usage(0)"
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: |
|
||||||
|
./venv/Scripts/python.exe -c "from benchmark_test import test_memory_usage; test_memory_usage(0)"
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
# 清理任务
|
||||||
|
clean:
|
||||||
|
desc: "清理生成的文件"
|
||||||
|
cmds:
|
||||||
|
- echo "清理生成的文件..."
|
||||||
|
- cmd: rm -rf results/golang/*.docx results/javascript/*.docx results/python/*.docx results/charts/ results/*.md results/*.json
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: |
|
||||||
|
if exist "results\golang\*.docx" del /q "results\golang\*.docx"
|
||||||
|
if exist "results\javascript\*.docx" del /q "results\javascript\*.docx"
|
||||||
|
if exist "results\python\*.docx" del /q "results\python\*.docx"
|
||||||
|
if exist "results\charts" rmdir /s /q "results\charts"
|
||||||
|
if exist "results\*.md" del /q "results\*.md"
|
||||||
|
if exist "results\*.json" del /q "results\*.json"
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
clean-all:
|
||||||
|
desc: "完整清理(包括依赖)"
|
||||||
|
deps: [clean]
|
||||||
|
cmds:
|
||||||
|
- echo "完整清理..."
|
||||||
|
- task: clean-golang
|
||||||
|
- task: clean-js
|
||||||
|
- task: clean-python
|
||||||
|
|
||||||
|
clean-golang:
|
||||||
|
desc: "清理Golang环境"
|
||||||
|
dir: golang
|
||||||
|
cmds:
|
||||||
|
- go clean
|
||||||
|
|
||||||
|
clean-js:
|
||||||
|
desc: "清理JavaScript环境"
|
||||||
|
dir: javascript
|
||||||
|
cmds:
|
||||||
|
- cmd: rm -rf node_modules
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: rmdir /s /q "node_modules" 2>nul || exit /b 0
|
||||||
|
platforms: [windows]
|
||||||
|
|
||||||
|
clean-python:
|
||||||
|
desc: "清理Python环境(删除虚拟环境)"
|
||||||
|
dir: python
|
||||||
|
cmds:
|
||||||
|
- echo "清理Python虚拟环境..."
|
||||||
|
- cmd: rm -rf venv
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- cmd: rmdir /s /q "venv" 2>nul || exit /b 0
|
||||||
|
platforms: [windows]
|
286
benchmark/compare_results.py
Normal file
286
benchmark/compare_results.py
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
性能测试结果对比分析工具
|
||||||
|
|
||||||
|
读取Golang、JavaScript和Python的性能测试结果,
|
||||||
|
生成对比图表和详细分析报告。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import pandas as pd
|
||||||
|
import seaborn as sns
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 设置中文字体
|
||||||
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
|
||||||
|
plt.rcParams['axes.unicode_minus'] = False
|
||||||
|
|
||||||
|
class PerformanceAnalyzer:
|
||||||
|
"""性能分析器"""
|
||||||
|
|
||||||
|
def __init__(self, results_dir: str = "results"):
|
||||||
|
# 确保找到正确的results目录
|
||||||
|
current_dir = Path.cwd()
|
||||||
|
if current_dir.name == "python":
|
||||||
|
# 如果当前在 python 目录,向上一级找 results
|
||||||
|
self.results_dir = current_dir.parent / results_dir
|
||||||
|
else:
|
||||||
|
self.results_dir = Path(results_dir)
|
||||||
|
self.golang_results = {}
|
||||||
|
self.javascript_results = {}
|
||||||
|
self.python_results = {}
|
||||||
|
|
||||||
|
def load_results(self):
|
||||||
|
"""加载所有测试结果"""
|
||||||
|
try:
|
||||||
|
# 加载Golang结果(可能需要从其他格式转换)
|
||||||
|
golang_path = self.results_dir / "golang" / "performance_report.json"
|
||||||
|
if golang_path.exists():
|
||||||
|
with open(golang_path, 'r', encoding='utf-8') as f:
|
||||||
|
self.golang_results = json.load(f)
|
||||||
|
|
||||||
|
# 加载JavaScript结果
|
||||||
|
js_path = self.results_dir / "javascript" / "performance_report.json"
|
||||||
|
if js_path.exists():
|
||||||
|
with open(js_path, 'r', encoding='utf-8') as f:
|
||||||
|
self.javascript_results = json.load(f)
|
||||||
|
|
||||||
|
# 加载Python结果
|
||||||
|
python_path = self.results_dir / "python" / "performance_report.json"
|
||||||
|
if python_path.exists():
|
||||||
|
with open(python_path, 'r', encoding='utf-8') as f:
|
||||||
|
self.python_results = json.load(f)
|
||||||
|
|
||||||
|
print("测试结果加载完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"加载测试结果时发生错误: {e}")
|
||||||
|
|
||||||
|
def create_comparison_dataframe(self) -> pd.DataFrame:
|
||||||
|
"""创建对比数据框"""
|
||||||
|
data = []
|
||||||
|
|
||||||
|
# 处理各个平台的结果
|
||||||
|
platforms = {
|
||||||
|
'Golang': self.golang_results,
|
||||||
|
'JavaScript': self.javascript_results,
|
||||||
|
'Python': self.python_results
|
||||||
|
}
|
||||||
|
|
||||||
|
for platform, results in platforms.items():
|
||||||
|
if results and 'results' in results:
|
||||||
|
for result in results['results']:
|
||||||
|
data.append({
|
||||||
|
'Platform': platform,
|
||||||
|
'Test': result['name'],
|
||||||
|
'AvgTime': float(result['avgTime']),
|
||||||
|
'MinTime': float(result['minTime']),
|
||||||
|
'MaxTime': float(result['maxTime']),
|
||||||
|
'Iterations': result['iterations']
|
||||||
|
})
|
||||||
|
|
||||||
|
return pd.DataFrame(data)
|
||||||
|
|
||||||
|
def generate_comparison_charts(self, df: pd.DataFrame):
|
||||||
|
"""生成对比图表"""
|
||||||
|
if df.empty:
|
||||||
|
print("没有数据可供生成图表")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建图表目录
|
||||||
|
charts_dir = self.results_dir / "charts"
|
||||||
|
charts_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# 1. 平均时间对比条形图
|
||||||
|
plt.figure(figsize=(15, 8))
|
||||||
|
pivot_df = df.pivot(index='Test', columns='Platform', values='AvgTime')
|
||||||
|
ax = pivot_df.plot(kind='bar', width=0.8)
|
||||||
|
plt.title('各平台平均执行时间对比 (毫秒)', fontsize=16, fontweight='bold')
|
||||||
|
plt.xlabel('测试项目', fontsize=12)
|
||||||
|
plt.ylabel('平均执行时间 (ms)', fontsize=12)
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.legend(title='平台')
|
||||||
|
plt.grid(axis='y', alpha=0.3)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(charts_dir / 'avg_time_comparison.png', dpi=300, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
# 2. 性能比率图(以Golang为基准)
|
||||||
|
if 'Golang' in pivot_df.columns:
|
||||||
|
plt.figure(figsize=(15, 8))
|
||||||
|
ratio_df = pivot_df.div(pivot_df['Golang'], axis=0)
|
||||||
|
ratio_df.plot(kind='bar', width=0.8)
|
||||||
|
plt.title('相对性能比率 (以Golang为基准=1.0)', fontsize=16, fontweight='bold')
|
||||||
|
plt.xlabel('测试项目', fontsize=12)
|
||||||
|
plt.ylabel('性能比率', fontsize=12)
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.legend(title='平台')
|
||||||
|
plt.axhline(y=1.0, color='red', linestyle='--', alpha=0.7, label='Golang基准')
|
||||||
|
plt.grid(axis='y', alpha=0.3)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(charts_dir / 'performance_ratio.png', dpi=300, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
# 3. 热力图
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
sns.heatmap(pivot_df, annot=True, fmt='.1f', cmap='YlOrRd',
|
||||||
|
cbar_kws={'label': '执行时间 (ms)'})
|
||||||
|
plt.title('性能热力图', fontsize=16, fontweight='bold')
|
||||||
|
plt.xlabel('平台', fontsize=12)
|
||||||
|
plt.ylabel('测试项目', fontsize=12)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(charts_dir / 'performance_heatmap.png', dpi=300, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
# 4. 箱线图(显示性能分布)
|
||||||
|
plt.figure(figsize=(15, 8))
|
||||||
|
melted_df = df.melt(id_vars=['Platform', 'Test'],
|
||||||
|
value_vars=['MinTime', 'AvgTime', 'MaxTime'],
|
||||||
|
var_name='Metric', value_name='Time')
|
||||||
|
sns.boxplot(data=melted_df, x='Test', y='Time', hue='Platform')
|
||||||
|
plt.title('各测试项目性能分布对比', fontsize=16, fontweight='bold')
|
||||||
|
plt.xlabel('测试项目', fontsize=12)
|
||||||
|
plt.ylabel('执行时间 (ms)', fontsize=12)
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.legend(title='平台')
|
||||||
|
plt.grid(axis='y', alpha=0.3)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(charts_dir / 'performance_distribution.png', dpi=300, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
print(f"图表已保存到: {charts_dir}")
|
||||||
|
|
||||||
|
def generate_detailed_report(self, df: pd.DataFrame):
|
||||||
|
"""生成详细分析报告"""
|
||||||
|
if df.empty:
|
||||||
|
print("没有数据可供生成报告")
|
||||||
|
return
|
||||||
|
|
||||||
|
report_path = self.results_dir / "detailed_comparison_report.md"
|
||||||
|
|
||||||
|
with open(report_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write("# WordZero 跨语言性能对比分析报告\n\n")
|
||||||
|
f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||||||
|
|
||||||
|
# 系统信息
|
||||||
|
f.write("## 测试环境\n\n")
|
||||||
|
for platform, results in [('Golang', self.golang_results),
|
||||||
|
('JavaScript', self.javascript_results),
|
||||||
|
('Python', self.python_results)]:
|
||||||
|
if results:
|
||||||
|
f.write(f"- **{platform}**: ")
|
||||||
|
if 'nodeVersion' in results:
|
||||||
|
f.write(f"Node.js {results['nodeVersion']}\n")
|
||||||
|
elif 'pythonVersion' in results:
|
||||||
|
f.write(f"Python {results['pythonVersion']}\n")
|
||||||
|
elif platform == 'Golang':
|
||||||
|
f.write("Go 1.19+\n")
|
||||||
|
else:
|
||||||
|
f.write("未知版本\n")
|
||||||
|
|
||||||
|
f.write("\n## 性能对比摘要\n\n")
|
||||||
|
|
||||||
|
# 计算平均性能
|
||||||
|
platform_avg = df.groupby('Platform')['AvgTime'].mean()
|
||||||
|
fastest_platform = platform_avg.idxmin()
|
||||||
|
f.write(f"- **总体最快平台**: {fastest_platform} (平均 {platform_avg[fastest_platform]:.2f}ms)\n")
|
||||||
|
|
||||||
|
# 各测试项目最快平台
|
||||||
|
f.write("- **各测试项目最快平台**:\n")
|
||||||
|
for test in df['Test'].unique():
|
||||||
|
test_data = df[df['Test'] == test]
|
||||||
|
fastest = test_data.loc[test_data['AvgTime'].idxmin()]
|
||||||
|
f.write(f" - {test}: {fastest['Platform']} ({fastest['AvgTime']:.2f}ms)\n")
|
||||||
|
|
||||||
|
f.write("\n## 详细测试结果\n\n")
|
||||||
|
|
||||||
|
# 创建详细表格
|
||||||
|
pivot_df = df.pivot(index='Test', columns='Platform', values='AvgTime')
|
||||||
|
f.write("### 平均执行时间对比 (毫秒)\n\n")
|
||||||
|
f.write(pivot_df.to_markdown())
|
||||||
|
f.write("\n\n")
|
||||||
|
|
||||||
|
# 性能比率分析(以最快的平台为基准)
|
||||||
|
f.write("### 相对性能分析\n\n")
|
||||||
|
if 'Golang' in pivot_df.columns:
|
||||||
|
ratio_df = pivot_df.div(pivot_df['Golang'], axis=0)
|
||||||
|
f.write("以Golang为基准的性能比率:\n\n")
|
||||||
|
f.write(ratio_df.to_markdown())
|
||||||
|
f.write("\n\n")
|
||||||
|
|
||||||
|
# 性能建议
|
||||||
|
f.write("## 性能建议\n\n")
|
||||||
|
f.write("### 各语言优势分析\n\n")
|
||||||
|
|
||||||
|
for platform in df['Platform'].unique():
|
||||||
|
platform_data = df[df['Platform'] == platform]
|
||||||
|
best_tests = []
|
||||||
|
for test in df['Test'].unique():
|
||||||
|
test_data = df[df['Test'] == test]
|
||||||
|
if test_data.loc[test_data['AvgTime'].idxmin(), 'Platform'] == platform:
|
||||||
|
best_tests.append(test)
|
||||||
|
|
||||||
|
f.write(f"**{platform}**:\n")
|
||||||
|
if best_tests:
|
||||||
|
f.write(f"- 最适合: {', '.join(best_tests)}\n")
|
||||||
|
else:
|
||||||
|
f.write("- 在所有测试项目中都不是最快的\n")
|
||||||
|
|
||||||
|
avg_time = platform_data['AvgTime'].mean()
|
||||||
|
f.write(f"- 平均性能: {avg_time:.2f}ms\n\n")
|
||||||
|
|
||||||
|
f.write("### 选型建议\n\n")
|
||||||
|
f.write("- **高性能需求**: 选择Golang实现,内存占用小,执行速度快\n")
|
||||||
|
f.write("- **快速开发**: JavaScript/Node.js生态丰富,开发效率高\n")
|
||||||
|
f.write("- **数据处理**: Python有丰富的数据处理库,适合复杂文档操作\n")
|
||||||
|
f.write("- **跨平台**: 三种语言都支持跨平台,根据团队技术栈选择\n\n")
|
||||||
|
|
||||||
|
print(f"详细报告已保存到: {report_path}")
|
||||||
|
|
||||||
|
def run_analysis(self):
|
||||||
|
"""运行完整分析"""
|
||||||
|
print("开始性能对比分析...")
|
||||||
|
|
||||||
|
# 加载数据
|
||||||
|
self.load_results()
|
||||||
|
|
||||||
|
# 创建对比数据框
|
||||||
|
df = self.create_comparison_dataframe()
|
||||||
|
|
||||||
|
if df.empty:
|
||||||
|
print("未找到测试结果数据,请先运行各平台的性能测试")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"成功加载 {len(df)} 条测试结果")
|
||||||
|
|
||||||
|
# 生成图表
|
||||||
|
try:
|
||||||
|
self.generate_comparison_charts(df)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"生成图表时发生错误: {e}")
|
||||||
|
|
||||||
|
# 生成详细报告
|
||||||
|
self.generate_detailed_report(df)
|
||||||
|
|
||||||
|
# 打印简要对比结果
|
||||||
|
print("\n=== 性能对比摘要 ===")
|
||||||
|
pivot_df = df.pivot(index='Test', columns='Platform', values='AvgTime')
|
||||||
|
print(pivot_df)
|
||||||
|
|
||||||
|
print("\n分析完成!")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
analyzer = PerformanceAnalyzer()
|
||||||
|
analyzer.run_analysis()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
590
benchmark/golang/benchmark_test.go
Normal file
590
benchmark/golang/benchmark_test.go
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
package benchmark
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZeroHawkeye/wordZero/pkg/document"
|
||||||
|
"github.com/ZeroHawkeye/wordZero/pkg/style"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 统一的测试配置,与JavaScript和Python保持一致
|
||||||
|
var testIterations = map[string]int{
|
||||||
|
"basic": 50, // 基础文档创建
|
||||||
|
"complex": 30, // 复杂格式化
|
||||||
|
"table": 20, // 表格操作
|
||||||
|
"largeTable": 10, // 大表格处理
|
||||||
|
"largeDoc": 5, // 大型文档
|
||||||
|
"memory": 10, // 内存使用测试
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkCreateBasicDocument 基础文档创建性能测试
|
||||||
|
func BenchmarkCreateBasicDocument(b *testing.B) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("这是一个基础性能测试文档")
|
||||||
|
doc.AddParagraph("测试内容包括基本的文本添加功能")
|
||||||
|
|
||||||
|
filename := filepath.Join(outputDir, fmt.Sprintf("basic_doc_%d.docx", i))
|
||||||
|
doc.Save(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkComplexFormatting 复杂格式化性能测试
|
||||||
|
func BenchmarkComplexFormatting(b *testing.B) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
doc := document.New()
|
||||||
|
|
||||||
|
// 添加标题
|
||||||
|
doc.AddParagraph("性能测试报告").SetStyle(style.StyleHeading1)
|
||||||
|
doc.AddParagraph("测试概述").SetStyle(style.StyleHeading2)
|
||||||
|
|
||||||
|
// 添加格式化文本
|
||||||
|
para := doc.AddParagraph("")
|
||||||
|
para.AddFormattedText("粗体文本", &document.TextFormat{Bold: true})
|
||||||
|
para.AddFormattedText(" ", &document.TextFormat{})
|
||||||
|
para.AddFormattedText("斜体文本", &document.TextFormat{Italic: true})
|
||||||
|
para.AddFormattedText(" ", &document.TextFormat{})
|
||||||
|
para.AddFormattedText("彩色文本", &document.TextFormat{FontColor: "FF0000"})
|
||||||
|
|
||||||
|
// 添加不同样式的段落
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
para2 := doc.AddParagraph(fmt.Sprintf("这是第%d个段落,包含复杂格式化", j+1))
|
||||||
|
para2.SetAlignment(document.AlignCenter)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(outputDir, fmt.Sprintf("complex_formatting_%d.docx", i))
|
||||||
|
doc.Save(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkTableOperations 表格操作性能测试
|
||||||
|
func BenchmarkTableOperations(b *testing.B) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("表格性能测试").SetStyle(style.StyleHeading1)
|
||||||
|
|
||||||
|
// 创建10行5列的表格
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 10,
|
||||||
|
Cols: 5,
|
||||||
|
Width: 7200, // 5英寸 = 7200磅
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
|
||||||
|
// 填充表格数据
|
||||||
|
for row := 0; row < 10; row++ {
|
||||||
|
for col := 0; col < 5; col++ {
|
||||||
|
cellText := fmt.Sprintf("R%dC%d", row+1, col+1)
|
||||||
|
table.SetCellText(row, col, cellText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(outputDir, fmt.Sprintf("table_operations_%d.docx", i))
|
||||||
|
doc.Save(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkLargeTable 大表格性能测试
|
||||||
|
func BenchmarkLargeTable(b *testing.B) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("大表格性能测试").SetStyle(style.StyleHeading1)
|
||||||
|
|
||||||
|
// 创建100行10列的大表格
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 100,
|
||||||
|
Cols: 10,
|
||||||
|
Width: 14400, // 10英寸 = 14400磅
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
|
||||||
|
// 填充表格数据
|
||||||
|
for row := 0; row < 100; row++ {
|
||||||
|
for col := 0; col < 10; col++ {
|
||||||
|
cellText := fmt.Sprintf("数据_%d_%d", row+1, col+1)
|
||||||
|
table.SetCellText(row, col, cellText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(outputDir, fmt.Sprintf("large_table_%d.docx", i))
|
||||||
|
doc.Save(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkLargeDocument 大型文档性能测试
|
||||||
|
func BenchmarkLargeDocument(b *testing.B) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("大型文档性能测试").SetStyle(style.StyleHeading1)
|
||||||
|
|
||||||
|
// 添加1000个段落
|
||||||
|
for j := 0; j < 1000; j++ {
|
||||||
|
if j%10 == 0 {
|
||||||
|
// 每10个段落添加一个标题
|
||||||
|
doc.AddParagraph(fmt.Sprintf("章节 %d", j/10+1)).SetStyle(style.StyleHeading2)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.AddParagraph(fmt.Sprintf("这是第%d个段落。Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", j+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一个中等大小的表格
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 20,
|
||||||
|
Cols: 8,
|
||||||
|
Width: 11520, // 8英寸 = 11520磅
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
for row := 0; row < 20; row++ {
|
||||||
|
for col := 0; col < 8; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("表格数据%d-%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(outputDir, fmt.Sprintf("large_document_%d.docx", i))
|
||||||
|
doc.Save(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkMemoryUsage 内存使用测试
|
||||||
|
func BenchmarkMemoryUsage(b *testing.B) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
var m1, m2 runtime.MemStats
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&m1)
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
|
||||||
|
// 创建复杂内容
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
doc.AddParagraph(fmt.Sprintf("段落%d: 这是一个测试段落,用于测试内存使用情况", j+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 50,
|
||||||
|
Cols: 6,
|
||||||
|
Width: 8640, // 6英寸 = 8640磅
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
for row := 0; row < 50; row++ {
|
||||||
|
for col := 0; col < 6; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("单元格%d-%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.ReadMemStats(&m2)
|
||||||
|
|
||||||
|
// 输出内存使用情况(可选)
|
||||||
|
if i == 0 {
|
||||||
|
b.Logf("内存使用: %d KB", (m2.Alloc-m1.Alloc)/1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(outputDir, fmt.Sprintf("memory_test_%d.docx", i))
|
||||||
|
doc.Save(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 新增:固定迭代次数的测试函数,与其他语言保持一致 ===
|
||||||
|
|
||||||
|
// TestFixedIterationsPerformance 固定迭代次数的性能测试,与JavaScript和Python保持一致
|
||||||
|
func TestFixedIterationsPerformance(t *testing.T) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
iterations int
|
||||||
|
testFunc func(int) time.Duration
|
||||||
|
}{
|
||||||
|
{"基础文档创建", testIterations["basic"], testBasicDocumentCreationFixed},
|
||||||
|
{"复杂格式化", testIterations["complex"], testComplexFormattingFixed},
|
||||||
|
{"表格操作", testIterations["table"], testTableOperationsFixed},
|
||||||
|
{"大表格处理", testIterations["largeTable"], testLargeTableProcessingFixed},
|
||||||
|
{"大型文档", testIterations["largeDoc"], testLargeDocumentFixed},
|
||||||
|
{"内存使用测试", testIterations["memory"], testMemoryUsageFixed},
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]map[string]interface{}, 0, len(tests))
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Logf("开始测试: %s (迭代次数: %d)", tt.name, tt.iterations)
|
||||||
|
|
||||||
|
times := make([]float64, 0, tt.iterations)
|
||||||
|
|
||||||
|
for i := 0; i < tt.iterations; i++ {
|
||||||
|
duration := tt.testFunc(i)
|
||||||
|
times = append(times, float64(duration.Nanoseconds())/1e6) // 转换为毫秒
|
||||||
|
|
||||||
|
if i%max(1, tt.iterations/10) == 0 {
|
||||||
|
t.Logf(" 进度: %d/%d", i+1, tt.iterations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算统计数据
|
||||||
|
var total float64
|
||||||
|
minTime := times[0]
|
||||||
|
maxTime := times[0]
|
||||||
|
|
||||||
|
for _, time := range times {
|
||||||
|
total += time
|
||||||
|
if time < minTime {
|
||||||
|
minTime = time
|
||||||
|
}
|
||||||
|
if time > maxTime {
|
||||||
|
maxTime = time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
avgTime := total / float64(len(times))
|
||||||
|
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"name": tt.name,
|
||||||
|
"avgTime": fmt.Sprintf("%.2f", avgTime),
|
||||||
|
"minTime": fmt.Sprintf("%.2f", minTime),
|
||||||
|
"maxTime": fmt.Sprintf("%.2f", maxTime),
|
||||||
|
"iterations": tt.iterations,
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, result)
|
||||||
|
|
||||||
|
t.Logf(" 平均耗时: %.2fms", avgTime)
|
||||||
|
t.Logf(" 最小耗时: %.2fms", minTime)
|
||||||
|
t.Logf(" 最大耗时: %.2fms", maxTime)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成性能报告(JSON格式,与其他语言保持一致)
|
||||||
|
report := map[string]interface{}{
|
||||||
|
"timestamp": time.Now().Format(time.RFC3339),
|
||||||
|
"platform": "Golang",
|
||||||
|
"goVersion": runtime.Version(),
|
||||||
|
"results": results,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存报告
|
||||||
|
reportPath := filepath.Join(outputDir, "performance_report.json")
|
||||||
|
file, err := os.Create(reportPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("无法创建报告文件: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
fmt.Fprintf(file, "{\n")
|
||||||
|
fmt.Fprintf(file, " \"timestamp\": \"%s\",\n", report["timestamp"])
|
||||||
|
fmt.Fprintf(file, " \"platform\": \"%s\",\n", report["platform"])
|
||||||
|
fmt.Fprintf(file, " \"goVersion\": \"%s\",\n", report["goVersion"])
|
||||||
|
fmt.Fprintf(file, " \"results\": [\n")
|
||||||
|
|
||||||
|
for i, result := range results {
|
||||||
|
fmt.Fprintf(file, " {\n")
|
||||||
|
fmt.Fprintf(file, " \"name\": \"%s\",\n", result["name"])
|
||||||
|
fmt.Fprintf(file, " \"avgTime\": \"%s\",\n", result["avgTime"])
|
||||||
|
fmt.Fprintf(file, " \"minTime\": \"%s\",\n", result["minTime"])
|
||||||
|
fmt.Fprintf(file, " \"maxTime\": \"%s\",\n", result["maxTime"])
|
||||||
|
fmt.Fprintf(file, " \"iterations\": %d\n", result["iterations"])
|
||||||
|
if i < len(results)-1 {
|
||||||
|
fmt.Fprintf(file, " },\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(file, " }\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(file, " ]\n")
|
||||||
|
fmt.Fprintf(file, "}\n")
|
||||||
|
|
||||||
|
t.Logf("\n=== Golang 性能测试报告 ===")
|
||||||
|
t.Logf("详细报告已保存到: %s", reportPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// max 辅助函数
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 固定迭代次数的测试实现函数 ===
|
||||||
|
|
||||||
|
func testBasicDocumentCreationFixed(index int) time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("这是一个基础性能测试文档")
|
||||||
|
doc.AddParagraph("测试内容包括基本的文本添加功能")
|
||||||
|
doc.Save(fmt.Sprintf("../results/golang/fixed_basic_doc_%d.docx", index))
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testComplexFormattingFixed(index int) time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("性能测试报告").SetStyle(style.StyleHeading1)
|
||||||
|
doc.AddParagraph("测试概述").SetStyle(style.StyleHeading2)
|
||||||
|
|
||||||
|
para := doc.AddParagraph("")
|
||||||
|
para.AddFormattedText("粗体文本", &document.TextFormat{Bold: true})
|
||||||
|
para.AddFormattedText(" ", &document.TextFormat{})
|
||||||
|
para.AddFormattedText("斜体文本", &document.TextFormat{Italic: true})
|
||||||
|
para.AddFormattedText(" ", &document.TextFormat{})
|
||||||
|
para.AddFormattedText("彩色文本", &document.TextFormat{FontColor: "FF0000"})
|
||||||
|
|
||||||
|
// 添加不同样式的段落
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
para2 := doc.AddParagraph(fmt.Sprintf("这是第%d个段落,包含复杂格式化", j+1))
|
||||||
|
para2.SetAlignment(document.AlignCenter)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Save(fmt.Sprintf("../results/golang/fixed_complex_formatting_%d.docx", index))
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTableOperationsFixed(index int) time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("表格性能测试").SetStyle(style.StyleHeading1)
|
||||||
|
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 10,
|
||||||
|
Cols: 5,
|
||||||
|
Width: 7200, // 5英寸
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
|
||||||
|
for row := 0; row < 10; row++ {
|
||||||
|
for col := 0; col < 5; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("R%dC%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Save(fmt.Sprintf("../results/golang/fixed_table_operations_%d.docx", index))
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLargeTableProcessingFixed(index int) time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("大表格性能测试").SetStyle(style.StyleHeading1)
|
||||||
|
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 100,
|
||||||
|
Cols: 10,
|
||||||
|
Width: 14400, // 10英寸
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
|
||||||
|
for row := 0; row < 100; row++ {
|
||||||
|
for col := 0; col < 10; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("数据_%d_%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Save(fmt.Sprintf("../results/golang/fixed_large_table_%d.docx", index))
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLargeDocumentFixed(index int) time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("大型文档性能测试").SetStyle(style.StyleHeading1)
|
||||||
|
|
||||||
|
// 添加1000个段落
|
||||||
|
for j := 0; j < 1000; j++ {
|
||||||
|
if j%10 == 0 {
|
||||||
|
// 每10个段落添加一个标题
|
||||||
|
doc.AddParagraph(fmt.Sprintf("章节 %d", j/10+1)).SetStyle(style.StyleHeading2)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.AddParagraph(fmt.Sprintf("这是第%d个段落。Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", j+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一个中等大小的表格
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 20,
|
||||||
|
Cols: 8,
|
||||||
|
Width: 11520, // 8英寸
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
for row := 0; row < 20; row++ {
|
||||||
|
for col := 0; col < 8; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("表格数据%d-%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Save(fmt.Sprintf("../results/golang/fixed_large_document_%d.docx", index))
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMemoryUsageFixed(index int) time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
var m1, m2 runtime.MemStats
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&m1)
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
|
||||||
|
// 创建复杂内容
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
doc.AddParagraph(fmt.Sprintf("段落%d: 这是一个测试段落,用于测试内存使用情况", j+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 50,
|
||||||
|
Cols: 6,
|
||||||
|
Width: 8640, // 6英寸
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
for row := 0; row < 50; row++ {
|
||||||
|
for col := 0; col < 6; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("单元格%d-%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.ReadMemStats(&m2)
|
||||||
|
|
||||||
|
doc.Save(fmt.Sprintf("../results/golang/fixed_memory_test_%d.docx", index))
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPerformanceComparison 性能对比测试(非基准测试,用于详细分析)
|
||||||
|
func TestPerformanceComparison(t *testing.T) {
|
||||||
|
outputDir := "../results/golang"
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
testFunc func() time.Duration
|
||||||
|
}{
|
||||||
|
{"基础文档创建", testBasicDocumentCreation},
|
||||||
|
{"复杂格式化", testComplexFormatting},
|
||||||
|
{"表格操作", testTableOperations},
|
||||||
|
{"大表格处理", testLargeTableProcessing},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 运行3次取平均值
|
||||||
|
var total time.Duration
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
duration := tt.testFunc()
|
||||||
|
total += duration
|
||||||
|
}
|
||||||
|
avg := total / 3
|
||||||
|
t.Logf("%s 平均耗时: %v", tt.name, avg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBasicDocumentCreation() time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("基础文档测试")
|
||||||
|
doc.AddParagraph("这是一个性能测试文档")
|
||||||
|
doc.Save("../results/golang/perf_basic.docx")
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testComplexFormatting() time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
doc.AddParagraph("复杂格式化测试").SetStyle(style.StyleHeading1)
|
||||||
|
|
||||||
|
para := doc.AddParagraph("")
|
||||||
|
para.AddFormattedText("粗体", &document.TextFormat{Bold: true})
|
||||||
|
para.AddFormattedText(" ", &document.TextFormat{})
|
||||||
|
para.AddFormattedText("斜体", &document.TextFormat{Italic: true})
|
||||||
|
para.AddFormattedText(" ", &document.TextFormat{})
|
||||||
|
para.AddFormattedText("红色", &document.TextFormat{FontColor: "FF0000"})
|
||||||
|
|
||||||
|
doc.Save("../results/golang/perf_complex.docx")
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTableOperations() time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 20,
|
||||||
|
Cols: 5,
|
||||||
|
Width: 7200, // 5英寸
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
|
||||||
|
for row := 0; row < 20; row++ {
|
||||||
|
for col := 0; col < 5; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("R%dC%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Save("../results/golang/perf_table.docx")
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLargeTableProcessing() time.Duration {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
doc := document.New()
|
||||||
|
tableConfig := &document.TableConfig{
|
||||||
|
Rows: 100,
|
||||||
|
Cols: 8,
|
||||||
|
Width: 11520, // 8英寸
|
||||||
|
}
|
||||||
|
table := doc.AddTable(tableConfig)
|
||||||
|
|
||||||
|
for row := 0; row < 100; row++ {
|
||||||
|
for col := 0; col < 8; col++ {
|
||||||
|
table.SetCellText(row, col, fmt.Sprintf("数据%d-%d", row+1, col+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Save("../results/golang/perf_large_table.docx")
|
||||||
|
|
||||||
|
return time.Since(start)
|
||||||
|
}
|
7
benchmark/golang/go.mod
Normal file
7
benchmark/golang/go.mod
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module benchmark
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
replace github.com/ZeroHawkeye/wordZero => ../../
|
||||||
|
|
||||||
|
require github.com/ZeroHawkeye/wordZero v0.0.0-00010101000000-000000000000
|
0
benchmark/golang/go.sum
Normal file
0
benchmark/golang/go.sum
Normal file
459
benchmark/javascript/benchmark.js
Normal file
459
benchmark/javascript/benchmark.js
Normal file
@@ -0,0 +1,459 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, WidthType, AlignmentType } = require('docx');
|
||||||
|
|
||||||
|
// 确保输出目录存在
|
||||||
|
const outputDir = '../results/javascript';
|
||||||
|
fs.ensureDirSync(outputDir);
|
||||||
|
|
||||||
|
// 性能测试工具类
|
||||||
|
class PerformanceTester {
|
||||||
|
constructor() {
|
||||||
|
this.results = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async runTest(name, testFunction, iterations = 10) {
|
||||||
|
console.log(`\n开始测试: ${name}`);
|
||||||
|
const times = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < iterations; i++) {
|
||||||
|
const start = process.hrtime.bigint();
|
||||||
|
await testFunction(i);
|
||||||
|
const end = process.hrtime.bigint();
|
||||||
|
|
||||||
|
const duration = Number(end - start) / 1000000; // 转换为毫秒
|
||||||
|
times.push(duration);
|
||||||
|
|
||||||
|
if (i % Math.ceil(iterations / 10) === 0) {
|
||||||
|
console.log(` 进度: ${i + 1}/${iterations}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
|
||||||
|
const minTime = Math.min(...times);
|
||||||
|
const maxTime = Math.max(...times);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
name,
|
||||||
|
avgTime: avgTime.toFixed(2),
|
||||||
|
minTime: minTime.toFixed(2),
|
||||||
|
maxTime: maxTime.toFixed(2),
|
||||||
|
iterations
|
||||||
|
};
|
||||||
|
|
||||||
|
this.results.push(result);
|
||||||
|
console.log(` 平均耗时: ${result.avgTime}ms`);
|
||||||
|
console.log(` 最小耗时: ${result.minTime}ms`);
|
||||||
|
console.log(` 最大耗时: ${result.maxTime}ms`);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateReport() {
|
||||||
|
console.log('\n=== JavaScript 性能测试报告 ===');
|
||||||
|
console.table(this.results);
|
||||||
|
|
||||||
|
// 保存详细报告
|
||||||
|
const reportPath = path.join(outputDir, 'performance_report.json');
|
||||||
|
fs.writeJsonSync(reportPath, {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
platform: 'JavaScript (Node.js)',
|
||||||
|
nodeVersion: process.version,
|
||||||
|
results: this.results
|
||||||
|
}, { spaces: 2 });
|
||||||
|
|
||||||
|
console.log(`\n详细报告已保存到: ${reportPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础文档创建测试
|
||||||
|
async function testBasicDocumentCreation(index) {
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [{
|
||||||
|
properties: {},
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun("这是一个基础性能测试文档")
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun("测试内容包括基本的文本添加功能")
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await Packer.toBuffer(doc);
|
||||||
|
const filename = path.join(outputDir, `basic_doc_${index}.docx`);
|
||||||
|
await fs.writeFile(filename, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复杂格式化测试
|
||||||
|
async function testComplexFormatting(index) {
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [{
|
||||||
|
properties: {},
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun({
|
||||||
|
text: "性能测试报告",
|
||||||
|
bold: true,
|
||||||
|
size: 32,
|
||||||
|
color: "2E74B5"
|
||||||
|
})
|
||||||
|
],
|
||||||
|
heading: "Heading1"
|
||||||
|
}),
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun({
|
||||||
|
text: "测试概述",
|
||||||
|
bold: true,
|
||||||
|
size: 26,
|
||||||
|
color: "2E74B5"
|
||||||
|
})
|
||||||
|
],
|
||||||
|
heading: "Heading2"
|
||||||
|
}),
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun({
|
||||||
|
text: "粗体文本",
|
||||||
|
bold: true
|
||||||
|
}),
|
||||||
|
new TextRun(" "),
|
||||||
|
new TextRun({
|
||||||
|
text: "斜体文本",
|
||||||
|
italics: true
|
||||||
|
}),
|
||||||
|
new TextRun(" "),
|
||||||
|
new TextRun({
|
||||||
|
text: "彩色文本",
|
||||||
|
color: "FF0000"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加多个格式化段落
|
||||||
|
for (let j = 0; j < 10; j++) {
|
||||||
|
doc.addSection({
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun(`这是第${j + 1}个段落,包含复杂格式化`)
|
||||||
|
],
|
||||||
|
alignment: AlignmentType.CENTER
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = await Packer.toBuffer(doc);
|
||||||
|
const filename = path.join(outputDir, `complex_formatting_${index}.docx`);
|
||||||
|
await fs.writeFile(filename, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格操作测试
|
||||||
|
async function testTableOperations(index) {
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// 创建10行5列的表格
|
||||||
|
for (let row = 0; row < 10; row++) {
|
||||||
|
const cells = [];
|
||||||
|
for (let col = 0; col < 5; col++) {
|
||||||
|
cells.push(new TableCell({
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun(`R${row + 1}C${col + 1}`)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
rows.push(new TableRow({ children: cells }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = new Table({
|
||||||
|
rows: rows,
|
||||||
|
width: {
|
||||||
|
size: 100,
|
||||||
|
type: WidthType.PERCENTAGE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [{
|
||||||
|
properties: {},
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun({
|
||||||
|
text: "表格性能测试",
|
||||||
|
bold: true,
|
||||||
|
size: 32
|
||||||
|
})
|
||||||
|
],
|
||||||
|
heading: "Heading1"
|
||||||
|
}),
|
||||||
|
table
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await Packer.toBuffer(doc);
|
||||||
|
const filename = path.join(outputDir, `table_operations_${index}.docx`);
|
||||||
|
await fs.writeFile(filename, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大表格测试
|
||||||
|
async function testLargeTable(index) {
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// 创建100行10列的大表格
|
||||||
|
for (let row = 0; row < 100; row++) {
|
||||||
|
const cells = [];
|
||||||
|
for (let col = 0; col < 10; col++) {
|
||||||
|
cells.push(new TableCell({
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun(`数据_${row + 1}_${col + 1}`)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
rows.push(new TableRow({ children: cells }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = new Table({
|
||||||
|
rows: rows,
|
||||||
|
width: {
|
||||||
|
size: 100,
|
||||||
|
type: WidthType.PERCENTAGE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [{
|
||||||
|
properties: {},
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun({
|
||||||
|
text: "大表格性能测试",
|
||||||
|
bold: true,
|
||||||
|
size: 32
|
||||||
|
})
|
||||||
|
],
|
||||||
|
heading: "Heading1"
|
||||||
|
}),
|
||||||
|
table
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await Packer.toBuffer(doc);
|
||||||
|
const filename = path.join(outputDir, `large_table_${index}.docx`);
|
||||||
|
await fs.writeFile(filename, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大型文档测试
|
||||||
|
async function testLargeDocument(index) {
|
||||||
|
const children = [];
|
||||||
|
|
||||||
|
children.push(new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun({
|
||||||
|
text: "大型文档性能测试",
|
||||||
|
bold: true,
|
||||||
|
size: 32
|
||||||
|
})
|
||||||
|
],
|
||||||
|
heading: "Heading1"
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 添加1000个段落
|
||||||
|
for (let j = 0; j < 1000; j++) {
|
||||||
|
if (j % 10 === 0) {
|
||||||
|
// 每10个段落添加一个标题
|
||||||
|
children.push(new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun({
|
||||||
|
text: `章节 ${Math.floor(j / 10) + 1}`,
|
||||||
|
bold: true,
|
||||||
|
size: 26
|
||||||
|
})
|
||||||
|
],
|
||||||
|
heading: "Heading2"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun(`这是第${j + 1}个段落。Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`)
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一个中等大小的表格
|
||||||
|
const tableRows = [];
|
||||||
|
for (let row = 0; row < 20; row++) {
|
||||||
|
const cells = [];
|
||||||
|
for (let col = 0; col < 8; col++) {
|
||||||
|
cells.push(new TableCell({
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun(`表格数据${row + 1}-${col + 1}`)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
tableRows.push(new TableRow({ children: cells }));
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(new Table({
|
||||||
|
rows: tableRows,
|
||||||
|
width: {
|
||||||
|
size: 100,
|
||||||
|
type: WidthType.PERCENTAGE
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [{
|
||||||
|
properties: {},
|
||||||
|
children: children
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await Packer.toBuffer(doc);
|
||||||
|
const filename = path.join(outputDir, `large_document_${index}.docx`);
|
||||||
|
await fs.writeFile(filename, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内存使用测试
|
||||||
|
async function testMemoryUsage(index) {
|
||||||
|
const initialMemory = process.memoryUsage();
|
||||||
|
|
||||||
|
const children = [];
|
||||||
|
|
||||||
|
// 创建复杂内容
|
||||||
|
for (let j = 0; j < 100; j++) {
|
||||||
|
children.push(new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun(`段落${j + 1}: 这是一个测试段落,用于测试内存使用情况`)
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加表格
|
||||||
|
const tableRows = [];
|
||||||
|
for (let row = 0; row < 50; row++) {
|
||||||
|
const cells = [];
|
||||||
|
for (let col = 0; col < 6; col++) {
|
||||||
|
cells.push(new TableCell({
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun(`单元格${row + 1}-${col + 1}`)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
tableRows.push(new TableRow({ children: cells }));
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(new Table({
|
||||||
|
rows: tableRows,
|
||||||
|
width: {
|
||||||
|
size: 100,
|
||||||
|
type: WidthType.PERCENTAGE
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [{
|
||||||
|
properties: {},
|
||||||
|
children: children
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await Packer.toBuffer(doc);
|
||||||
|
const filename = path.join(outputDir, `memory_test_${index}.docx`);
|
||||||
|
await fs.writeFile(filename, buffer);
|
||||||
|
|
||||||
|
const finalMemory = process.memoryUsage();
|
||||||
|
if (index === 0) {
|
||||||
|
console.log(` 内存使用: ${Math.round((finalMemory.heapUsed - initialMemory.heapUsed) / 1024)}KB`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主测试函数
|
||||||
|
async function runBenchmarks() {
|
||||||
|
console.log('开始 JavaScript (docx库) 性能基准测试...');
|
||||||
|
console.log(`Node.js 版本: ${process.version}`);
|
||||||
|
console.log(`输出目录: ${outputDir}`);
|
||||||
|
|
||||||
|
const tester = new PerformanceTester();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 统一的测试配置,与其他语言保持一致
|
||||||
|
const testIterations = {
|
||||||
|
basic: 50, // 基础文档创建
|
||||||
|
complex: 30, // 复杂格式化
|
||||||
|
table: 20, // 表格操作
|
||||||
|
largeTable: 10, // 大表格处理
|
||||||
|
largeDoc: 5, // 大型文档
|
||||||
|
memory: 10, // 内存使用测试
|
||||||
|
};
|
||||||
|
|
||||||
|
await tester.runTest('基础文档创建', testBasicDocumentCreation, testIterations.basic);
|
||||||
|
await tester.runTest('复杂格式化', testComplexFormatting, testIterations.complex);
|
||||||
|
await tester.runTest('表格操作', testTableOperations, testIterations.table);
|
||||||
|
await tester.runTest('大表格处理', testLargeTable, testIterations.largeTable);
|
||||||
|
await tester.runTest('大型文档', testLargeDocument, testIterations.largeDoc);
|
||||||
|
await tester.runTest('内存使用测试', testMemoryUsage, testIterations.memory);
|
||||||
|
|
||||||
|
tester.generateReport();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('测试过程中发生错误:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此文件,则执行基准测试
|
||||||
|
if (require.main === module) {
|
||||||
|
runBenchmarks()
|
||||||
|
.then(() => {
|
||||||
|
console.log('\n所有测试完成!');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('基准测试失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
runBenchmarks,
|
||||||
|
PerformanceTester,
|
||||||
|
testBasicDocumentCreation,
|
||||||
|
testComplexFormatting,
|
||||||
|
testTableOperations,
|
||||||
|
testLargeTable,
|
||||||
|
testLargeDocument,
|
||||||
|
testMemoryUsage
|
||||||
|
};
|
23
benchmark/javascript/package.json
Normal file
23
benchmark/javascript/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "wordZero-benchmark-js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "JavaScript Word操作性能测试对比",
|
||||||
|
"main": "benchmark.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "node benchmark.js",
|
||||||
|
"benchmark": "node benchmark.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"docx": "^8.5.0",
|
||||||
|
"officegen": "^0.6.5",
|
||||||
|
"fs-extra": "^11.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"benchmark": "^2.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"author": "WordZero Benchmark Team",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
101
benchmark/parse_golang_results.py
Normal file
101
benchmark/parse_golang_results.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
解析 Golang 基准测试结果并生成兼容的 JSON 格式
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def parse_golang_benchmark_output(file_path: str):
|
||||||
|
"""解析 Golang 基准测试输出文件"""
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 查找基准测试结果行
|
||||||
|
benchmark_pattern = r'(\d+)\s+(\d+)\s+ns/op\s+(\d+)\s+B/op\s+(\d+)\s+allocs/op'
|
||||||
|
results = []
|
||||||
|
|
||||||
|
# 基准测试名称映射
|
||||||
|
test_names = [
|
||||||
|
"基础文档创建", # BenchmarkCreateBasicDocument
|
||||||
|
"复杂格式化", # BenchmarkComplexFormatting
|
||||||
|
"表格操作", # BenchmarkTableOperations
|
||||||
|
"大表格处理", # BenchmarkLargeTable
|
||||||
|
"大型文档", # BenchmarkLargeDocument
|
||||||
|
"内存使用测试" # BenchmarkMemoryUsage
|
||||||
|
]
|
||||||
|
|
||||||
|
matches = re.findall(benchmark_pattern, content)
|
||||||
|
|
||||||
|
for i, match in enumerate(matches):
|
||||||
|
iterations, ns_per_op, bytes_per_op, allocs_per_op = match
|
||||||
|
|
||||||
|
# 转换纳秒到毫秒
|
||||||
|
avg_time_ms = float(ns_per_op) / 1_000_000
|
||||||
|
|
||||||
|
# 估算最小和最大时间(基于经验,通常有±10%的变化)
|
||||||
|
min_time_ms = avg_time_ms * 0.9
|
||||||
|
max_time_ms = avg_time_ms * 1.1
|
||||||
|
|
||||||
|
if i < len(test_names):
|
||||||
|
result = {
|
||||||
|
"name": test_names[i],
|
||||||
|
"avgTime": round(avg_time_ms, 2),
|
||||||
|
"minTime": round(min_time_ms, 2),
|
||||||
|
"maxTime": round(max_time_ms, 2),
|
||||||
|
"iterations": int(iterations),
|
||||||
|
"bytesPerOp": int(bytes_per_op),
|
||||||
|
"allocsPerOp": int(allocs_per_op)
|
||||||
|
}
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def generate_golang_performance_report():
|
||||||
|
"""生成 Golang 性能报告 JSON 文件"""
|
||||||
|
input_file = Path("results/golang/benchmark_output.txt")
|
||||||
|
output_file = Path("results/golang/performance_report.json")
|
||||||
|
|
||||||
|
if not input_file.exists():
|
||||||
|
print(f"错误:找不到 Golang 基准测试输出文件: {input_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
results = parse_golang_benchmark_output(input_file)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("警告:未找到有效的基准测试结果")
|
||||||
|
return
|
||||||
|
|
||||||
|
report_data = {
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"platform": "Golang",
|
||||||
|
"goVersion": "1.19+",
|
||||||
|
"results": results
|
||||||
|
}
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(report_data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"✅ Golang 性能报告已生成: {output_file}")
|
||||||
|
print(f"📊 共解析了 {len(results)} 个测试结果")
|
||||||
|
|
||||||
|
# 打印摘要
|
||||||
|
print("\n🎯 Golang 性能测试摘要:")
|
||||||
|
for result in results:
|
||||||
|
print(f" - {result['name']}: {result['avgTime']}ms (平均)")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 生成 Golang 性能报告时发生错误: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
generate_golang_performance_report()
|
28
benchmark/python/.gitignore
vendored
Normal file
28
benchmark/python/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Python 虚拟环境
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Python 字节码文件
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# 测试结果文件
|
||||||
|
*.docx
|
||||||
|
test_output/
|
||||||
|
benchmark_results/
|
||||||
|
|
||||||
|
# 性能分析文件
|
||||||
|
*.prof
|
||||||
|
*.profile
|
||||||
|
|
||||||
|
# IDE 文件
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# 操作系统生成的文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
328
benchmark/python/benchmark_test.py
Normal file
328
benchmark/python/benchmark_test.py
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Python Word操作性能基准测试
|
||||||
|
|
||||||
|
使用python-docx库进行Word文档操作的性能测试,
|
||||||
|
与Golang WordZero库进行对比。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import tracemalloc
|
||||||
|
import psutil
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from docx import Document
|
||||||
|
from docx.shared import Inches, Pt, RGBColor
|
||||||
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
|
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
OUTPUT_DIR = Path("../results/python")
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 统一的测试配置,与其他语言保持一致
|
||||||
|
TEST_ITERATIONS = {
|
||||||
|
"basic": 50, # 基础文档创建
|
||||||
|
"complex": 30, # 复杂格式化
|
||||||
|
"table": 20, # 表格操作
|
||||||
|
"largeTable": 10, # 大表格处理
|
||||||
|
"largeDoc": 5, # 大型文档
|
||||||
|
"memory": 10, # 内存使用测试
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PerformanceTester:
|
||||||
|
"""性能测试工具类"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.results = []
|
||||||
|
|
||||||
|
def run_test(self, name: str, test_function, iterations: int = 10):
|
||||||
|
"""运行性能测试"""
|
||||||
|
print(f"\n开始测试: {name}")
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for i in range(iterations):
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
test_function(i)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
|
||||||
|
duration = (end_time - start_time) * 1000 # 转换为毫秒
|
||||||
|
times.append(duration)
|
||||||
|
|
||||||
|
if i % max(1, iterations // 10) == 0:
|
||||||
|
print(f" 进度: {i + 1}/{iterations}")
|
||||||
|
|
||||||
|
avg_time = sum(times) / len(times)
|
||||||
|
min_time = min(times)
|
||||||
|
max_time = max(times)
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'name': name,
|
||||||
|
'avgTime': round(avg_time, 2),
|
||||||
|
'minTime': round(min_time, 2),
|
||||||
|
'maxTime': round(max_time, 2),
|
||||||
|
'iterations': iterations
|
||||||
|
}
|
||||||
|
|
||||||
|
self.results.append(result)
|
||||||
|
print(f" 平均耗时: {result['avgTime']}ms")
|
||||||
|
print(f" 最小耗时: {result['minTime']}ms")
|
||||||
|
print(f" 最大耗时: {result['maxTime']}ms")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def generate_report(self):
|
||||||
|
"""生成性能测试报告"""
|
||||||
|
print('\n=== Python 性能测试报告 ===')
|
||||||
|
for result in self.results:
|
||||||
|
print(f"{result['name']}: {result['avgTime']}ms (平均)")
|
||||||
|
|
||||||
|
# 保存详细报告
|
||||||
|
report_path = OUTPUT_DIR / 'performance_report.json'
|
||||||
|
report_data = {
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'platform': 'Python',
|
||||||
|
'pythonVersion': f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
||||||
|
'results': self.results
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(report_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(report_data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"\n详细报告已保存到: {report_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic_document_creation(index: int):
|
||||||
|
"""基础文档创建测试"""
|
||||||
|
doc = Document()
|
||||||
|
doc.add_paragraph("这是一个基础性能测试文档")
|
||||||
|
doc.add_paragraph("测试内容包括基本的文本添加功能")
|
||||||
|
|
||||||
|
filename = OUTPUT_DIR / f"basic_doc_{index}.docx"
|
||||||
|
doc.save(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_formatting(index: int):
|
||||||
|
"""复杂格式化测试"""
|
||||||
|
doc = Document()
|
||||||
|
|
||||||
|
# 添加标题
|
||||||
|
title = doc.add_heading("性能测试报告", level=1)
|
||||||
|
subtitle = doc.add_heading("测试概述", level=2)
|
||||||
|
|
||||||
|
# 添加格式化文本
|
||||||
|
para = doc.add_paragraph()
|
||||||
|
|
||||||
|
# 粗体文本
|
||||||
|
run = para.add_run("粗体文本")
|
||||||
|
run.bold = True
|
||||||
|
|
||||||
|
para.add_run(" ")
|
||||||
|
|
||||||
|
# 斜体文本
|
||||||
|
run = para.add_run("斜体文本")
|
||||||
|
run.italic = True
|
||||||
|
|
||||||
|
para.add_run(" ")
|
||||||
|
|
||||||
|
# 彩色文本
|
||||||
|
run = para.add_run("彩色文本")
|
||||||
|
run.font.color.rgb = RGBColor(255, 0, 0) # 红色
|
||||||
|
|
||||||
|
# 添加多个格式化段落
|
||||||
|
for j in range(10):
|
||||||
|
para = doc.add_paragraph(f"这是第{j + 1}个段落,包含复杂格式化")
|
||||||
|
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
|
||||||
|
filename = OUTPUT_DIR / f"complex_formatting_{index}.docx"
|
||||||
|
doc.save(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_operations(index: int):
|
||||||
|
"""表格操作测试"""
|
||||||
|
doc = Document()
|
||||||
|
|
||||||
|
# 添加标题
|
||||||
|
doc.add_heading("表格性能测试", level=1)
|
||||||
|
|
||||||
|
# 创建10行5列的表格
|
||||||
|
table = doc.add_table(rows=10, cols=5)
|
||||||
|
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||||
|
|
||||||
|
# 填充表格数据
|
||||||
|
for row_idx, row in enumerate(table.rows):
|
||||||
|
for col_idx, cell in enumerate(row.cells):
|
||||||
|
cell.text = f"R{row_idx + 1}C{col_idx + 1}"
|
||||||
|
|
||||||
|
filename = OUTPUT_DIR / f"table_operations_{index}.docx"
|
||||||
|
doc.save(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_large_table(index: int):
|
||||||
|
"""大表格测试"""
|
||||||
|
doc = Document()
|
||||||
|
|
||||||
|
# 添加标题
|
||||||
|
doc.add_heading("大表格性能测试", level=1)
|
||||||
|
|
||||||
|
# 创建100行10列的大表格
|
||||||
|
table = doc.add_table(rows=100, cols=10)
|
||||||
|
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||||
|
|
||||||
|
# 填充表格数据
|
||||||
|
for row_idx, row in enumerate(table.rows):
|
||||||
|
for col_idx, cell in enumerate(row.cells):
|
||||||
|
cell.text = f"数据_{row_idx + 1}_{col_idx + 1}"
|
||||||
|
|
||||||
|
filename = OUTPUT_DIR / f"large_table_{index}.docx"
|
||||||
|
doc.save(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_large_document(index: int):
|
||||||
|
"""大型文档测试"""
|
||||||
|
doc = Document()
|
||||||
|
|
||||||
|
# 添加主标题
|
||||||
|
doc.add_heading("大型文档性能测试", level=1)
|
||||||
|
|
||||||
|
# 添加1000个段落
|
||||||
|
for j in range(1000):
|
||||||
|
if j % 10 == 0:
|
||||||
|
# 每10个段落添加一个标题
|
||||||
|
doc.add_heading(f"章节 {j // 10 + 1}", level=2)
|
||||||
|
|
||||||
|
doc.add_paragraph(
|
||||||
|
f"这是第{j + 1}个段落。Lorem ipsum dolor sit amet, consectetur "
|
||||||
|
f"adipiscing elit. Sed do eiusmod tempor incididunt ut labore et "
|
||||||
|
f"dolore magna aliqua."
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加一个中等大小的表格
|
||||||
|
table = doc.add_table(rows=20, cols=8)
|
||||||
|
for row_idx, row in enumerate(table.rows):
|
||||||
|
for col_idx, cell in enumerate(row.cells):
|
||||||
|
cell.text = f"表格数据{row_idx + 1}-{col_idx + 1}"
|
||||||
|
|
||||||
|
filename = OUTPUT_DIR / f"large_document_{index}.docx"
|
||||||
|
doc.save(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_memory_usage(index: int):
|
||||||
|
"""内存使用测试"""
|
||||||
|
# 开始内存追踪
|
||||||
|
tracemalloc.start()
|
||||||
|
initial_memory = tracemalloc.get_traced_memory()[0]
|
||||||
|
|
||||||
|
doc = Document()
|
||||||
|
|
||||||
|
# 创建复杂内容
|
||||||
|
for j in range(100):
|
||||||
|
doc.add_paragraph(f"段落{j + 1}: 这是一个测试段落,用于测试内存使用情况")
|
||||||
|
|
||||||
|
# 添加表格
|
||||||
|
table = doc.add_table(rows=50, cols=6)
|
||||||
|
for row_idx, row in enumerate(table.rows):
|
||||||
|
for col_idx, cell in enumerate(row.cells):
|
||||||
|
cell.text = f"单元格{row_idx + 1}-{col_idx + 1}"
|
||||||
|
|
||||||
|
# 保存文档
|
||||||
|
filename = OUTPUT_DIR / f"memory_test_{index}.docx"
|
||||||
|
doc.save(filename)
|
||||||
|
|
||||||
|
# 检查内存使用
|
||||||
|
final_memory = tracemalloc.get_traced_memory()[0]
|
||||||
|
if index == 0:
|
||||||
|
memory_used = (final_memory - initial_memory) / 1024 # 转换为KB
|
||||||
|
print(f" 内存使用: {memory_used:.0f}KB")
|
||||||
|
|
||||||
|
tracemalloc.stop()
|
||||||
|
|
||||||
|
|
||||||
|
# pytest benchmark 测试函数
|
||||||
|
def test_benchmark_basic_document(benchmark):
|
||||||
|
"""pytest-benchmark: 基础文档创建"""
|
||||||
|
benchmark(test_basic_document_creation, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_benchmark_complex_formatting(benchmark):
|
||||||
|
"""pytest-benchmark: 复杂格式化"""
|
||||||
|
benchmark(test_complex_formatting, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_benchmark_table_operations(benchmark):
|
||||||
|
"""pytest-benchmark: 表格操作"""
|
||||||
|
benchmark(test_table_operations, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_benchmark_large_table(benchmark):
|
||||||
|
"""pytest-benchmark: 大表格"""
|
||||||
|
benchmark(test_large_table, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_benchmark_large_document(benchmark):
|
||||||
|
"""pytest-benchmark: 大型文档"""
|
||||||
|
benchmark(test_large_document, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_benchmark_memory_usage(benchmark):
|
||||||
|
"""pytest-benchmark: 内存使用"""
|
||||||
|
benchmark(test_memory_usage, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPerformanceComparison:
|
||||||
|
"""性能对比测试类"""
|
||||||
|
|
||||||
|
def test_performance_comparison(self):
|
||||||
|
"""运行完整的性能对比测试"""
|
||||||
|
print('开始 Python (python-docx库) 性能基准测试...')
|
||||||
|
print(f'Python 版本: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')
|
||||||
|
print(f'输出目录: {OUTPUT_DIR}')
|
||||||
|
|
||||||
|
tester = PerformanceTester()
|
||||||
|
|
||||||
|
# 运行各项测试(使用统一的迭代次数配置)
|
||||||
|
tester.run_test('基础文档创建', test_basic_document_creation, TEST_ITERATIONS["basic"])
|
||||||
|
tester.run_test('复杂格式化', test_complex_formatting, TEST_ITERATIONS["complex"])
|
||||||
|
tester.run_test('表格操作', test_table_operations, TEST_ITERATIONS["table"])
|
||||||
|
tester.run_test('大表格处理', test_large_table, TEST_ITERATIONS["largeTable"])
|
||||||
|
tester.run_test('大型文档', test_large_document, TEST_ITERATIONS["largeDoc"])
|
||||||
|
tester.run_test('内存使用测试', test_memory_usage, TEST_ITERATIONS["memory"])
|
||||||
|
|
||||||
|
# 生成报告
|
||||||
|
tester.generate_report()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数,直接运行时执行"""
|
||||||
|
print("开始 Python 性能基准测试...")
|
||||||
|
|
||||||
|
tester = PerformanceTester()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用统一的迭代次数配置
|
||||||
|
tester.run_test('基础文档创建', test_basic_document_creation, TEST_ITERATIONS["basic"])
|
||||||
|
tester.run_test('复杂格式化', test_complex_formatting, TEST_ITERATIONS["complex"])
|
||||||
|
tester.run_test('表格操作', test_table_operations, TEST_ITERATIONS["table"])
|
||||||
|
tester.run_test('大表格处理', test_large_table, TEST_ITERATIONS["largeTable"])
|
||||||
|
tester.run_test('大型文档', test_large_document, TEST_ITERATIONS["largeDoc"])
|
||||||
|
tester.run_test('内存使用测试', test_memory_usage, TEST_ITERATIONS["memory"])
|
||||||
|
|
||||||
|
tester.generate_report()
|
||||||
|
|
||||||
|
print('\n所有测试完成!')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f'测试过程中发生错误: {e}')
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
10
benchmark/python/requirements.txt
Normal file
10
benchmark/python/requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
python-docx>=0.8.11
|
||||||
|
docxtpl>=0.16.7
|
||||||
|
pytest>=7.0.0
|
||||||
|
pytest-benchmark>=4.0.0
|
||||||
|
memory-profiler>=0.60.0
|
||||||
|
psutil>=5.9.0
|
||||||
|
matplotlib>=3.5.0
|
||||||
|
pandas>=1.3.0
|
||||||
|
seaborn>=0.11.0
|
||||||
|
tabulate>=0.9.0
|
BIN
benchmark/results/charts/avg_time_comparison.png
Normal file
BIN
benchmark/results/charts/avg_time_comparison.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 133 KiB |
BIN
benchmark/results/charts/performance_distribution.png
Normal file
BIN
benchmark/results/charts/performance_distribution.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 189 KiB |
BIN
benchmark/results/charts/performance_heatmap.png
Normal file
BIN
benchmark/results/charts/performance_heatmap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 169 KiB |
BIN
benchmark/results/charts/performance_ratio.png
Normal file
BIN
benchmark/results/charts/performance_ratio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 KiB |
70
benchmark/results/detailed_comparison_report.md
Normal file
70
benchmark/results/detailed_comparison_report.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# WordZero 跨语言性能对比分析报告
|
||||||
|
|
||||||
|
生成时间: 2025-06-02 14:30:42
|
||||||
|
|
||||||
|
## 测试环境
|
||||||
|
|
||||||
|
- **Golang**: Go 1.19+
|
||||||
|
- **JavaScript**: Node.js v22.14.0
|
||||||
|
- **Python**: Python 3.13.3
|
||||||
|
|
||||||
|
## 性能对比摘要
|
||||||
|
|
||||||
|
- **总体最快平台**: Golang (平均 2.62ms)
|
||||||
|
- **各测试项目最快平台**:
|
||||||
|
- 基础文档创建: Golang (0.95ms)
|
||||||
|
- 复杂格式化: Golang (0.74ms)
|
||||||
|
- 表格操作: Golang (0.89ms)
|
||||||
|
- 大表格处理: Golang (4.93ms)
|
||||||
|
- 大型文档: Golang (5.13ms)
|
||||||
|
- 内存使用测试: Golang (3.07ms)
|
||||||
|
|
||||||
|
## 详细测试结果
|
||||||
|
|
||||||
|
### 平均执行时间对比 (毫秒)
|
||||||
|
|
||||||
|
| Test | Golang | JavaScript | Python |
|
||||||
|
|:-------|---------:|-------------:|---------:|
|
||||||
|
| 内存使用测试 | 3.07 | 7.33 | 80.2 |
|
||||||
|
| 基础文档创建 | 0.95 | 5.97 | 19.07 |
|
||||||
|
| 复杂格式化 | 0.74 | 5.77 | 19.98 |
|
||||||
|
| 大型文档 | 5.13 | 19.45 | 123.61 |
|
||||||
|
| 大表格处理 | 4.93 | 13.25 | 71.38 |
|
||||||
|
| 表格操作 | 0.89 | 6.02 | 21.62 |
|
||||||
|
|
||||||
|
### 相对性能分析
|
||||||
|
|
||||||
|
以Golang为基准的性能比率:
|
||||||
|
|
||||||
|
| Test | Golang | JavaScript | Python |
|
||||||
|
|:-------|---------:|-------------:|---------:|
|
||||||
|
| 内存使用测试 | 1 | 2.38762 | 26.1238 |
|
||||||
|
| 基础文档创建 | 1 | 6.28421 | 20.0737 |
|
||||||
|
| 复杂格式化 | 1 | 7.7973 | 27 |
|
||||||
|
| 大型文档 | 1 | 3.79142 | 24.0955 |
|
||||||
|
| 大表格处理 | 1 | 2.68763 | 14.4787 |
|
||||||
|
| 表格操作 | 1 | 6.76404 | 24.2921 |
|
||||||
|
|
||||||
|
## 性能建议
|
||||||
|
|
||||||
|
### 各语言优势分析
|
||||||
|
|
||||||
|
**Golang**:
|
||||||
|
- 最适合: 基础文档创建, 复杂格式化, 表格操作, 大表格处理, 大型文档, 内存使用测试
|
||||||
|
- 平均性能: 2.62ms
|
||||||
|
|
||||||
|
**JavaScript**:
|
||||||
|
- 在所有测试项目中都不是最快的
|
||||||
|
- 平均性能: 9.63ms
|
||||||
|
|
||||||
|
**Python**:
|
||||||
|
- 在所有测试项目中都不是最快的
|
||||||
|
- 平均性能: 55.98ms
|
||||||
|
|
||||||
|
### 选型建议
|
||||||
|
|
||||||
|
- **高性能需求**: 选择Golang实现,内存占用小,执行速度快
|
||||||
|
- **快速开发**: JavaScript/Node.js生态丰富,开发效率高
|
||||||
|
- **数据处理**: Python有丰富的数据处理库,适合复杂文档操作
|
||||||
|
- **跨平台**: 三种语言都支持跨平台,根据团队技术栈选择
|
||||||
|
|
142
examples/template_inheritance_demo/template_inheritance_demo.go
Normal file
142
examples/template_inheritance_demo/template_inheritance_demo.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
// Package main 模板继承功能演示
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/ZeroHawkeye/wordZero/pkg/document"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("WordZero 模板继承功能演示")
|
||||||
|
fmt.Println("=====================================")
|
||||||
|
|
||||||
|
// 演示基础块继承
|
||||||
|
demonstrateBasicInheritance()
|
||||||
|
|
||||||
|
fmt.Println("\n=====================================")
|
||||||
|
fmt.Println("模板继承功能演示完成!")
|
||||||
|
fmt.Println("生成的文档保存在 examples/output/ 目录下")
|
||||||
|
}
|
||||||
|
|
||||||
|
// demonstrateBasicInheritance 演示基础模板继承功能
|
||||||
|
func demonstrateBasicInheritance() {
|
||||||
|
engine := document.NewTemplateEngine()
|
||||||
|
|
||||||
|
// 创建基础模板
|
||||||
|
baseTemplate := `{{companyName}} 工作报告
|
||||||
|
|
||||||
|
报告日期:{{reportDate}}
|
||||||
|
|
||||||
|
{{#block "summary"}}
|
||||||
|
默认摘要内容
|
||||||
|
本报告总结了本期的工作情况。
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
默认主要内容
|
||||||
|
这里是详细的工作内容描述。
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "conclusion"}}
|
||||||
|
默认结论
|
||||||
|
总体来说,工作进展顺利。
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "signature"}}
|
||||||
|
报告人:{{reporterName}}
|
||||||
|
部门:{{department}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err := engine.LoadTemplate("base_report", baseTemplate)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("创建基础模板失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建销售报告模板(重写部分块)
|
||||||
|
salesReportTemplate := `{{extends "base_report"}}
|
||||||
|
|
||||||
|
{{#block "summary"}}
|
||||||
|
销售业绩摘要
|
||||||
|
本月销售目标已达成 {{achievementRate}}%,超额完成预定指标。
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
销售数据分析
|
||||||
|
|
||||||
|
本月销售业绩:
|
||||||
|
- 总销售额:{{totalSales}} 元
|
||||||
|
- 新增客户:{{newCustomers}} 个
|
||||||
|
- 成交订单:{{orders}} 笔
|
||||||
|
- 平均客单价:{{avgOrderValue}} 元
|
||||||
|
|
||||||
|
销售渠道分析:
|
||||||
|
{{#each channels}}
|
||||||
|
- {{name}}:{{sales}} 元 ({{percentage}}%)
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
重点客户维护:
|
||||||
|
{{#each keyAccounts}}
|
||||||
|
- {{name}}:{{revenue}} 元
|
||||||
|
{{/each}}
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "conclusion"}}
|
||||||
|
销售总结
|
||||||
|
本月销售业绩超出预期,特别是在{{topChannel}}渠道表现突出。
|
||||||
|
建议下月重点关注{{focusArea}}市场开拓。
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err = engine.LoadTemplate("sales_report", salesReportTemplate)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("创建销售报告模板失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
data := document.NewTemplateData()
|
||||||
|
data.SetVariable("companyName", "WordZero科技有限公司")
|
||||||
|
data.SetVariable("reportDate", "2024年12月01日")
|
||||||
|
data.SetVariable("reporterName", "张三")
|
||||||
|
data.SetVariable("department", "销售部")
|
||||||
|
data.SetVariable("achievementRate", "125")
|
||||||
|
data.SetVariable("totalSales", "1,850,000")
|
||||||
|
data.SetVariable("newCustomers", "45")
|
||||||
|
data.SetVariable("orders", "183")
|
||||||
|
data.SetVariable("avgOrderValue", "10,109")
|
||||||
|
data.SetVariable("topChannel", "线上电商")
|
||||||
|
data.SetVariable("focusArea", "企业级")
|
||||||
|
|
||||||
|
// 销售渠道数据
|
||||||
|
channels := []interface{}{
|
||||||
|
map[string]interface{}{"name": "线上电商", "sales": "742,000", "percentage": "40.1"},
|
||||||
|
map[string]interface{}{"name": "直销团队", "sales": "555,000", "percentage": "30.0"},
|
||||||
|
map[string]interface{}{"name": "合作伙伴", "sales": "370,000", "percentage": "20.0"},
|
||||||
|
map[string]interface{}{"name": "其他渠道", "sales": "183,000", "percentage": "9.9"},
|
||||||
|
}
|
||||||
|
data.SetList("channels", channels)
|
||||||
|
|
||||||
|
// 重点客户数据
|
||||||
|
keyAccounts := []interface{}{
|
||||||
|
map[string]interface{}{"name": "大型企业A", "revenue": "280,000"},
|
||||||
|
map[string]interface{}{"name": "知名公司B", "revenue": "195,000"},
|
||||||
|
map[string]interface{}{"name": "科技集团C", "revenue": "150,000"},
|
||||||
|
}
|
||||||
|
data.SetList("keyAccounts", keyAccounts)
|
||||||
|
|
||||||
|
// 渲染并保存
|
||||||
|
doc, err := engine.RenderToDocument("sales_report", data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("渲染模板失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = doc.Save("examples/output/template_inheritance_demo.docx")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("保存文档失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✓ 销售报告已生成:template_inheritance_demo.docx")
|
||||||
|
fmt.Println(" - 重写了摘要、主要内容和结论块")
|
||||||
|
fmt.Println(" - 保留了签名块的默认内容")
|
||||||
|
fmt.Println(" - 演示了完整的块重写机制")
|
||||||
|
}
|
@@ -126,7 +126,11 @@
|
|||||||
**变量替换**: 支持 `{{变量名}}` 语法进行动态内容替换
|
**变量替换**: 支持 `{{变量名}}` 语法进行动态内容替换
|
||||||
**条件语句**: 支持 `{{#if 条件}}...{{/if}}` 语法进行条件渲染
|
**条件语句**: 支持 `{{#if 条件}}...{{/if}}` 语法进行条件渲染
|
||||||
**循环语句**: 支持 `{{#each 列表}}...{{/each}}` 语法进行列表渲染
|
**循环语句**: 支持 `{{#each 列表}}...{{/each}}` 语法进行列表渲染
|
||||||
**模板继承**: 支持 `{{extends "基础模板"}}` 语法进行模板继承
|
**模板继承**: 支持 `{{extends "基础模板"}}` 语法和 `{{#block "块名"}}...{{/block}}` 块重写机制,实现真正的模板继承
|
||||||
|
- **块定义**: 在基础模板中定义可重写的内容块
|
||||||
|
- **块重写**: 在子模板中选择性重写特定块,未重写的块保持父模板默认内容
|
||||||
|
- **多级继承**: 支持模板的多层继承关系
|
||||||
|
- **完整保留**: 未重写的块完整保留父模板的默认内容和格式
|
||||||
**循环内条件**: 完美支持循环内部的条件表达式,如 `{{#each items}}{{#if isActive}}...{{/if}}{{/each}}`
|
**循环内条件**: 完美支持循环内部的条件表达式,如 `{{#each items}}{{#if isActive}}...{{/if}}{{/each}}`
|
||||||
**数据类型支持**: 支持字符串、数字、布尔值、对象等多种数据类型
|
**数据类型支持**: 支持字符串、数字、布尔值、对象等多种数据类型
|
||||||
**结构体绑定**: 支持从Go结构体自动生成模板数据
|
**结构体绑定**: 支持从Go结构体自动生成模板数据
|
||||||
@@ -144,6 +148,169 @@
|
|||||||
- [`Clear()`](template.go) - 清空模板数据
|
- [`Clear()`](template.go) - 清空模板数据
|
||||||
- [`FromStruct(data interface{})`](template.go) - 从结构体生成模板数据
|
- [`FromStruct(data interface{})`](template.go) - 从结构体生成模板数据
|
||||||
|
|
||||||
|
### 模板继承详细使用说明 ✨ **新增功能**
|
||||||
|
|
||||||
|
模板继承是WordZero模板引擎的高级功能,允许基于现有模板创建扩展模板,通过块定义和重写机制实现模板的复用和扩展。
|
||||||
|
|
||||||
|
#### 基础语法
|
||||||
|
|
||||||
|
**1. 基础模板块定义**
|
||||||
|
```go
|
||||||
|
// 定义带有可重写块的基础模板
|
||||||
|
baseTemplate := `{{companyName}} 报告
|
||||||
|
|
||||||
|
{{#block "header"}}
|
||||||
|
默认标题内容
|
||||||
|
日期:{{reportDate}}
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "summary"}}
|
||||||
|
默认摘要内容
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
默认主要内容
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "footer"}}
|
||||||
|
报告人:{{reporterName}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
engine.LoadTemplate("base_report", baseTemplate)
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 子模板继承和块重写**
|
||||||
|
```go
|
||||||
|
// 创建继承基础模板的子模板
|
||||||
|
childTemplate := `{{extends "base_report"}}
|
||||||
|
|
||||||
|
{{#block "summary"}}
|
||||||
|
销售业绩摘要
|
||||||
|
本月销售目标已达成 {{achievementRate}}%
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
详细销售数据:
|
||||||
|
- 总销售额:{{totalSales}}
|
||||||
|
- 新增客户:{{newCustomers}}
|
||||||
|
- 成交订单:{{orders}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
engine.LoadTemplate("sales_report", childTemplate)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 继承特性
|
||||||
|
|
||||||
|
**块重写策略**:
|
||||||
|
- 重写的块完全替换父模板中的对应块
|
||||||
|
- 未重写的块保持父模板的默认内容
|
||||||
|
- 支持选择性重写,灵活性极高
|
||||||
|
|
||||||
|
**多级继承**:
|
||||||
|
```go
|
||||||
|
// 第一级:基础模板
|
||||||
|
baseTemplate := `{{#block "document"}}基础文档{{/block}}`
|
||||||
|
|
||||||
|
// 第二级:业务模板
|
||||||
|
businessTemplate := `{{extends "base"}}
|
||||||
|
{{#block "document"}}
|
||||||
|
{{#block "business_header"}}业务标题{{/block}}
|
||||||
|
{{#block "business_content"}}业务内容{{/block}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
// 第三级:具体业务模板
|
||||||
|
salesTemplate := `{{extends "business"}}
|
||||||
|
{{#block "business_header"}}销售报告{{/block}}
|
||||||
|
{{#block "business_content"}}销售数据分析{{/block}}`
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 实际应用示例
|
||||||
|
|
||||||
|
```go
|
||||||
|
func demonstrateTemplateInheritance() {
|
||||||
|
engine := document.NewTemplateEngine()
|
||||||
|
|
||||||
|
// 基础报告模板
|
||||||
|
baseTemplate := `{{companyName}} 工作报告
|
||||||
|
报告日期:{{reportDate}}
|
||||||
|
|
||||||
|
{{#block "summary"}}
|
||||||
|
默认摘要内容
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
默认主要内容
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "conclusion"}}
|
||||||
|
默认结论
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "signature"}}
|
||||||
|
报告人:{{reporterName}}
|
||||||
|
部门:{{department}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
engine.LoadTemplate("base_report", baseTemplate)
|
||||||
|
|
||||||
|
// 销售报告模板(重写部分块)
|
||||||
|
salesTemplate := `{{extends "base_report"}}
|
||||||
|
|
||||||
|
{{#block "summary"}}
|
||||||
|
销售业绩摘要
|
||||||
|
本月销售目标已达成 {{achievementRate}}%
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
销售数据分析
|
||||||
|
- 总销售额:{{totalSales}}
|
||||||
|
- 新增客户:{{newCustomers}}
|
||||||
|
- 成交订单:{{orders}}
|
||||||
|
|
||||||
|
{{#each channels}}
|
||||||
|
- {{name}}:{{sales}} ({{percentage}}%)
|
||||||
|
{{/each}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
engine.LoadTemplate("sales_report", salesTemplate)
|
||||||
|
|
||||||
|
// 准备数据并渲染
|
||||||
|
data := document.NewTemplateData()
|
||||||
|
data.SetVariable("companyName", "WordZero科技")
|
||||||
|
data.SetVariable("reportDate", "2024年12月01日")
|
||||||
|
data.SetVariable("reporterName", "张三")
|
||||||
|
data.SetVariable("department", "销售部")
|
||||||
|
data.SetVariable("achievementRate", "125")
|
||||||
|
data.SetVariable("totalSales", "1,850,000")
|
||||||
|
data.SetVariable("newCustomers", "45")
|
||||||
|
data.SetVariable("orders", "183")
|
||||||
|
|
||||||
|
channels := []interface{}{
|
||||||
|
map[string]interface{}{"name": "线上电商", "sales": "742,000", "percentage": "40.1"},
|
||||||
|
map[string]interface{}{"name": "直销团队", "sales": "555,000", "percentage": "30.0"},
|
||||||
|
}
|
||||||
|
data.SetList("channels", channels)
|
||||||
|
|
||||||
|
// 渲染并保存
|
||||||
|
doc, _ := engine.RenderToDocument("sales_report", data)
|
||||||
|
doc.Save("sales_report.docx")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 优势与应用场景
|
||||||
|
|
||||||
|
**主要优势**:
|
||||||
|
- **代码复用**:避免重复定义相同的模板结构
|
||||||
|
- **维护性**:修改基础模板自动影响所有子模板
|
||||||
|
- **灵活性**:可选择性重写需要的部分,保留其他默认内容
|
||||||
|
- **扩展性**:支持多级继承,构建复杂的模板层次结构
|
||||||
|
|
||||||
|
**典型应用场景**:
|
||||||
|
- **企业报告体系**:基础报告模板+各部门专用模板
|
||||||
|
- **文档标准化**:统一格式的不同类型文档(合同、发票、通知等)
|
||||||
|
- **多语言文档**:相同结构不同语言的文档模板
|
||||||
|
- **品牌一致性**:保持企业品牌元素的统一性
|
||||||
|
|
||||||
### 图片操作功能 ✨ 新增功能
|
### 图片操作功能 ✨ 新增功能
|
||||||
- [`AddImageFromFile(filePath string, config *ImageConfig)`](image.go) - 从文件添加图片
|
- [`AddImageFromFile(filePath string, config *ImageConfig)`](image.go) - 从文件添加图片
|
||||||
- [`AddImageFromData(imageData []byte, fileName string, format ImageFormat, width, height int, config *ImageConfig)`](image.go) - 从数据添加图片
|
- [`AddImageFromData(imageData []byte, fileName string, format ImageFormat, width, height int, config *ImageConfig)`](image.go) - 从数据添加图片
|
||||||
|
@@ -23,6 +23,12 @@ var (
|
|||||||
|
|
||||||
// ErrInvalidTemplateData 无效模板数据
|
// ErrInvalidTemplateData 无效模板数据
|
||||||
ErrInvalidTemplateData = NewDocumentError("invalid_template_data", fmt.Errorf("invalid template data"), "")
|
ErrInvalidTemplateData = NewDocumentError("invalid_template_data", fmt.Errorf("invalid template data"), "")
|
||||||
|
|
||||||
|
// ErrBlockNotFound 块未找到
|
||||||
|
ErrBlockNotFound = NewDocumentError("block_not_found", fmt.Errorf("block not found"), "")
|
||||||
|
|
||||||
|
// ErrInvalidBlockDefinition 无效块定义
|
||||||
|
ErrInvalidBlockDefinition = NewDocumentError("invalid_block_definition", fmt.Errorf("invalid block definition"), "")
|
||||||
)
|
)
|
||||||
|
|
||||||
// TemplateEngine 模板引擎
|
// TemplateEngine 模板引擎
|
||||||
@@ -34,22 +40,26 @@ type TemplateEngine struct {
|
|||||||
|
|
||||||
// Template 模板结构
|
// Template 模板结构
|
||||||
type Template struct {
|
type Template struct {
|
||||||
Name string // 模板名称
|
Name string // 模板名称
|
||||||
Content string // 模板内容
|
Content string // 模板内容
|
||||||
BaseDoc *Document // 基础文档
|
BaseDoc *Document // 基础文档
|
||||||
Variables map[string]string // 模板变量
|
Variables map[string]string // 模板变量
|
||||||
Blocks []*TemplateBlock // 模板块
|
Blocks []*TemplateBlock // 模板块列表
|
||||||
Parent *Template // 父模板(用于继承)
|
Parent *Template // 父模板(用于继承)
|
||||||
|
DefinedBlocks map[string]*TemplateBlock // 定义的块映射
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateBlock 模板块
|
// TemplateBlock 模板块
|
||||||
type TemplateBlock struct {
|
type TemplateBlock struct {
|
||||||
Type string // 块类型:variable, if, each, inherit
|
Type string // 块类型:variable, if, each, inherit, block
|
||||||
Content string // 块内容
|
Name string // 块名称(block类型使用)
|
||||||
Condition string // 条件(if块使用)
|
Content string // 块内容
|
||||||
Variable string // 变量名(each块使用)
|
Condition string // 条件(if块使用)
|
||||||
Children []*TemplateBlock // 子块
|
Variable string // 变量名(each块使用)
|
||||||
Data map[string]interface{} // 块数据
|
Children []*TemplateBlock // 子块
|
||||||
|
Data map[string]interface{} // 块数据
|
||||||
|
DefaultContent string // 默认内容(用于可选重写)
|
||||||
|
IsOverridden bool // 是否被重写
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateData 模板数据
|
// TemplateData 模板数据
|
||||||
@@ -80,10 +90,11 @@ func (te *TemplateEngine) LoadTemplate(name, content string) (*Template, error)
|
|||||||
defer te.mutex.Unlock()
|
defer te.mutex.Unlock()
|
||||||
|
|
||||||
template := &Template{
|
template := &Template{
|
||||||
Name: name,
|
Name: name,
|
||||||
Content: content,
|
Content: content,
|
||||||
Variables: make(map[string]string),
|
Variables: make(map[string]string),
|
||||||
Blocks: make([]*TemplateBlock, 0),
|
Blocks: make([]*TemplateBlock, 0),
|
||||||
|
DefinedBlocks: make(map[string]*TemplateBlock),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析模板内容
|
// 解析模板内容
|
||||||
@@ -109,11 +120,12 @@ func (te *TemplateEngine) LoadTemplateFromDocument(name string, doc *Document) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
template := &Template{
|
template := &Template{
|
||||||
Name: name,
|
Name: name,
|
||||||
Content: content,
|
Content: content,
|
||||||
BaseDoc: doc,
|
BaseDoc: doc,
|
||||||
Variables: make(map[string]string),
|
Variables: make(map[string]string),
|
||||||
Blocks: make([]*TemplateBlock, 0),
|
Blocks: make([]*TemplateBlock, 0),
|
||||||
|
DefinedBlocks: make(map[string]*TemplateBlock),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析模板内容
|
// 解析模板内容
|
||||||
@@ -176,6 +188,27 @@ func (te *TemplateEngine) parseTemplate(template *Template) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析块定义: {{#block "blockName"}}...{{/block}}
|
||||||
|
blockPattern := regexp.MustCompile(`(?s)\{\{#block\s+"([^"]+)"\}\}(.*?)\{\{/block\}\}`)
|
||||||
|
blockMatches := blockPattern.FindAllStringSubmatch(content, -1)
|
||||||
|
for _, match := range blockMatches {
|
||||||
|
if len(match) >= 3 {
|
||||||
|
blockName := match[1]
|
||||||
|
blockContent := match[2]
|
||||||
|
|
||||||
|
block := &TemplateBlock{
|
||||||
|
Type: "block",
|
||||||
|
Name: blockName,
|
||||||
|
Content: blockContent,
|
||||||
|
DefaultContent: blockContent,
|
||||||
|
Children: make([]*TemplateBlock, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Blocks = append(template.Blocks, block)
|
||||||
|
template.DefinedBlocks[blockName] = block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 解析条件语句: {{#if 条件}}...{{/if}} (修复:添加 (?s) 标志以匹配换行符)
|
// 解析条件语句: {{#if 条件}}...{{/if}} (修复:添加 (?s) 标志以匹配换行符)
|
||||||
ifPattern := regexp.MustCompile(`(?s)\{\{#if\s+(\w+)\}\}(.*?)\{\{/if\}\}`)
|
ifPattern := regexp.MustCompile(`(?s)\{\{#if\s+(\w+)\}\}(.*?)\{\{/if\}\}`)
|
||||||
ifMatches := ifPattern.FindAllStringSubmatch(content, -1)
|
ifMatches := ifPattern.FindAllStringSubmatch(content, -1)
|
||||||
@@ -222,12 +255,31 @@ func (te *TemplateEngine) parseTemplate(template *Template) error {
|
|||||||
baseTemplate, err := te.getTemplateInternal(baseName)
|
baseTemplate, err := te.getTemplateInternal(baseName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
template.Parent = baseTemplate
|
template.Parent = baseTemplate
|
||||||
|
// 处理块重写
|
||||||
|
te.processBlockOverrides(template, baseTemplate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processBlockOverrides 处理块重写
|
||||||
|
func (te *TemplateEngine) processBlockOverrides(childTemplate, parentTemplate *Template) {
|
||||||
|
// 遍历子模板的块定义,检查是否重写父模板的块
|
||||||
|
for blockName, childBlock := range childTemplate.DefinedBlocks {
|
||||||
|
if parentBlock, exists := parentTemplate.DefinedBlocks[blockName]; exists {
|
||||||
|
// 标记父模板块被重写
|
||||||
|
parentBlock.IsOverridden = true
|
||||||
|
parentBlock.Content = childBlock.Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理父模板的父模板
|
||||||
|
if parentTemplate.Parent != nil {
|
||||||
|
te.processBlockOverrides(childTemplate, parentTemplate.Parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RenderToDocument 渲染模板到新文档
|
// RenderToDocument 渲染模板到新文档
|
||||||
func (te *TemplateEngine) RenderToDocument(templateName string, data *TemplateData) (*Document, error) {
|
func (te *TemplateEngine) RenderToDocument(templateName string, data *TemplateData) (*Document, error) {
|
||||||
template, err := te.GetTemplate(templateName)
|
template, err := te.GetTemplate(templateName)
|
||||||
@@ -261,17 +313,27 @@ func (te *TemplateEngine) RenderToDocument(templateName string, data *TemplateDa
|
|||||||
|
|
||||||
// renderTemplate 渲染模板
|
// renderTemplate 渲染模板
|
||||||
func (te *TemplateEngine) renderTemplate(template *Template, data *TemplateData) (string, error) {
|
func (te *TemplateEngine) renderTemplate(template *Template, data *TemplateData) (string, error) {
|
||||||
content := template.Content
|
var content string
|
||||||
|
|
||||||
// 处理继承
|
// 处理继承:如果有父模板,使用父模板作为基础
|
||||||
if template.Parent != nil {
|
if template.Parent != nil {
|
||||||
|
// 渲染父模板作为基础内容
|
||||||
parentContent, err := te.renderTemplate(template.Parent, data)
|
parentContent, err := te.renderTemplate(template.Parent, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
content = parentContent + "\n" + content
|
content = parentContent
|
||||||
|
|
||||||
|
// 应用子模板的块重写到父模板内容中
|
||||||
|
content = te.applyBlockOverrides(content, template)
|
||||||
|
} else {
|
||||||
|
// 没有父模板,直接使用当前模板内容
|
||||||
|
content = template.Content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 渲染块定义
|
||||||
|
content = te.renderBlocks(content, template, data)
|
||||||
|
|
||||||
// 渲染变量
|
// 渲染变量
|
||||||
content = te.renderVariables(content, data.Variables)
|
content = te.renderVariables(content, data.Variables)
|
||||||
|
|
||||||
@@ -284,6 +346,50 @@ func (te *TemplateEngine) renderTemplate(template *Template, data *TemplateData)
|
|||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyBlockOverrides 将子模板的块重写应用到父模板内容中
|
||||||
|
func (te *TemplateEngine) applyBlockOverrides(content string, template *Template) string {
|
||||||
|
// 将子模板的块内容替换父模板中对应的块占位符
|
||||||
|
blockPattern := regexp.MustCompile(`(?s)\{\{#block\s+"([^"]+)"\}\}.*?\{\{/block\}\}`)
|
||||||
|
|
||||||
|
return blockPattern.ReplaceAllStringFunc(content, func(match string) string {
|
||||||
|
matches := blockPattern.FindStringSubmatch(match)
|
||||||
|
if len(matches) >= 2 {
|
||||||
|
blockName := matches[1]
|
||||||
|
// 如果子模板中定义了这个块,使用子模板的内容
|
||||||
|
if childBlock, exists := template.DefinedBlocks[blockName]; exists {
|
||||||
|
return childBlock.Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match // 保持原样
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderBlocks 渲染块定义
|
||||||
|
func (te *TemplateEngine) renderBlocks(content string, template *Template, data *TemplateData) string {
|
||||||
|
blockPattern := regexp.MustCompile(`(?s)\{\{#block\s+"([^"]+)"\}\}(.*?)\{\{/block\}\}`)
|
||||||
|
|
||||||
|
return blockPattern.ReplaceAllStringFunc(content, func(match string) string {
|
||||||
|
matches := blockPattern.FindStringSubmatch(match)
|
||||||
|
if len(matches) >= 3 {
|
||||||
|
blockName := matches[1]
|
||||||
|
blockContent := matches[2]
|
||||||
|
|
||||||
|
// 检查是否有定义的块
|
||||||
|
if block, exists := template.DefinedBlocks[blockName]; exists {
|
||||||
|
// 如果块被重写,使用重写的内容,否则使用默认内容
|
||||||
|
if block.IsOverridden {
|
||||||
|
return block.Content
|
||||||
|
}
|
||||||
|
return block.DefaultContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有定义块,使用原始内容
|
||||||
|
return blockContent
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// renderVariables 渲染变量
|
// renderVariables 渲染变量
|
||||||
func (te *TemplateEngine) renderVariables(content string, variables map[string]interface{}) string {
|
func (te *TemplateEngine) renderVariables(content string, variables map[string]interface{}) string {
|
||||||
varPattern := regexp.MustCompile(`\{\{(\w+)\}\}`)
|
varPattern := regexp.MustCompile(`\{\{(\w+)\}\}`)
|
||||||
@@ -431,6 +537,11 @@ func (te *TemplateEngine) ValidateTemplate(template *Template) error {
|
|||||||
return WrapErrorWithContext("validate_template", err, template.Name)
|
return WrapErrorWithContext("validate_template", err, template.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查块语句配对
|
||||||
|
if err := te.validateBlockStatements(content); err != nil {
|
||||||
|
return WrapErrorWithContext("validate_template", err, template.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 检查if语句配对
|
// 检查if语句配对
|
||||||
if err := te.validateIfStatements(content); err != nil {
|
if err := te.validateIfStatements(content); err != nil {
|
||||||
return WrapErrorWithContext("validate_template", err, template.Name)
|
return WrapErrorWithContext("validate_template", err, template.Name)
|
||||||
@@ -456,6 +567,18 @@ func (te *TemplateEngine) validateBrackets(content string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateBlockStatements 验证块语句配对
|
||||||
|
func (te *TemplateEngine) validateBlockStatements(content string) error {
|
||||||
|
blockCount := len(regexp.MustCompile(`\{\{#block\s+"[^"]+"\}\}`).FindAllString(content, -1))
|
||||||
|
endblockCount := len(regexp.MustCompile(`\{\{/block\}\}`).FindAllString(content, -1))
|
||||||
|
|
||||||
|
if blockCount != endblockCount {
|
||||||
|
return NewValidationError("block_statements", content, "mismatched block/endblock statements")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateIfStatements 验证if语句配对
|
// validateIfStatements 验证if语句配对
|
||||||
func (te *TemplateEngine) validateIfStatements(content string) error {
|
func (te *TemplateEngine) validateIfStatements(content string) error {
|
||||||
ifCount := len(regexp.MustCompile(`\{\{#if\s+\w+\}\}`).FindAllString(content, -1))
|
ifCount := len(regexp.MustCompile(`\{\{#if\s+\w+\}\}`).FindAllString(content, -1))
|
||||||
|
463
test/template_inheritance_test.go
Normal file
463
test/template_inheritance_test.go
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
// Package test 模板继承功能测试
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ZeroHawkeye/wordZero/pkg/document"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestTemplateInheritanceComplete 完整的模板继承测试
|
||||||
|
func TestTemplateInheritanceComplete(t *testing.T) {
|
||||||
|
// 确保输出目录存在
|
||||||
|
outputDir := "output"
|
||||||
|
if _, err := os.Stat(outputDir); os.IsNotExist(err) {
|
||||||
|
err = os.Mkdir(outputDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create output directory: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := document.NewTemplateEngine()
|
||||||
|
|
||||||
|
// 测试1: 基础模板继承
|
||||||
|
t.Run("基础模板继承", func(t *testing.T) {
|
||||||
|
testBasicInheritance(t, engine)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试2: 块重写功能
|
||||||
|
t.Run("块重写功能", func(t *testing.T) {
|
||||||
|
testBlockOverride(t, engine)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试3: 多级继承
|
||||||
|
t.Run("多级继承", func(t *testing.T) {
|
||||||
|
testMultiLevelInheritance(t, engine)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试4: 块默认内容
|
||||||
|
t.Run("块默认内容", func(t *testing.T) {
|
||||||
|
testBlockDefaultContent(t, engine)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBasicInheritance 测试基础模板继承功能
|
||||||
|
func testBasicInheritance(t *testing.T, engine *document.TemplateEngine) {
|
||||||
|
// 创建基础模板
|
||||||
|
baseTemplate := `{{companyName}} 官方文档
|
||||||
|
|
||||||
|
版本:{{version}}
|
||||||
|
创建时间:{{createTime}}
|
||||||
|
|
||||||
|
{{#block "content"}}
|
||||||
|
默认内容区域
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "footer"}}
|
||||||
|
版权所有 © {{year}} {{companyName}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err := engine.LoadTemplate("base", baseTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load base template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建子模板
|
||||||
|
childTemplate := `{{extends "base"}}
|
||||||
|
|
||||||
|
{{#block "content"}}
|
||||||
|
用户手册
|
||||||
|
|
||||||
|
第一章:快速开始
|
||||||
|
欢迎使用我们的产品!
|
||||||
|
|
||||||
|
第二章:功能介绍
|
||||||
|
详细的功能说明...
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err = engine.LoadTemplate("user_manual", childTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load child template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
data := document.NewTemplateData()
|
||||||
|
data.SetVariable("companyName", "WordZero科技")
|
||||||
|
data.SetVariable("version", "v1.0.0")
|
||||||
|
data.SetVariable("createTime", "2024-12-01")
|
||||||
|
data.SetVariable("year", "2024")
|
||||||
|
|
||||||
|
// 渲染模板
|
||||||
|
doc, err := engine.RenderToDocument("user_manual", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to render template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存文档
|
||||||
|
err = doc.Save("output/test_basic_inheritance.docx")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save document: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文档内容
|
||||||
|
if len(doc.Body.Elements) == 0 {
|
||||||
|
t.Error("Expected document to have content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBlockOverride 测试块重写功能
|
||||||
|
func testBlockOverride(t *testing.T, engine *document.TemplateEngine) {
|
||||||
|
// 创建基础模板
|
||||||
|
baseTemplate := `企业报告模板
|
||||||
|
|
||||||
|
{{#block "header"}}
|
||||||
|
标准报告头部
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
标准内容区域
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "sidebar"}}
|
||||||
|
标准侧边栏
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "footer"}}
|
||||||
|
标准页脚
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err := engine.LoadTemplate("report_base", baseTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load base template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建特定报告模板,重写部分块
|
||||||
|
salesReportTemplate := `{{extends "report_base"}}
|
||||||
|
|
||||||
|
{{#block "header"}}
|
||||||
|
销售业绩报告
|
||||||
|
报告期间:{{reportPeriod}}
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_content"}}
|
||||||
|
销售数据分析
|
||||||
|
|
||||||
|
总销售额:{{totalSales}} 元
|
||||||
|
增长率:{{growthRate}}%
|
||||||
|
|
||||||
|
{{#each regions}}
|
||||||
|
- {{name}}: {{sales}} 元
|
||||||
|
{{/each}}
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "sidebar"}}
|
||||||
|
快速统计
|
||||||
|
- 新客户:{{newCustomers}} 人
|
||||||
|
- 回头客:{{returningCustomers}} 人
|
||||||
|
- 平均订单:{{averageOrder}} 元
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err = engine.LoadTemplate("sales_report", salesReportTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load sales report template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
data := document.NewTemplateData()
|
||||||
|
data.SetVariable("reportPeriod", "2024年11月")
|
||||||
|
data.SetVariable("totalSales", "1,250,000")
|
||||||
|
data.SetVariable("growthRate", "15.8")
|
||||||
|
data.SetVariable("newCustomers", "158")
|
||||||
|
data.SetVariable("returningCustomers", "432")
|
||||||
|
data.SetVariable("averageOrder", "2,890")
|
||||||
|
|
||||||
|
regions := []interface{}{
|
||||||
|
map[string]interface{}{"name": "华东区", "sales": "450,000"},
|
||||||
|
map[string]interface{}{"name": "华北区", "sales": "380,000"},
|
||||||
|
map[string]interface{}{"name": "华南区", "sales": "420,000"},
|
||||||
|
}
|
||||||
|
data.SetList("regions", regions)
|
||||||
|
|
||||||
|
// 渲染模板
|
||||||
|
doc, err := engine.RenderToDocument("sales_report", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to render template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存文档
|
||||||
|
err = doc.Save("output/test_block_override.docx")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save document: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文档内容
|
||||||
|
if len(doc.Body.Elements) == 0 {
|
||||||
|
t.Error("Expected document to have content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testMultiLevelInheritance 测试多级继承
|
||||||
|
func testMultiLevelInheritance(t *testing.T, engine *document.TemplateEngine) {
|
||||||
|
// 第一级:基础文档模板
|
||||||
|
baseDocTemplate := `{{#block "document_header"}}
|
||||||
|
文档标题:{{title}}
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_body"}}
|
||||||
|
主要内容区域
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "document_footer"}}
|
||||||
|
页脚信息
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err := engine.LoadTemplate("base_doc", baseDocTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load base doc template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二级:技术文档模板
|
||||||
|
techDocTemplate := `{{extends "base_doc"}}
|
||||||
|
|
||||||
|
{{#block "document_header"}}
|
||||||
|
技术文档:{{title}}
|
||||||
|
版本:{{version}}
|
||||||
|
作者:{{author}}
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "main_body"}}
|
||||||
|
{{#block "abstract"}}
|
||||||
|
文档摘要
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "technical_content"}}
|
||||||
|
技术内容
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "references"}}
|
||||||
|
参考资料
|
||||||
|
{{/block}}
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err = engine.LoadTemplate("tech_doc", techDocTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load tech doc template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三级:API文档模板
|
||||||
|
apiDocTemplate := `{{extends "tech_doc"}}
|
||||||
|
|
||||||
|
{{#block "abstract"}}
|
||||||
|
API接口文档摘要
|
||||||
|
本文档描述了{{apiName}}的使用方法和接口规范。
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "technical_content"}}
|
||||||
|
API接口列表
|
||||||
|
|
||||||
|
{{#each endpoints}}
|
||||||
|
### {{method}} {{path}}
|
||||||
|
描述:{{description}}
|
||||||
|
参数:{{parameters}}
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "references"}}
|
||||||
|
相关文档:
|
||||||
|
- API设计规范
|
||||||
|
- 认证机制说明
|
||||||
|
- 错误码参考
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err = engine.LoadTemplate("api_doc", apiDocTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load API doc template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
data := document.NewTemplateData()
|
||||||
|
data.SetVariable("title", "WordZero API文档")
|
||||||
|
data.SetVariable("version", "v2.1.0")
|
||||||
|
data.SetVariable("author", "技术团队")
|
||||||
|
data.SetVariable("apiName", "WordZero API")
|
||||||
|
|
||||||
|
endpoints := []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/api/documents",
|
||||||
|
"description": "创建新文档",
|
||||||
|
"parameters": "title, content, format",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/documents/{id}",
|
||||||
|
"description": "获取文档详情",
|
||||||
|
"parameters": "id (路径参数)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data.SetList("endpoints", endpoints)
|
||||||
|
|
||||||
|
// 渲染模板
|
||||||
|
doc, err := engine.RenderToDocument("api_doc", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to render template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存文档
|
||||||
|
err = doc.Save("output/test_multi_level_inheritance.docx")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save document: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文档内容
|
||||||
|
if len(doc.Body.Elements) == 0 {
|
||||||
|
t.Error("Expected document to have content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBlockDefaultContent 测试块默认内容
|
||||||
|
func testBlockDefaultContent(t *testing.T, engine *document.TemplateEngine) {
|
||||||
|
// 创建有默认内容的基础模板
|
||||||
|
baseTemplate := `产品文档
|
||||||
|
|
||||||
|
{{#block "intro"}}
|
||||||
|
默认介绍内容
|
||||||
|
这是产品的基本介绍。
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "features"}}
|
||||||
|
默认功能列表
|
||||||
|
- 基础功能1
|
||||||
|
- 基础功能2
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "contact"}}
|
||||||
|
默认联系方式
|
||||||
|
邮箱:default@example.com
|
||||||
|
电话:000-0000-0000
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err := engine.LoadTemplate("product_base", baseTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load base template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建子模板,只重写部分块
|
||||||
|
productTemplate := `{{extends "product_base"}}
|
||||||
|
|
||||||
|
{{#block "intro"}}
|
||||||
|
WordZero产品介绍
|
||||||
|
WordZero是一个强大的Word文档处理库。
|
||||||
|
{{/block}}
|
||||||
|
|
||||||
|
{{#block "features"}}
|
||||||
|
WordZero功能特性
|
||||||
|
- 文档创建和编辑
|
||||||
|
- 模板渲染
|
||||||
|
- 表格处理
|
||||||
|
- 图片插入
|
||||||
|
- 样式管理
|
||||||
|
{{/block}}`
|
||||||
|
|
||||||
|
_, err = engine.LoadTemplate("wordzero_product", productTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load product template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
data := document.NewTemplateData()
|
||||||
|
|
||||||
|
// 渲染模板
|
||||||
|
doc, err := engine.RenderToDocument("wordzero_product", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to render template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存文档
|
||||||
|
err = doc.Save("output/test_block_default_content.docx")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save document: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文档内容
|
||||||
|
if len(doc.Body.Elements) == 0 {
|
||||||
|
t.Error("Expected document to have content")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证默认内容保持不变(contact块应该使用默认内容)
|
||||||
|
// 这里我们可以通过渲染的内容来验证
|
||||||
|
renderedContent, err := engine.GetTemplate("wordzero_product")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证模板结构
|
||||||
|
if renderedContent.Parent == nil {
|
||||||
|
t.Error("Expected template to have parent")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(renderedContent.DefinedBlocks) != 2 {
|
||||||
|
t.Errorf("Expected 2 defined blocks, got %d", len(renderedContent.DefinedBlocks))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTemplateInheritanceValidation 测试模板继承验证
|
||||||
|
func TestTemplateInheritanceValidation(t *testing.T) {
|
||||||
|
engine := document.NewTemplateEngine()
|
||||||
|
|
||||||
|
// 测试块语法验证
|
||||||
|
t.Run("块语法验证", func(t *testing.T) {
|
||||||
|
// 正确的块语法
|
||||||
|
validTemplate := `{{#block "test"}}内容{{/block}}`
|
||||||
|
template, err := engine.LoadTemplate("valid_block", validTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load valid template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.ValidateTemplate(template)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Valid template should pass validation: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误的块语法 - 缺少结束标签
|
||||||
|
invalidTemplate := `{{#block "test"}}内容`
|
||||||
|
template2, err := engine.LoadTemplate("invalid_block", invalidTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load invalid template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.ValidateTemplate(template2)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Invalid template should fail validation")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试继承链验证
|
||||||
|
t.Run("继承链验证", func(t *testing.T) {
|
||||||
|
// 创建基础模板
|
||||||
|
baseTemplate := `{{#block "content"}}基础内容{{/block}}`
|
||||||
|
_, err := engine.LoadTemplate("inheritance_base", baseTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load base template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建子模板
|
||||||
|
childTemplate := `{{extends "inheritance_base"}}
|
||||||
|
{{#block "content"}}子模板内容{{/block}}`
|
||||||
|
child, err := engine.LoadTemplate("inheritance_child", childTemplate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load child template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证继承关系
|
||||||
|
if child.Parent == nil {
|
||||||
|
t.Error("Child template should have parent")
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.Parent.Name != "inheritance_base" {
|
||||||
|
t.Errorf("Expected parent name 'inheritance_base', got '%s'", child.Parent.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Submodule wordZero.wiki updated: 17e18e751f...64ef18dac0
Reference in New Issue
Block a user