重构模板引擎,新增文档克隆和变量替换功能,优化模板渲染逻辑

This commit is contained in:
zero
2025-06-03 17:59:37 +08:00
parent 67e6333a07
commit 3be9cc345a
2 changed files with 660 additions and 33 deletions

View File

@@ -0,0 +1,356 @@
// Package main 演示增强的模板功能
package main
import (
"fmt"
"log"
"os"
"time"
"github.com/ZeroHawkeye/wordZero/pkg/document"
)
func main() {
fmt.Println("=== WordZero 增强模板功能演示 ===")
// 确保输出目录存在
os.MkdirAll("examples/output", 0755)
// 演示1保持样式的变量替换
demonstrateStyledVariableTemplate()
// 演示2表格模板功能
demonstrateTableTemplate()
// 演示3复杂文档模板
demonstrateComplexDocumentTemplate()
fmt.Println("\n✅ 增强模板功能演示完成!")
}
// demonstrateStyledVariableTemplate 演示保持样式的变量替换
func demonstrateStyledVariableTemplate() {
fmt.Println("\n📝 演示1保持样式的变量替换")
// 创建一个包含格式化内容的模板文档
templateDoc := document.New()
// 添加标题
titlePara := templateDoc.AddParagraph("")
titleRun := &document.Run{
Properties: &document.RunProperties{
Bold: &document.Bold{},
FontSize: &document.FontSize{Val: "32"}, // 16磅
Color: &document.Color{Val: "0066CC"},
},
Text: document.Text{Content: "{{title}}"},
}
titlePara.Runs = []document.Run{*titleRun}
titlePara.SetAlignment(document.AlignCenter)
// 添加副标题
subtitlePara := templateDoc.AddParagraph("")
subtitleRun := &document.Run{
Properties: &document.RunProperties{
Italic: &document.Italic{},
FontSize: &document.FontSize{Val: "24"}, // 12磅
Color: &document.Color{Val: "666666"},
},
Text: document.Text{Content: "作者:{{author}} | 日期:{{date}}"},
}
subtitlePara.Runs = []document.Run{*subtitleRun}
subtitlePara.SetAlignment(document.AlignCenter)
// 添加正文段落
bodyPara := templateDoc.AddParagraph("")
// 混合格式的正文
normalRun := &document.Run{
Text: document.Text{Content: "这是一个"},
}
boldRun := &document.Run{
Properties: &document.RunProperties{
Bold: &document.Bold{},
},
Text: document.Text{Content: "{{status}}"},
}
endRun := &document.Run{
Text: document.Text{Content: "的项目,当前进度为"},
}
progressRun := &document.Run{
Properties: &document.RunProperties{
Bold: &document.Bold{},
Color: &document.Color{Val: "FF0000"},
},
Text: document.Text{Content: "{{progress}}%"},
}
finalRun := &document.Run{
Text: document.Text{Content: "。"},
}
bodyPara.Runs = []document.Run{*normalRun, *boldRun, *endRun, *progressRun, *finalRun}
// 保存模板文档
templateFile := "examples/output/styled_template.docx"
err := templateDoc.Save(templateFile)
if err != nil {
log.Fatalf("保存模板文档失败: %v", err)
}
fmt.Printf("✓ 创建样式模板文档: %s\n", templateFile)
// 创建模板引擎并加载模板
engine := document.NewTemplateEngine()
_, err = engine.LoadTemplateFromDocument("styled_template", templateDoc)
if err != nil {
log.Fatalf("加载模板失败: %v", err)
}
// 准备数据
data := document.NewTemplateData()
data.SetVariable("title", "WordZero 项目报告")
data.SetVariable("author", "张开发")
data.SetVariable("date", time.Now().Format("2006年01月02日"))
data.SetVariable("status", "进行中")
data.SetVariable("progress", "85")
// 使用新的渲染方法
resultDoc, err := engine.RenderTemplateToDocument("styled_template", data)
if err != nil {
log.Fatalf("渲染模板失败: %v", err)
}
// 保存结果文档
outputFile := "examples/output/styled_result_" + time.Now().Format("20060102_150405") + ".docx"
err = resultDoc.Save(outputFile)
if err != nil {
log.Fatalf("保存结果文档失败: %v", err)
}
fmt.Printf("✓ 生成保持样式的文档: %s\n", outputFile)
}
// demonstrateTableTemplate 演示表格模板功能
func demonstrateTableTemplate() {
fmt.Println("\n📊 演示2表格模板功能")
// 创建包含表格模板的文档
templateDoc := document.New()
// 添加标题
templateDoc.AddHeadingParagraph("销售报表", 1)
// 创建表格模板
tableConfig := &document.TableConfig{
Rows: 2, // 表头 + 模板行
Cols: 4,
Width: 9000, // 15cm
}
table := templateDoc.CreateTable(tableConfig)
// 设置表头
table.SetCellText(0, 0, "产品名称")
table.SetCellText(0, 1, "销售数量")
table.SetCellText(0, 2, "单价")
table.SetCellText(0, 3, "总金额")
// 设置表头样式
headerFormat := &document.TextFormat{
Bold: true,
FontSize: 12,
FontColor: "FFFFFF",
}
headerTexts := []string{"产品名称", "销售数量", "单价", "总金额"}
for i := 0; i < 4; i++ {
table.SetCellFormattedText(0, i, headerTexts[i], headerFormat)
// 设置表头背景色
table.SetCellShading(0, i, &document.ShadingConfig{
Pattern: document.ShadingPatternClear,
BackgroundColor: "366092",
})
}
// 设置模板行(包含循环语法)
table.SetCellText(1, 0, "{{#each items}}{{name}}")
table.SetCellText(1, 1, "{{quantity}}")
table.SetCellText(1, 2, "{{price}}")
table.SetCellText(1, 3, "{{total}}{{/each}}")
// 添加表格到文档
templateDoc.Body.AddElement(table)
// 保存模板文档
templateFile := "examples/output/table_template.docx"
err := templateDoc.Save(templateFile)
if err != nil {
log.Fatalf("保存表格模板失败: %v", err)
}
fmt.Printf("✓ 创建表格模板文档: %s\n", templateFile)
// 创建模板引擎并加载模板
engine := document.NewTemplateEngine()
_, err = engine.LoadTemplateFromDocument("table_template", templateDoc)
if err != nil {
log.Fatalf("加载表格模板失败: %v", err)
}
// 准备销售数据
data := document.NewTemplateData()
salesItems := []interface{}{
map[string]interface{}{
"name": "WordZero专业版",
"quantity": "10",
"price": "¥999.00",
"total": "¥9,990.00",
},
map[string]interface{}{
"name": "技术支持服务",
"quantity": "12",
"price": "¥500.00",
"total": "¥6,000.00",
},
map[string]interface{}{
"name": "培训课程",
"quantity": "5",
"price": "¥800.00",
"total": "¥4,000.00",
},
}
data.SetList("items", salesItems)
// 渲染表格模板
resultDoc, err := engine.RenderTemplateToDocument("table_template", data)
if err != nil {
log.Fatalf("渲染表格模板失败: %v", err)
}
// 保存结果文档
outputFile := "examples/output/table_result_" + time.Now().Format("20060102_150405") + ".docx"
err = resultDoc.Save(outputFile)
if err != nil {
log.Fatalf("保存表格结果失败: %v", err)
}
fmt.Printf("✓ 生成表格报表文档: %s\n", outputFile)
}
// demonstrateComplexDocumentTemplate 演示复杂文档模板
func demonstrateComplexDocumentTemplate() {
fmt.Println("\n📋 演示3复杂文档模板")
// 创建复杂的文档模板
templateDoc := document.New()
// 注释:文档属性设置功能需要实现
// templateDoc.SetProperty("title", "{{projectName}} - 项目报告")
// templateDoc.SetProperty("author", "{{manager}}")
// templateDoc.SetProperty("subject", "项目进度报告")
// 添加封面
titlePara := templateDoc.AddParagraph("")
titleRun := &document.Run{
Properties: &document.RunProperties{
Bold: &document.Bold{},
FontSize: &document.FontSize{Val: "48"}, // 24磅
Color: &document.Color{Val: "2F5496"},
},
Text: document.Text{Content: "{{projectName}}"},
}
titlePara.Runs = []document.Run{*titleRun}
titlePara.SetAlignment(document.AlignCenter)
titlePara.SetSpacing(&document.SpacingConfig{
BeforePara: 72, // 1英寸
AfterPara: 36, // 0.5英寸
})
// 添加项目基本信息
templateDoc.AddHeadingParagraph("项目基本信息", 2)
templateDoc.AddParagraph("项目经理:{{manager}}\n报告日期{{reportDate}}\n项目状态{{#if isActive}}进行中{{/if}}{{#if isComplete}}已完成{{/if}}\n完成进度{{progress}}%")
// 添加团队成员表格
templateDoc.AddHeadingParagraph("团队成员", 2)
teamTableConfig := &document.TableConfig{
Rows: 2, // 表头 + 模板行
Cols: 3,
Width: 8000,
}
teamTable := templateDoc.CreateTable(teamTableConfig)
// 设置团队表格表头
teamTable.SetCellText(0, 0, "姓名")
teamTable.SetCellText(0, 1, "角色")
teamTable.SetCellText(0, 2, "工作内容")
// 设置团队表格模板行
teamTable.SetCellText(1, 0, "{{#each team}}{{name}}")
teamTable.SetCellText(1, 1, "{{role}}")
teamTable.SetCellText(1, 2, "{{work}}{{/each}}")
templateDoc.Body.AddElement(teamTable)
// 保存复杂模板
templateFile := "examples/output/complex_template.docx"
err := templateDoc.Save(templateFile)
if err != nil {
log.Fatalf("保存复杂模板失败: %v", err)
}
fmt.Printf("✓ 创建复杂文档模板: %s\n", templateFile)
// 创建模板引擎并渲染
engine := document.NewTemplateEngine()
_, err = engine.LoadTemplateFromDocument("complex_template", templateDoc)
if err != nil {
log.Fatalf("加载复杂模板失败: %v", err)
}
// 准备复杂数据
data := document.NewTemplateData()
data.SetVariable("projectName", "WordZero 企业文档管理系统")
data.SetVariable("manager", "李项目经理")
data.SetVariable("reportDate", time.Now().Format("2006年01月02日"))
data.SetVariable("progress", "88")
// 设置条件
data.SetCondition("isActive", true)
data.SetCondition("isComplete", false)
// 设置团队成员数据
teamMembers := []interface{}{
map[string]interface{}{
"name": "张开发",
"role": "技术负责人",
"work": "架构设计与核心开发",
},
map[string]interface{}{
"name": "王测试",
"role": "质量保证",
"work": "功能测试与性能优化",
},
map[string]interface{}{
"name": "刘设计",
"role": "UI设计师",
"work": "界面设计与用户体验",
},
}
data.SetList("team", teamMembers)
// 渲染复杂文档
resultDoc, err := engine.RenderTemplateToDocument("complex_template", data)
if err != nil {
log.Fatalf("渲染复杂模板失败: %v", err)
}
// 保存结果
outputFile := "examples/output/complex_result_" + time.Now().Format("20060102_150405") + ".docx"
err = resultDoc.Save(outputFile)
if err != nil {
log.Fatalf("保存复杂结果失败: %v", err)
}
fmt.Printf("✓ 生成复杂项目报告: %s\n", outputFile)
}

View File

@@ -605,37 +605,14 @@ func (te *TemplateEngine) validateEachStatements(content string) error {
// documentToTemplateString 将文档转换为模板字符串
func (te *TemplateEngine) documentToTemplateString(doc *Document) (string, error) {
var content strings.Builder
// 遍历文档的所有段落,生成模板字符串
for _, element := range doc.Body.Elements {
switch elem := element.(type) {
case *Paragraph:
// 处理段落
for _, run := range elem.Runs {
content.WriteString(run.Text.Content)
}
content.WriteString("\n")
case *Table:
// 处理表格
content.WriteString("{{#table}}\n")
for i, row := range elem.Rows {
content.WriteString(fmt.Sprintf("{{#row_%d}}\n", i))
for j := range row.Cells {
content.WriteString(fmt.Sprintf("{{cell_%d_%d}}", i, j))
}
content.WriteString("{{/row}}\n")
}
content.WriteString("{{/table}}\n")
}
}
return content.String(), nil
// 这里不再转换为纯字符串,而是保持原始文档结构
// 实际上我们应该直接在原文档上进行变量替换
return "", nil // 将在新的方法中处理
}
// cloneDocument 克隆文档
func (te *TemplateEngine) cloneDocument(source *Document) *Document {
// 简单实现:创建新文档并复制基本结构
// 创建新文档
doc := New()
// 复制样式管理器
@@ -643,24 +620,318 @@ func (te *TemplateEngine) cloneDocument(source *Document) *Document {
doc.styleManager = source.styleManager
}
// 深度复制文档体元素
for _, element := range source.Body.Elements {
switch elem := element.(type) {
case *Paragraph:
// 复制段落
newPara := &Paragraph{
Properties: elem.Properties,
Runs: make([]Run, len(elem.Runs)),
}
for i, run := range elem.Runs {
newRun := Run{
Properties: run.Properties,
Text: Text{Content: run.Text.Content},
}
newPara.Runs[i] = newRun
}
doc.Body.Elements = append(doc.Body.Elements, newPara)
case *Table:
// 复制表格
newTable := &Table{
Properties: elem.Properties,
Grid: elem.Grid,
Rows: make([]TableRow, len(elem.Rows)),
}
for i, row := range elem.Rows {
newRow := TableRow{
Properties: row.Properties,
Cells: make([]TableCell, len(row.Cells)),
}
for j, cell := range row.Cells {
newCell := TableCell{
Properties: cell.Properties,
Paragraphs: make([]Paragraph, len(cell.Paragraphs)),
}
for k, para := range cell.Paragraphs {
newPara := Paragraph{
Properties: para.Properties,
Runs: make([]Run, len(para.Runs)),
}
for l, run := range para.Runs {
newRun := Run{
Properties: run.Properties,
Text: Text{Content: run.Text.Content},
}
newPara.Runs[l] = newRun
}
newCell.Paragraphs[k] = newPara
}
newRow.Cells[j] = newCell
}
newTable.Rows[i] = newRow
}
doc.Body.Elements = append(doc.Body.Elements, newTable)
}
}
return doc
}
// applyRenderedContentToDocument 将渲染内容应用到文档
func (te *TemplateEngine) applyRenderedContentToDocument(doc *Document, content string) error {
// 将渲染后的内容按行分割并添加到文档
lines := strings.Split(content, "\n")
// 这个方法将被新的结构化处理方法替代
return nil
}
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" {
doc.AddParagraph(line)
// RenderTemplateToDocument 渲染模板到新文档(新的主要方法)
func (te *TemplateEngine) RenderTemplateToDocument(templateName string, data *TemplateData) (*Document, error) {
template, err := te.GetTemplate(templateName)
if err != nil {
return nil, WrapErrorWithContext("render_template_to_document", err, templateName)
}
// 如果有基础文档,克隆它并在其上进行变量替换
if template.BaseDoc != nil {
doc := te.cloneDocument(template.BaseDoc)
// 在文档结构中直接进行变量替换
err := te.replaceVariablesInDocument(doc, data)
if err != nil {
return nil, WrapErrorWithContext("render_template_to_document", err, templateName)
}
return doc, nil
}
// 如果没有基础文档,使用原有的方式
return te.RenderToDocument(templateName, data)
}
// replaceVariablesInDocument 在文档结构中直接替换变量
func (te *TemplateEngine) replaceVariablesInDocument(doc *Document, data *TemplateData) error {
for _, element := range doc.Body.Elements {
switch elem := element.(type) {
case *Paragraph:
// 处理段落中的变量替换
err := te.replaceVariablesInParagraph(elem, data)
if err != nil {
return err
}
case *Table:
// 处理表格中的变量替换和模板语法
err := te.replaceVariablesInTable(elem, data)
if err != nil {
return err
}
}
}
return nil
}
// replaceVariablesInParagraph 在段落中替换变量
func (te *TemplateEngine) replaceVariablesInParagraph(para *Paragraph, data *TemplateData) error {
for i := range para.Runs {
if para.Runs[i].Text.Content != "" {
// 替换普通变量
para.Runs[i].Text.Content = te.renderVariables(para.Runs[i].Text.Content, data.Variables)
// 处理条件语句
para.Runs[i].Text.Content = te.renderConditionals(para.Runs[i].Text.Content, data.Conditions)
}
}
return nil
}
// replaceVariablesInTable 在表格中替换变量和处理表格模板
func (te *TemplateEngine) replaceVariablesInTable(table *Table, data *TemplateData) error {
// 检查是否有表格循环模板
if len(table.Rows) > 0 && te.isTableTemplate(table) {
return te.renderTableTemplate(table, data)
}
// 普通表格变量替换
for i := range table.Rows {
for j := range table.Rows[i].Cells {
for k := range table.Rows[i].Cells[j].Paragraphs {
err := te.replaceVariablesInParagraph(&table.Rows[i].Cells[j].Paragraphs[k], data)
if err != nil {
return err
}
}
}
}
return nil
}
// isTableTemplate 检查表格是否包含模板语法
func (te *TemplateEngine) isTableTemplate(table *Table) bool {
if len(table.Rows) == 0 {
return false
}
// 检查所有行是否包含循环语法
for _, row := range table.Rows {
for _, cell := range row.Cells {
for _, para := range cell.Paragraphs {
for _, run := range para.Runs {
if run.Text.Content != "" && te.containsTemplateLoop(run.Text.Content) {
return true
}
}
}
}
}
return false
}
// containsTemplateLoop 检查文本是否包含循环模板语法
func (te *TemplateEngine) containsTemplateLoop(text string) bool {
eachPattern := regexp.MustCompile(`\{\{#each\s+\w+\}\}`)
return eachPattern.MatchString(text)
}
// renderTableTemplate 渲染表格模板
func (te *TemplateEngine) renderTableTemplate(table *Table, data *TemplateData) error {
if len(table.Rows) == 0 {
return nil
}
// 找到模板行(包含循环语法的行)
templateRowIndex := -1
var listVarName string
for i, row := range table.Rows {
found := false
for _, cell := range row.Cells {
for _, para := range cell.Paragraphs {
for _, run := range para.Runs {
if run.Text.Content != "" {
eachPattern := regexp.MustCompile(`\{\{#each\s+(\w+)\}\}`)
matches := eachPattern.FindStringSubmatch(run.Text.Content)
if len(matches) > 1 {
templateRowIndex = i
listVarName = matches[1]
found = true
break
}
}
}
if found {
break
}
}
if found {
break
}
}
if found {
break
}
}
if templateRowIndex < 0 || listVarName == "" {
return nil
}
// 获取列表数据
listData, exists := data.Lists[listVarName]
if !exists || len(listData) == 0 {
// 删除模板行
table.Rows = append(table.Rows[:templateRowIndex], table.Rows[templateRowIndex+1:]...)
return nil
}
// 保存模板行
templateRow := table.Rows[templateRowIndex]
newRows := make([]TableRow, 0)
// 保留模板行之前的行
newRows = append(newRows, table.Rows[:templateRowIndex]...)
// 为每个数据项生成新行
for _, item := range listData {
newRow := te.cloneTableRow(&templateRow)
// 在新行中替换变量
if itemMap, ok := item.(map[string]interface{}); ok {
for i := range newRow.Cells {
for j := range newRow.Cells[i].Paragraphs {
for k := range newRow.Cells[i].Paragraphs[j].Runs {
if newRow.Cells[i].Paragraphs[j].Runs[k].Text.Content != "" {
// 移除模板语法标记
content := newRow.Cells[i].Paragraphs[j].Runs[k].Text.Content
content = regexp.MustCompile(`\{\{#each\s+\w+\}\}`).ReplaceAllString(content, "")
content = regexp.MustCompile(`\{\{/each\}\}`).ReplaceAllString(content, "")
// 替换变量
for key, value := range itemMap {
placeholder := fmt.Sprintf("{{%s}}", key)
content = strings.ReplaceAll(content, placeholder, te.interfaceToString(value))
}
newRow.Cells[i].Paragraphs[j].Runs[k].Text.Content = content
}
}
}
}
}
newRows = append(newRows, *newRow)
}
// 保留模板行之后的行
newRows = append(newRows, table.Rows[templateRowIndex+1:]...)
// 更新表格行
table.Rows = newRows
return nil
}
// cloneTableRow 克隆表格行
func (te *TemplateEngine) cloneTableRow(row *TableRow) *TableRow {
newRow := &TableRow{
Properties: row.Properties,
Cells: make([]TableCell, len(row.Cells)),
}
for i, cell := range row.Cells {
newCell := TableCell{
Properties: cell.Properties,
Paragraphs: make([]Paragraph, len(cell.Paragraphs)),
}
for j, para := range cell.Paragraphs {
newPara := Paragraph{
Properties: para.Properties,
Runs: make([]Run, len(para.Runs)),
}
for k, run := range para.Runs {
newRun := Run{
Properties: run.Properties,
Text: Text{Content: run.Text.Content},
}
newPara.Runs[k] = newRun
}
newCell.Paragraphs[j] = newPara
}
newRow.Cells[i] = newCell
}
return newRow
}
// NewTemplateData 创建新的模板数据
func NewTemplateData() *TemplateData {
return &TemplateData{