mirror of
https://github.com/ZeroHawkeye/wordZero.git
synced 2025-09-26 20:01:17 +08:00
- 增加基准测试
- 修复模板继承问题 - 优化wiki文档
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -47,3 +47,9 @@ Thumbs.db
|
||||
bin/
|
||||
dist/
|
||||
build/
|
||||
benchmark/results/golang
|
||||
benchmark/results/javascript
|
||||
benchmark/results/python
|
||||
benchmark/python/venv
|
||||
benchmark/javascript/node_modules
|
||||
benchmark/javascript/package-lock.json
|
497
README.md
497
README.md
@@ -3,6 +3,8 @@
|
||||
[](https://golang.org)
|
||||
[](LICENSE)
|
||||
[](#测试)
|
||||
[](wordZero.wiki/13-性能基准测试)
|
||||
[](wordZero.wiki/13-性能基准测试)
|
||||
[](https://deepwiki.com/ZeroHawkeye/wordZero)
|
||||
|
||||
## 项目介绍
|
||||
@@ -16,7 +18,11 @@ WordZero 是一个使用 Golang 实现的 Word 文档操作库,提供基础的
|
||||
- 📝 **文本格式化**: 字体、大小、颜色、粗体、斜体等完整支持
|
||||
- 📐 **段落格式**: 对齐、间距、缩进等段落属性设置
|
||||
- 🏷️ **标题导航**: 完整支持Heading1-9样式,可被Word导航窗格识别
|
||||
- ⚡ **高性能**: 零依赖的纯Go实现,内存占用低
|
||||
- 📊 **表格功能**: 完整的表格创建、编辑、样式设置和迭代器支持
|
||||
- 📄 **页面设置**: 页面尺寸、边距、页眉页脚等专业排版功能
|
||||
- 🔧 **高级功能**: 目录生成、脚注尾注、列表编号、模板引擎等
|
||||
- 🎯 **模板继承**: 支持基础模板和块重写机制,实现模板复用和扩展
|
||||
- ⚡ **卓越性能**: 零依赖的纯Go实现,平均2.62ms处理速度,比JavaScript快3.7倍,比Python快21倍
|
||||
- 🔧 **易于使用**: 简洁的API设计,链式调用支持
|
||||
|
||||
## 安装
|
||||
@@ -37,353 +43,111 @@ go get github.com/ZeroHawkeye/wordZero@latest
|
||||
go get github.com/ZeroHawkeye/wordZero@v0.4.0
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
## 快速开始
|
||||
|
||||
```
|
||||
wordZero/
|
||||
├── pkg/ # 公共包
|
||||
│ ├── document/ # 文档核心操作
|
||||
│ │ ├── document.go # 主要文档操作API
|
||||
│ │ ├── table.go # 表格操作功能
|
||||
│ │ ├── page.go # 页面设置功能 ✨ 新增
|
||||
│ │ ├── header_footer.go # 页眉页脚功能 ✨ 新增
|
||||
│ │ ├── toc.go # 目录生成功能 ✨ 新增
|
||||
│ │ ├── footnotes.go # 脚注尾注功能 ✨ 新增
|
||||
│ │ ├── numbering.go # 列表编号功能 ✨ 新增
|
||||
│ │ ├── sdt.go # 结构化文档标签 ✨ 新增
|
||||
│ │ ├── field.go # 域字段功能 ✨ 新增
|
||||
│ │ ├── properties.go # 文档属性管理 ✨ 新增
|
||||
│ │ ├── template.go # 模板功能 ✨ 新增
|
||||
│ │ ├── errors.go # 错误定义和处理
|
||||
│ │ ├── logger.go # 日志系统
|
||||
│ │ ├── doc.go # 包文档
|
||||
│ │ ├── document_test.go # 文档操作单元测试
|
||||
│ │ ├── table_test.go # 表格功能单元测试
|
||||
│ │ └── page_test.go # 页面设置单元测试 ✨ 新增
|
||||
│ └── style/ # 样式管理系统
|
||||
│ ├── style.go # 样式核心定义
|
||||
│ ├── api.go # 快速API接口
|
||||
│ ├── predefined.go # 预定义样式常量
|
||||
│ ├── api_test.go # API测试
|
||||
│ ├── style_test.go # 样式系统测试
|
||||
│ └── README.md # 样式系统详细文档
|
||||
├── examples/ # 使用示例
|
||||
│ ├── basic/ # 基础功能示例
|
||||
│ │ └── basic_example.go
|
||||
│ ├── formatting/ # 格式化示例
|
||||
│ │ └── text_formatting_example.go
|
||||
│ ├── style_demo/ # 样式系统演示
|
||||
│ │ └── style_demo.go
|
||||
│ ├── table/ # 表格功能示例
|
||||
│ │ └── table_example.go
|
||||
│ ├── table_layout/ # 表格布局和尺寸示例
|
||||
│ │ └── main.go
|
||||
│ ├── table_style/ # 表格样式和外观示例
|
||||
│ │ └── main.go
|
||||
│ ├── cell_advanced/ # 单元格高级功能示例
|
||||
│ │ └── main.go
|
||||
│ ├── 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
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"github.com/ZeroHawkeye/wordZero/pkg/document"
|
||||
"github.com/ZeroHawkeye/wordZero/pkg/style"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建新文档
|
||||
doc := document.New()
|
||||
|
||||
// 添加标题
|
||||
titlePara := doc.AddParagraph("WordZero 使用示例")
|
||||
titlePara.SetStyle(style.StyleHeading1)
|
||||
|
||||
// 添加正文段落
|
||||
para := doc.AddParagraph("这是一个使用 WordZero 创建的文档示例。")
|
||||
para.SetFontFamily("宋体")
|
||||
para.SetFontSize(12)
|
||||
para.SetColor("333333")
|
||||
|
||||
// 创建表格
|
||||
tableConfig := &document.TableConfig{
|
||||
Rows: 3,
|
||||
Columns: 3,
|
||||
}
|
||||
table := doc.AddTable(tableConfig)
|
||||
table.SetCellText(0, 0, "表头1")
|
||||
table.SetCellText(0, 1, "表头2")
|
||||
table.SetCellText(0, 2, "表头3")
|
||||
|
||||
// 保存文档
|
||||
if err := doc.Save("example.docx"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
### 模板继承功能示例
|
||||
|
||||
### ✅ 已实现功能
|
||||
```go
|
||||
// 创建基础模板
|
||||
engine := document.NewTemplateEngine()
|
||||
baseTemplate := `{{companyName}} 工作报告
|
||||
|
||||
#### 文档基础操作
|
||||
- [x] 创建新的 Word 文档
|
||||
- [x] 读取和解析现有文档 ✨ **重大改进**
|
||||
- [x] **动态元素解析**: 支持段落、表格、节属性等多种元素类型
|
||||
- [x] **结构化解析**: 保持文档元素的原始顺序和层次结构
|
||||
- [x] **完整XML解析**: 使用流式解析,支持复杂的嵌套结构
|
||||
- [x] **错误恢复**: 智能跳过未知元素,确保解析稳定性
|
||||
- [x] **性能优化**: 内存友好的增量解析,适用于大型文档
|
||||
- [x] 文档保存和压缩
|
||||
- [x] ZIP文件处理和OOXML结构解析
|
||||
{{#block "summary"}}
|
||||
默认摘要内容
|
||||
{{/block}}
|
||||
|
||||
#### 文本和段落操作
|
||||
- [x] 文本内容的添加和修改
|
||||
- [x] 段落创建和管理
|
||||
- [x] 文本格式化(字体、大小、颜色、粗体、斜体)
|
||||
- [x] 段落对齐(左对齐、居中、右对齐、两端对齐)
|
||||
- [x] 行间距和段间距设置
|
||||
- [x] 首行缩进和左右缩进
|
||||
- [x] 混合格式文本(一个段落中多种格式)
|
||||
{{#block "content"}}
|
||||
默认主要内容
|
||||
{{/block}}`
|
||||
|
||||
#### 样式管理系统
|
||||
- [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**: 便捷的样式操作接口
|
||||
engine.LoadTemplate("base_report", baseTemplate)
|
||||
|
||||
#### 页面设置功能 ✨ 新增
|
||||
- [x] 页面大小设置(A4、Letter、Legal、A3、A5等标准尺寸)
|
||||
- [x] 自定义页面尺寸(毫米单位,支持任意尺寸)
|
||||
- [x] 页面方向设置(纵向/横向)
|
||||
- [x] 页面边距设置(上下左右边距,毫米单位)
|
||||
- [x] 页眉页脚距离设置
|
||||
- [x] 装订线宽度设置
|
||||
- [x] 完整页面设置API和配置结构
|
||||
- [x] 页面设置验证和错误处理
|
||||
- [x] 页面设置的保存和加载支持
|
||||
// 创建扩展模板,重写特定块
|
||||
salesTemplate := `{{extends "base_report"}}
|
||||
|
||||
#### 表格功能
|
||||
{{#block "summary"}}
|
||||
销售业绩摘要:本月达成 {{achievement}}%
|
||||
{{/block}}
|
||||
|
||||
##### 表格基础操作
|
||||
- [x] 表格创建和初始化
|
||||
- [x] 创建指定行列数的表格
|
||||
- [x] 设置表格初始数据
|
||||
- [x] 表格插入到文档指定位置
|
||||
- [x] **新增**:默认表格样式(参考Word标准格式)
|
||||
- [x] 默认单线边框(single边框样式)
|
||||
- [x] 标准边框粗细(4 * 1/8磅)
|
||||
- [x] 自动调整布局(autofit)
|
||||
- [x] 标准单元格边距(108 dxa)
|
||||
- [x] 支持样式覆盖和自定义
|
||||
- [x] 表格结构管理
|
||||
- [x] 插入行(指定位置、末尾、开头)
|
||||
- [x] 删除行(单行、多行、指定范围)
|
||||
- [x] 表格复制和剪切
|
||||
- [x] 表格删除和清空
|
||||
{{#block "content"}}
|
||||
销售详情:
|
||||
- 总销售额:{{totalSales}}
|
||||
- 新增客户:{{newCustomers}}
|
||||
{{/block}}`
|
||||
|
||||
##### 单元格操作
|
||||
- [x] 单元格内容管理
|
||||
- [x] 单元格文本设置和获取
|
||||
- [x] 单元格富文本格式支持
|
||||
- [x] 单元格内容清空和重置
|
||||
- [x] 单元格合并功能
|
||||
- [x] 横向合并(合并列)
|
||||
- [x] 纵向合并(合并行)
|
||||
- [x] 区域合并(多行多列)
|
||||
- [x] 取消合并操作
|
||||
- [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] 避免分页的行设置
|
||||
// 渲染模板
|
||||
data := document.NewTemplateData()
|
||||
data.SetVariable("companyName", "WordZero科技")
|
||||
data.SetVariable("achievement", "125")
|
||||
data.SetVariable("totalSales", "1,850,000")
|
||||
data.SetVariable("newCustomers", "45")
|
||||
|
||||
##### 表格数据处理
|
||||
- [x] 数据导入导出
|
||||
- [x] 二维数组数据绑定
|
||||
- [x] 表格数据提取为数组
|
||||
- [x] 批量数据填充
|
||||
doc, _ := engine.RenderToDocument("sales_report", data)
|
||||
doc.Save("sales_report.docx")
|
||||
```
|
||||
|
||||
##### 表格访问和查询
|
||||
- [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] 表格整体样式
|
||||
- [x] 预定义表格样式模板
|
||||
- [x] 自定义表格样式创建
|
||||
- [x] 表格主题色彩应用
|
||||
- [x] 表格样式继承和覆盖
|
||||
- [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] 模板块管理和组织
|
||||
|
||||
## 使用示例
|
||||
### 📚 完整文档
|
||||
- [**📖 Wiki文档**](wordZero.wiki/) - 完整的使用文档和API参考
|
||||
- [**🚀 快速开始**](wordZero.wiki/01-快速开始) - 新手入门指南
|
||||
- [**⚡ 功能特性详览**](wordZero.wiki/14-功能特性详览) - 所有功能的详细说明
|
||||
- [**📊 性能基准测试**](wordZero.wiki/13-性能基准测试) - 跨语言性能对比分析
|
||||
- [**🏗️ 项目结构详解**](wordZero.wiki/15-项目结构详解) - 项目架构和代码组织
|
||||
|
||||
### 💡 使用示例
|
||||
查看 `examples/` 目录下的示例代码:
|
||||
|
||||
- `examples/basic/` - 基础功能演示
|
||||
- `examples/style_demo/` - 样式系统演示
|
||||
- `examples/table/` - 表格功能演示
|
||||
- `examples/table_layout/` - 表格布局和尺寸演示
|
||||
- `examples/table_style/` - 表格样式和外观演示
|
||||
- `examples/cell_advanced/` - 单元格高级功能演示
|
||||
- `examples/cell_iterator/` - **单元格迭代器功能演示** ✨ **新增**
|
||||
- `examples/formatting/` - 格式化演示
|
||||
- `examples/page_settings/` - **页面设置演示** ✨ **新增**
|
||||
- `examples/advanced_features/` - **高级功能综合演示** ✨ **新增**
|
||||
- 页眉页脚功能演示
|
||||
- 目录生成和管理演示
|
||||
- 脚注尾注功能演示
|
||||
- 列表和编号演示
|
||||
- 结构化文档标签演示
|
||||
- `examples/template_demo/` - **模板功能演示** ✨ **新增**
|
||||
- 基础变量替换演示
|
||||
- 条件语句功能演示
|
||||
- 循环语句功能演示
|
||||
- 模板继承功能演示
|
||||
- 复杂模板综合应用演示
|
||||
- 从现有文档创建模板演示
|
||||
- 结构体数据绑定演示
|
||||
- `examples/page_settings/` - 页面设置演示
|
||||
- `examples/advanced_features/` - 高级功能综合演示
|
||||
- `examples/template_demo/` - 模板功能演示
|
||||
- `examples/template_inheritance_demo/` - 模板继承功能演示 ✨ **新增**
|
||||
|
||||
运行示例:
|
||||
```bash
|
||||
@@ -396,31 +160,58 @@ go run ./examples/style_demo/
|
||||
# 运行表格演示
|
||||
go run ./examples/table/
|
||||
|
||||
# 运行表格布局和尺寸演示
|
||||
go run ./examples/table_layout/
|
||||
|
||||
# 运行表格样式演示
|
||||
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/
|
||||
# 运行模板继承演示
|
||||
go run ./examples/template_inheritance_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!在提交代码前请确保:
|
||||
@@ -433,3 +224,11 @@ go run ./examples/template_demo/
|
||||
## 许可证
|
||||
|
||||
本项目采用 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}}` 语法进行条件渲染
|
||||
**循环语句**: 支持 `{{#each 列表}}...{{/each}}` 语法进行列表渲染
|
||||
**模板继承**: 支持 `{{extends "基础模板"}}` 语法进行模板继承
|
||||
**模板继承**: 支持 `{{extends "基础模板"}}` 语法和 `{{#block "块名"}}...{{/block}}` 块重写机制,实现真正的模板继承
|
||||
- **块定义**: 在基础模板中定义可重写的内容块
|
||||
- **块重写**: 在子模板中选择性重写特定块,未重写的块保持父模板默认内容
|
||||
- **多级继承**: 支持模板的多层继承关系
|
||||
- **完整保留**: 未重写的块完整保留父模板的默认内容和格式
|
||||
**循环内条件**: 完美支持循环内部的条件表达式,如 `{{#each items}}{{#if isActive}}...{{/if}}{{/each}}`
|
||||
**数据类型支持**: 支持字符串、数字、布尔值、对象等多种数据类型
|
||||
**结构体绑定**: 支持从Go结构体自动生成模板数据
|
||||
@@ -144,6 +148,169 @@
|
||||
- [`Clear()`](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) - 从文件添加图片
|
||||
- [`AddImageFromData(imageData []byte, fileName string, format ImageFormat, width, height int, config *ImageConfig)`](image.go) - 从数据添加图片
|
||||
|
@@ -23,6 +23,12 @@ var (
|
||||
|
||||
// ErrInvalidTemplateData 无效模板数据
|
||||
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 模板引擎
|
||||
@@ -34,22 +40,26 @@ type TemplateEngine struct {
|
||||
|
||||
// Template 模板结构
|
||||
type Template struct {
|
||||
Name string // 模板名称
|
||||
Content string // 模板内容
|
||||
BaseDoc *Document // 基础文档
|
||||
Variables map[string]string // 模板变量
|
||||
Blocks []*TemplateBlock // 模板块
|
||||
Parent *Template // 父模板(用于继承)
|
||||
Name string // 模板名称
|
||||
Content string // 模板内容
|
||||
BaseDoc *Document // 基础文档
|
||||
Variables map[string]string // 模板变量
|
||||
Blocks []*TemplateBlock // 模板块列表
|
||||
Parent *Template // 父模板(用于继承)
|
||||
DefinedBlocks map[string]*TemplateBlock // 定义的块映射
|
||||
}
|
||||
|
||||
// TemplateBlock 模板块
|
||||
type TemplateBlock struct {
|
||||
Type string // 块类型:variable, if, each, inherit
|
||||
Content string // 块内容
|
||||
Condition string // 条件(if块使用)
|
||||
Variable string // 变量名(each块使用)
|
||||
Children []*TemplateBlock // 子块
|
||||
Data map[string]interface{} // 块数据
|
||||
Type string // 块类型:variable, if, each, inherit, block
|
||||
Name string // 块名称(block类型使用)
|
||||
Content string // 块内容
|
||||
Condition string // 条件(if块使用)
|
||||
Variable string // 变量名(each块使用)
|
||||
Children []*TemplateBlock // 子块
|
||||
Data map[string]interface{} // 块数据
|
||||
DefaultContent string // 默认内容(用于可选重写)
|
||||
IsOverridden bool // 是否被重写
|
||||
}
|
||||
|
||||
// TemplateData 模板数据
|
||||
@@ -80,10 +90,11 @@ func (te *TemplateEngine) LoadTemplate(name, content string) (*Template, error)
|
||||
defer te.mutex.Unlock()
|
||||
|
||||
template := &Template{
|
||||
Name: name,
|
||||
Content: content,
|
||||
Variables: make(map[string]string),
|
||||
Blocks: make([]*TemplateBlock, 0),
|
||||
Name: name,
|
||||
Content: content,
|
||||
Variables: make(map[string]string),
|
||||
Blocks: make([]*TemplateBlock, 0),
|
||||
DefinedBlocks: make(map[string]*TemplateBlock),
|
||||
}
|
||||
|
||||
// 解析模板内容
|
||||
@@ -109,11 +120,12 @@ func (te *TemplateEngine) LoadTemplateFromDocument(name string, doc *Document) (
|
||||
}
|
||||
|
||||
template := &Template{
|
||||
Name: name,
|
||||
Content: content,
|
||||
BaseDoc: doc,
|
||||
Variables: make(map[string]string),
|
||||
Blocks: make([]*TemplateBlock, 0),
|
||||
Name: name,
|
||||
Content: content,
|
||||
BaseDoc: doc,
|
||||
Variables: make(map[string]string),
|
||||
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) 标志以匹配换行符)
|
||||
ifPattern := regexp.MustCompile(`(?s)\{\{#if\s+(\w+)\}\}(.*?)\{\{/if\}\}`)
|
||||
ifMatches := ifPattern.FindAllStringSubmatch(content, -1)
|
||||
@@ -222,12 +255,31 @@ func (te *TemplateEngine) parseTemplate(template *Template) error {
|
||||
baseTemplate, err := te.getTemplateInternal(baseName)
|
||||
if err == nil {
|
||||
template.Parent = baseTemplate
|
||||
// 处理块重写
|
||||
te.processBlockOverrides(template, baseTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
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 渲染模板到新文档
|
||||
func (te *TemplateEngine) RenderToDocument(templateName string, data *TemplateData) (*Document, error) {
|
||||
template, err := te.GetTemplate(templateName)
|
||||
@@ -261,17 +313,27 @@ func (te *TemplateEngine) RenderToDocument(templateName string, data *TemplateDa
|
||||
|
||||
// renderTemplate 渲染模板
|
||||
func (te *TemplateEngine) renderTemplate(template *Template, data *TemplateData) (string, error) {
|
||||
content := template.Content
|
||||
var content string
|
||||
|
||||
// 处理继承
|
||||
// 处理继承:如果有父模板,使用父模板作为基础
|
||||
if template.Parent != nil {
|
||||
// 渲染父模板作为基础内容
|
||||
parentContent, err := te.renderTemplate(template.Parent, data)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
@@ -284,6 +346,50 @@ func (te *TemplateEngine) renderTemplate(template *Template, data *TemplateData)
|
||||
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 渲染变量
|
||||
func (te *TemplateEngine) renderVariables(content string, variables map[string]interface{}) string {
|
||||
varPattern := regexp.MustCompile(`\{\{(\w+)\}\}`)
|
||||
@@ -431,6 +537,11 @@ func (te *TemplateEngine) ValidateTemplate(template *Template) error {
|
||||
return WrapErrorWithContext("validate_template", err, template.Name)
|
||||
}
|
||||
|
||||
// 检查块语句配对
|
||||
if err := te.validateBlockStatements(content); err != nil {
|
||||
return WrapErrorWithContext("validate_template", err, template.Name)
|
||||
}
|
||||
|
||||
// 检查if语句配对
|
||||
if err := te.validateIfStatements(content); err != nil {
|
||||
return WrapErrorWithContext("validate_template", err, template.Name)
|
||||
@@ -456,6 +567,18 @@ func (te *TemplateEngine) validateBrackets(content string) error {
|
||||
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语句配对
|
||||
func (te *TemplateEngine) validateIfStatements(content string) error {
|
||||
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