当前位置:   article > 正文

Golang处理Word文档模板实现标签填充|表格插入|图标绘制和插入|删除段落|删除标签_wordtemplate.replacedocument(worddatamap, 1);

wordtemplate.replacedocument(worddatamap, 1);

本教程主要实现【Golang处理Word文档模板实现标签填充|表格插入|图标绘制和插入|删除段落|删除标签】。
本文源码:https://gitee.com/songfayuan/go-zero-demo
教程源码分支:master 分支(_examples/word-template/fill-word-template.go)

Golang处理Word文档模板教程

本教程将指导您使用Golang处理Word文档模板,包括自定义标签填充、动态插入表格、绘制图表和插入图表。我们将使用unioffice库和gg库来完成这些任务。

前提条件

在开始之前,请确保您已安装以下库:

  1. unioffice
  2. gg

您可以使用以下命令安装这些库:

go get -u github.com/Esword618/unioffice
go get -u github.com/fogleman/gg
  • 1
  • 2

代码结构

我们的代码分为以下几部分:

  1. 打开Word文档
  2. 填充模板中的变量
  3. 在指定标签处插入表格
  4. 创建折线图并保存为图片
  5. 在指定标签处插入图表
  6. 保存更新后的文档

示例代码

以下是完整的示例代码:

package main

import (
	"fmt"
	"log"
	"math"
	"strings"

	"github.com/Esword618/unioffice/color"
	"github.com/Esword618/unioffice/common"
	"github.com/Esword618/unioffice/document"
	"github.com/Esword618/unioffice/measurement"
	"github.com/Esword618/unioffice/schema/soo/wml"
	"github.com/fogleman/gg"
)

//教程:https://blog.csdn.net/u011019141/article/details/140788882

func main() {
	// 定义文档路径和图表文件路径
	docPath := "_examples/word-template/template.docx"
	lineChartFile := "/Users/songfayuan/Downloads/line_chart.PNG"
	barChartFile := "/Users/songfayuan/Downloads/bar_chart.png"
	pieChartFile := "/Users/songfayuan/Downloads/pie_chart.png"
	updatedDocPath := "/Users/songfayuan/Downloads/updated_demo.docx"

	// 打开文档
	doc, err := openDocument(docPath)
	if err != nil {
		log.Fatalf("无法打开文档: %v", err)
	}

	// 填充模板中的变量
	fillTemplate(doc, map[string]string{
		"{{TASK_NAME}}": "任务名称示例",
		"{{DETAILS}}":   "详细信息示例",
	})

	// 在指定标签处插入表格
	if err := insertTableAt(doc, "{{biaoge}}"); err != nil {
		log.Fatalf("插入表格时出错: %v", err)
	}

	// 创建折线图并保存为图片
	if err := createLineChart(lineChartFile); err != nil {
		log.Fatalf("创建图表时出错: %v", err)
	}

	// 在指定标签处插入图表
	if err := insertImageAt(doc, lineChartFile, "{{tubiao}}"); err != nil {
		log.Fatalf("插入图表时出错: %v", err)
	}

	// 创建柱状图并保存为图片
	if err := createBarChart(barChartFile); err != nil {
		log.Fatalf("创建柱状图时出错: %v", err)
	}

	// 在指定标签处插入柱状图
	if err := insertImageAt(doc, barChartFile, "{{zhuzhuangtu}}"); err != nil {
		log.Fatalf("插入柱状图时出错: %v", err)
	}

	// 创建饼图并保存为图片
	if err := createPieChart(pieChartFile); err != nil {
		log.Fatalf("创建饼图时出错: %v", err)
	}

	// 在指定标签处插入饼图
	if err := insertImageAt(doc, pieChartFile, "{{bingtu}}"); err != nil {
		log.Fatalf("插入饼图时出错: %v", err)
	}

	// 删除{{a}}到{{b}}之间的段落
	if err := removeParagraphsBetweenTags(doc, "{{a}}", "{{b}}"); err != nil {
		log.Fatalf("删除段落时出错: %v", err)
	}

	// 删除指定标签
	if err := removeParagraphWithTag(doc, "{{shanchu}}"); err != nil {
		log.Fatalf("删除指定标签时出错: %v", err)
	}

	// 保存更新后的Word文档
	if err := doc.SaveToFile(updatedDocPath); err != nil {
		log.Fatalf("无法保存文档: %v", err)
	}
	fmt.Println("文档更新成功")
}

// 打开文档
func openDocument(path string) (*document.Document, error) {
	return document.Open(path) // 使用unioffice库打开指定路径的文档
}

// 填充模板中的变量
func fillTemplate(doc *document.Document, replacements map[string]string) {
	for _, para := range doc.Paragraphs() { // 遍历文档中的每个段落
		for _, run := range para.Runs() { // 遍历段落中的每个运行(文本片段)
			text := run.Text()
			for placeholder, replacement := range replacements { // 遍历需要替换的占位符
				if strings.Contains(text, placeholder) { // 如果文本包含占位符
					text = strings.ReplaceAll(text, placeholder, replacement) // 替换占位符
					run.Clear()                                               // 清除原有内容
					run.AddText(text)                                         // 添加替换后的文本
				}
			}
		}
	}
}

// 在指定标签处插入表格
func insertTableAt(doc *document.Document, tag string) error {
	paras := doc.Paragraphs() // 获取文档中的所有段落
	for _, para := range paras {
		if paraContainsTag(&para, tag) { // 如果段落包含指定标签
			// 创建并配置表格
			table := doc.InsertTableAfter(para)     // 在标签段落之后插入表格
			table.Properties().SetWidthPercent(100) // 设置表格宽度为100%
			borders := table.Properties().Borders()
			borders.SetAll(wml.ST_BorderSingle, color.Black, measurement.Dxa) // 设置所有边框为单线黑色

			for i := 0; i < 3; i++ { // 创建表格行和单元格
				row := table.AddRow()
				for j := 0; j < 3; j++ {
					cell := row.AddCell()
					cellPara := cell.AddParagraph()
					cellRun := cellPara.AddRun()
					cellRun.AddText(fmt.Sprintf("单元格 %d-%d", i+1, j+1))
				}
			}

			//分隔不同表格
			//doc.InsertParagraphAfter(para).AddRun().AddText("--------------")
			doc.InsertParagraphAfter(para).AddRun()

			// 创建并配置表格
			table = doc.InsertTableAfter(para)      // 在标签段落之后插入表格
			table.Properties().SetWidthPercent(100) // 设置表格宽度为100%
			borders = table.Properties().Borders()
			borders.SetAll(wml.ST_BorderSingle, color.Black, measurement.Dxa) // 设置所有边框为单线黑色

			for i := 0; i < 3; i++ { // 创建表格行和单元格
				row := table.AddRow()
				for j := 0; j < 3; j++ {
					cell := row.AddCell()
					cellPara := cell.AddParagraph()
					cellRun := cellPara.AddRun()
					cellRun.AddText(fmt.Sprintf("单元格 %d-%d", i+1, j+1))
				}
			}

			// 移除标签段落
			replaceParagraphWithTable(&para, tag) // 替换标签段落为表格
			// 删除段落
			doc.RemoveParagraph(para) // 从文档中删除标签段落
			return nil
		}
	}
	return fmt.Errorf("未找到标签 %s", tag) // 如果未找到标签段落,返回错误
}

// 在指定标签处插入图表
func insertImageAt(doc *document.Document, imagePath string, tag string) error {
	paras := doc.Paragraphs() // 获取文档中的所有段落
	for _, para := range paras {
		if paraContainsTag(&para, tag) { // 如果段落包含指定标签
			img, err := common.ImageFromFile(imagePath) // 从文件中加载图片
			if err != nil {
				return fmt.Errorf("无法从文件中加载图片: %v", err)
			}

			// 创建图片引用
			iref, err := doc.AddImage(img) // 将图片添加到文档中
			if err != nil {
				return fmt.Errorf("无法将图片添加到文档: %v", err)
			}

			// 创建新的段落和运行以插入图表
			newPara := doc.InsertParagraphAfter(para) // 在标签段落之后插入新段落
			run := newPara.AddRun()

			// 插入图片到文档
			imgInl, err := run.AddDrawingInline(iref) // 在运行中添加图片
			if err != nil {
				return fmt.Errorf("插入图片时出错: %v", err)
			}
			imgInl.SetSize(6*measurement.Inch, 4*measurement.Inch) // 设置图片尺寸为6x4英寸

			// 移除标签段落
			replaceParagraphWithTable(&para, tag) // 替换标签段落为图表
			// 删除段落
			doc.RemoveParagraph(para) // 从文档中删除标签段落
			return nil
		}
	}
	return fmt.Errorf("未找到标签 %s", tag) // 如果未找到标签段落,返回错误
}

// 判断段落是否包含指定标签
func paraContainsTag(para *document.Paragraph, tag string) bool {
	for _, run := range para.Runs() { // 遍历段落中的每个运行
		if strings.Contains(run.Text(), tag) { // 如果运行文本包含标签
			return true
		}
	}
	return false
}

// 移除标签段落
func replaceParagraphWithTable(para *document.Paragraph, tag string) {
	// 找到标签的 Run
	for _, run := range para.Runs() {
		log.Printf("替换标签:tag =  %v", tag)
		if strings.Contains(run.Text(), tag) {
			para.InsertRunAfter(para.AddRun())
			run.Clear()         // 清除原有内容
			para.RemoveRun(run) // 移除运行
			break
		}
	}
}

// 删除两个标签之间的段落
func removeParagraphsBetweenTags(doc *document.Document, startTag, endTag string) error {
	paras := doc.Paragraphs()
	startIndex, endIndex := -1, -1

	// 找到包含startTag和endTag的段落索引
	for i, para := range paras {
		if paraContainsTag(&para, startTag) {
			startIndex = i
		}
		if paraContainsTag(&para, endTag) {
			endIndex = i
			break
		}
	}

	if startIndex == -1 {
		return fmt.Errorf("未找到标签 %s", startTag)
	}
	if endIndex == -1 {
		return fmt.Errorf("未找到标签 %s", endTag)
	}
	if startIndex >= endIndex {
		return fmt.Errorf("标签 %s 和 %s 之间的顺序不正确", startTag, endTag)
	}

	// 删除startTag和endTag之间的段落
	for i := startIndex; i <= endIndex; i++ {
		doc.RemoveParagraph(paras[i])
	}

	return nil
}

// 删除指定标签段落
func removeParagraphWithTag(doc *document.Document, tag string) error {
	paras := doc.Paragraphs()
	for _, para := range paras {
		if paraContainsTag(&para, tag) {
			doc.RemoveParagraph(para)
			return nil
		}
	}
	return fmt.Errorf("未找到标签 %s", tag)
}

// 创建折线图并保存为图片
func createLineChart(filename string) error {
	const (
		width  = 900 // 画布宽度
		height = 600 // 画布高度
	)

	// 数据
	data := []struct {
		label string
		value float64
	}{
		{"身份证", 15},
		{"电话号码", 20},
		{"地址信息", 25},
		{"银行卡号", 30},
		{"财务数据", 35},
		{"基础信息", 40},
		{"户籍信息", 45},
	}

	// 创建画布
	dc := gg.NewContext(width, height)
	dc.SetRGB(1, 1, 1) // 背景色为白色
	dc.Clear()

	barWidth := float64(width-200) / float64(len(data)) // 调整每个数据点之间的间隔
	maxValue := 50.0                                    // 纵坐标最大值

	// 加载自定义字体
	if err := dc.LoadFontFace("_examples/word-template/msyh.ttf", 12); err != nil {
		return fmt.Errorf("无法加载字体: %v", err)
	}

	// 绘制折线图
	dc.SetRGB(0, 0, 0) // 线条颜色为黑色
	for i, d := range data {
		x := 50 + float64(i)*(barWidth+20)                       // x轴起始位置
		y := height - 50 - (d.value / maxValue * (height - 100)) // y轴起始位置减去数据点的高度

		if i == 0 {
			dc.MoveTo(x, y)
		} else {
			dc.LineTo(x, y)
		}
	}
	dc.Stroke()

	// 绘制坐标轴
	dc.SetRGB(0, 0, 0)                              // 黑色
	dc.DrawLine(50, height-50, width-50, height-50) // X轴
	dc.DrawLine(50, height-50, 50, 50)              // Y轴
	dc.Stroke()

	// 添加横坐标标签
	for i, d := range data {
		dc.DrawStringAnchored(d.label, 50+float64(i)*(barWidth+20), height-30, 0.5, 1)
	}

	// 添加纵坐标标签
	for i := 0; i <= int(maxValue); i += 5 {
		y := height - 50 - (float64(i) / maxValue * (height - 100))
		dc.DrawStringAnchored(fmt.Sprintf("%d", int(i)), 30, y, 1, 0.5)
	}

	// 保存图像为PNG文件
	return dc.SavePNG(filename)
}

// 创建柱状图并保存为图片
func createBarChart(filename string) error {
	const (
		width  = 900 // 画布宽度
		height = 700 // 画布高度
	)

	// 数据
	data := []struct {
		label string
		value float64
	}{
		{"身份证", 15},
		{"电话号码", 20},
		{"地址信息", 25},
		{"银行卡号", 30},
		{"财务数据", 35},
		{"基础信息", 40},
		{"户籍信息", 45},
	}

	// 创建画布
	dc := gg.NewContext(width, height)
	dc.SetRGB(1, 1, 1) // 背景色为白色
	dc.Clear()

	barWidth := float64(width-200) / float64(len(data)) // 调整柱子的宽度
	barSpacing := 10.0                                  // 增加柱子之间的间隔
	maxValue := 50.0                                    // 纵坐标最大值

	// 定义颜色
	colors := []struct{ R, G, B float64 }{
		{0.8, 0.2, 0.2}, // 红色
		{0.2, 0.8, 0.2}, // 绿色
		{0.2, 0.2, 0.8}, // 蓝色
		{0.8, 0.8, 0.2}, // 黄色
		{0.8, 0.2, 0.8}, // 紫色
		{0.2, 0.8, 0.8}, // 青色
		{0.8, 0.8, 0.8}, // 灰色
	}

	// 加载自定义字体
	if err := dc.LoadFontFace("_examples/word-template/msyh.ttf", 12); err != nil {
		return fmt.Errorf("无法加载字体: %v", err)
	}

	// 绘制柱状图
	for i, d := range data {
		x := 50 + float64(i)*(barWidth+barSpacing)               // x轴起始位置
		y := height - 50 - (d.value / maxValue * (height - 100)) // y轴起始位置减去柱子的高度
		color := colors[i%len(colors)]                           // 循环使用颜色
		dc.SetRGB(color.R, color.G, color.B)
		dc.DrawRectangle(x, y, barWidth, (d.value / maxValue * (height - 100)))
		dc.Fill()
	}

	// 绘制坐标轴
	dc.SetRGB(0, 0, 0)                              // 黑色
	dc.DrawLine(50, height-50, width-50, height-50) // X轴
	dc.DrawLine(50, height-50, 50, 50)              // Y轴
	dc.Stroke()

	// 添加横坐标标签
	for i, d := range data {
		dc.DrawStringAnchored(d.label, 50+float64(i)*(barWidth+barSpacing)+barWidth/2, height-30, 0.5, 1)
	}

	// 添加纵坐标标签
	for i := 0; i <= int(maxValue); i += 5 {
		y := height - 50 - (float64(i) / maxValue * (height - 100))
		dc.DrawStringAnchored(fmt.Sprintf("%d", int(i)), 30, y, 1, 0.5)
	}

	// 保存图像为PNG文件
	return dc.SavePNG(filename)
}

// 创建饼图并保存为图片
func createPieChart(filename string) error {
	const (
		width  = 640
		height = 600
		radius = 200 // 半径
	)

	dc := gg.NewContext(width, height)
	dc.SetRGB(1, 1, 1) // 背景色为白色
	dc.Clear()

	data := []struct {
		label string
		value float64
	}{
		{"身份证", 15}, {"电话号码", 20}, {"地址信息", 25}, {"银行卡号", 30}, {"财务数据", 35}, {"基础信息", 40}, {"户籍信息", 45},
	}

	colors := []struct{ R, G, B float64 }{
		{0.9, 0.3, 0.3}, // 红色
		{0.3, 0.9, 0.3}, // 绿色
		{0.3, 0.3, 0.9}, // 蓝色
		{0.9, 0.9, 0.3}, // 黄色
		{0.9, 0.3, 0.9}, // 紫色
		{0.3, 0.9, 0.9}, // 青色
		{0.9, 0.6, 0.3}, // 橙色
	}

	// 设置字体(选择支持中文的字体)
	if err := dc.LoadFontFace("_examples/word-template/msyh.ttf", 12); err != nil {
		return fmt.Errorf("无法加载字体: %v", err)
	}

	var total float64
	for _, d := range data {
		total += d.value
	}

	startAngle := -math.Pi / 2 // 从顶部开始绘制
	for i, d := range data {
		percentage := d.value / total
		angle := percentage * 2 * math.Pi

		// 设置扇形颜色
		color := colors[i%len(colors)]
		dc.SetRGB(color.R, color.G, color.B)
		dc.DrawArc(width/2, height/2, radius, startAngle, startAngle+angle)
		dc.LineTo(width/2, height/2)
		dc.Fill()

		// 计算标签位置
		midAngle := startAngle + angle/2
		labelX := width/2 + (radius+20)*math.Cos(midAngle)
		labelY := height/2 + (radius+20)*math.Sin(midAngle)

		// 绘制标签和数据
		labelText := fmt.Sprintf("%s: %.0f", d.label, d.value)
		dc.SetRGB(0, 0, 0) // 文字颜色(黑色)
		dc.DrawStringAnchored(labelText, labelX, labelY, 0.5, 0.5)

		startAngle += angle
	}

	return dc.SavePNG(filename) // 保存图像为PNG文件
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482

解释

  1. 打开文档

    doc, err := openDocument(docPath)
    
    • 1

    使用unioffice库打开指定路径的Word文档。

  2. 填充模板中的变量

    fillTemplate(doc, map[string]string{
        "{{TASK_NAME}}": "任务名称示例",
        "{{DETAILS}}":   "详细信息示例",
    })
    
    • 1
    • 2
    • 3
    • 4

    遍历文档中的每个段落和运行(文本片段),如果文本包含占位符,将其替换为实际值。

  3. 在指定标签处插入表格

    if err := insertTableAt(doc, "{{biaoge}}"); err != nil {
        log.Fatalf("插入表格时出错: %v", err)
    }
    
    • 1
    • 2
    • 3

    查找包含指定标签的段落,在该段落之后插入表格并删除标签段落。

  4. 创建折线图并保存为图片

    if err := createLineChart(chartFile); err != nil {
        log.Fatalf("创建图表时出错: %v", err)
    }
    
    • 1
    • 2
    • 3

    使用gg库绘制折线图并保存为PNG文件。

  5. 在指定标签处插入图表

    if err := insertImageAt(doc, chartFile, "{{tubiao}}"); err != nil {
        log.Fatalf("插入图表时出错: %v", err)
    }
    
    • 1
    • 2
    • 3

    查找包含指定标签的段落,在该段落之后插入图表并删除标签段落。

  6. 保存更新后的文档

    if err := doc.SaveToFile(updatedDocPath); err != nil {
        log.Fatalf("无法保存文档: %v", err)
    }
    
    • 1
    • 2
    • 3

    将更新后的文档保存到指定路径。

运行代码

确保您已正确安装所需的库,并将代码中的文件路径替换为您自己的路径。运行代码后,您将得到一个填充了模板变量、插入了表格和图表的更新Word文档。

Word模板

标签填充
张三:{{TASK_NAME}}
李四:{{DETAILS}}

插入表格
表格
{{biaoge}}

插入图表
图表
{{tubiao}}
{{zhuzhuangtu}}
{{bingtu}}

删除标签之间段落
{{a}}
段落1
段落2
段落3
段落4
{{b}}
段落5
删除指定标签
删除指定标签
{{shanchu}}
其他段落
9282892890
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

拷贝到Word文档中保存为template.docx,长这样:
在这里插入图片描述

代码处理后的Word内容

在这里插入图片描述

优化版源码

优化版本,图片不保存到磁盘,保存到了缓存。

package main

import (
	"bytes"
	"fmt"
	"log"
	"math"
	"strings"

	"github.com/Esword618/unioffice/color"
	"github.com/Esword618/unioffice/common"
	"github.com/Esword618/unioffice/document"
	"github.com/Esword618/unioffice/measurement"
	"github.com/Esword618/unioffice/schema/soo/wml"
	"github.com/fogleman/gg"
)

//教程:https://blog.csdn.net/u011019141/article/details/140788882

func main() {
	// 定义文档路径和图表文件路径
	docPath := "_examples/word-template/template.docx"
	updatedDocPath := "/Users/songfayuan/Downloads/updated_demo.docx"

	// 打开文档
	doc, err := openDocument(docPath)
	if err != nil {
		log.Fatalf("无法打开文档: %v", err)
	}

	// 填充模板中的变量
	fillTemplate(doc, map[string]string{
		"{{TASK_NAME}}": "任务名称示例",
		"{{DETAILS}}":   "详细信息示例",
	})

	// 在指定标签处插入表格
	if err := insertTableAt(doc, "{{biaoge}}"); err != nil {
		log.Fatalf("插入表格时出错: %v", err)
	}

	// 创建折线图并存储到缓存中
	lineChartBuffer, err := createLineChart()
	if err != nil {
		log.Fatalf("创建图表时出错: %v", err)
	}

	// 在指定标签处插入图表
	if err := insertImageAt(doc, lineChartBuffer, "{{tubiao}}"); err != nil {
		log.Fatalf("插入图表时出错: %v", err)
	}

	// 创建柱状图并存储到缓存中
	barChartBuffer, err := createBarChart()
	if err != nil {
		log.Fatalf("创建柱状图时出错: %v", err)
	}

	// 在指定标签处插入柱状图
	if err := insertImageAt(doc, barChartBuffer, "{{zhuzhuangtu}}"); err != nil {
		log.Fatalf("插入柱状图时出错: %v", err)
	}

	// 创建饼图并存储到缓存中
	pieChartBuffer, err := createPieChart()
	if err != nil {
		log.Fatalf("创建饼图时出错: %v", err)
	}

	// 在指定标签处插入饼图
	if err := insertImageAt(doc, pieChartBuffer, "{{bingtu}}"); err != nil {
		log.Fatalf("插入饼图时出错: %v", err)
	}

	// 删除{{a}}到{{b}}之间的段落
	if err := removeParagraphsBetweenTags(doc, "{{a}}", "{{b}}"); err != nil {
		log.Fatalf("删除段落时出错: %v", err)
	}

	// 删除指定标签
	if err := removeParagraphWithTag(doc, "{{shanchu}}"); err != nil {
		log.Fatalf("删除指定标签时出错: %v", err)
	}

	// 保存更新后的Word文档
	if err := doc.SaveToFile(updatedDocPath); err != nil {
		log.Fatalf("无法保存文档: %v", err)
	}
	fmt.Println("文档更新成功")
}

// 打开文档
func openDocument(path string) (*document.Document, error) {
	return document.Open(path) // 使用unioffice库打开指定路径的文档
}

// 填充模板中的变量
func fillTemplate(doc *document.Document, replacements map[string]string) {
	for _, para := range doc.Paragraphs() { // 遍历文档中的每个段落
		for _, run := range para.Runs() { // 遍历段落中的每个运行(文本片段)
			text := run.Text()
			for placeholder, replacement := range replacements { // 遍历需要替换的占位符
				if strings.Contains(text, placeholder) { // 如果文本包含占位符
					text = strings.ReplaceAll(text, placeholder, replacement) // 替换占位符
					run.Clear()                                               // 清除原有内容
					run.AddText(text)                                         // 添加替换后的文本
				}
			}
		}
	}
}

// 在指定标签处插入表格
func insertTableAt(doc *document.Document, tag string) error {
	paras := doc.Paragraphs() // 获取文档中的所有段落
	for _, para := range paras {
		if paraContainsTag(&para, tag) { // 如果段落包含指定标签
			// 创建并配置表格
			table := doc.InsertTableAfter(para)     // 在标签段落之后插入表格
			table.Properties().SetWidthPercent(100) // 设置表格宽度为100%
			borders := table.Properties().Borders()
			borders.SetAll(wml.ST_BorderSingle, color.Black, measurement.Dxa) // 设置所有边框为单线黑色

			for i := 0; i < 3; i++ { // 创建表格行和单元格
				row := table.AddRow()
				for j := 0; j < 3; j++ {
					cell := row.AddCell()
					cellPara := cell.AddParagraph()
					cellRun := cellPara.AddRun()
					cellRun.AddText(fmt.Sprintf("单元格 %d-%d", i+1, j+1))
				}
			}

			//分隔不同表格
			//doc.InsertParagraphAfter(para).AddRun().AddText("--------------")
			doc.InsertParagraphAfter(para).AddRun()

			// 创建并配置表格
			table = doc.InsertTableAfter(para)      // 在标签段落之后插入表格
			table.Properties().SetWidthPercent(100) // 设置表格宽度为100%
			borders = table.Properties().Borders()
			borders.SetAll(wml.ST_BorderSingle, color.Black, measurement.Dxa) // 设置所有边框为单线黑色

			for i := 0; i < 3; i++ { // 创建表格行和单元格
				row := table.AddRow()
				for j := 0; j < 3; j++ {
					cell := row.AddCell()
					cellPara := cell.AddParagraph()
					cellRun := cellPara.AddRun()
					cellRun.AddText(fmt.Sprintf("单元格 %d-%d", i+1, j+1))
				}
			}

			// 移除标签段落
			replaceParagraphWithTable(&para, tag) // 替换标签段落为表格
			// 删除段落
			doc.RemoveParagraph(para) // 从文档中删除标签段落
			return nil
		}
	}
	return fmt.Errorf("未找到标签 %s", tag) // 如果未找到标签段落,返回错误
}

// 在指定标签处插入图表
func insertImageAt(doc *document.Document, imageBuffer *bytes.Buffer, tag string) error {
	paras := doc.Paragraphs() // 获取文档中的所有段落
	for _, para := range paras {
		if paraContainsTag(&para, tag) { // 如果段落包含指定标签
			img, err := common.ImageFromBytes(imageBuffer.Bytes()) // 从内存缓冲区中加载图片
			if err != nil {
				return fmt.Errorf("无法从内存缓冲区中加载图片: %v", err)
			}

			// 创建图片引用
			iref, err := doc.AddImage(img) // 将图片添加到文档中
			if err != nil {
				return fmt.Errorf("无法将图片添加到文档: %v", err)
			}

			// 创建新的段落和运行以插入图表
			newPara := doc.InsertParagraphAfter(para) // 在标签段落之后插入新段落
			run := newPara.AddRun()

			// 插入图片到文档
			imgInl, err := run.AddDrawingInline(iref) // 在运行中添加图片
			if err != nil {
				return fmt.Errorf("插入图片时出错: %v", err)
			}
			imgInl.SetSize(6*measurement.Inch, 4*measurement.Inch) // 设置图片尺寸为6x4英寸

			// 移除标签段落
			replaceParagraphWithTable(&para, tag) // 替换标签段落为图表
			// 删除段落
			doc.RemoveParagraph(para) // 从文档中删除标签段落
			return nil
		}
	}
	return fmt.Errorf("未找到标签 %s", tag) // 如果未找到标签段落,返回错误
}

// 判断段落是否包含指定标签
func paraContainsTag(para *document.Paragraph, tag string) bool {
	for _, run := range para.Runs() { // 遍历段落中的每个运行
		if strings.Contains(run.Text(), tag) { // 如果运行文本包含标签
			return true
		}
	}
	return false
}

// 移除标签段落
func replaceParagraphWithTable(para *document.Paragraph, tag string) {
	// 找到标签的 Run
	for _, run := range para.Runs() {
		log.Printf("替换标签:tag =  %v", tag)
		if strings.Contains(run.Text(), tag) {
			para.InsertRunAfter(para.AddRun())
			run.Clear()         // 清除原有内容
			para.RemoveRun(run) // 移除运行
			break
		}
	}
}

// 删除两个标签之间的段落
func removeParagraphsBetweenTags(doc *document.Document, startTag, endTag string) error {
	paras := doc.Paragraphs()
	startIndex, endIndex := -1, -1

	// 找到包含startTag和endTag的段落索引
	for i, para := range paras {
		if paraContainsTag(&para, startTag) {
			startIndex = i
		}
		if paraContainsTag(&para, endTag) {
			endIndex = i
			break
		}
	}

	if startIndex == -1 {
		return fmt.Errorf("未找到标签 %s", startTag)
	}
	if endIndex == -1 {
		return fmt.Errorf("未找到标签 %s", endTag)
	}
	if startIndex >= endIndex {
		return fmt.Errorf("标签 %s 和 %s 之间的顺序不正确", startTag, endTag)
	}

	// 删除startTag和endTag之间的段落
	for i := startIndex; i <= endIndex; i++ {
		doc.RemoveParagraph(paras[i])
	}

	return nil
}

// 删除指定标签段落
func removeParagraphWithTag(doc *document.Document, tag string) error {
	paras := doc.Paragraphs()
	for _, para := range paras {
		if paraContainsTag(&para, tag) {
			doc.RemoveParagraph(para)
			return nil
		}
	}
	return fmt.Errorf("未找到标签 %s", tag)
}

// 创建折线图并将其存储到缓存中
func createLineChart() (*bytes.Buffer, error) {
	const (
		width  = 900 // 画布宽度
		height = 600 // 画布高度
	)

	// 数据
	data := []struct {
		label string
		value float64
	}{
		{"身份证", 15},
		{"电话号码", 20},
		{"地址信息", 25},
		{"银行卡号", 30},
		{"财务数据", 35},
		{"基础信息", 40},
		{"户籍信息", 45},
	}

	// 创建画布
	dc := gg.NewContext(width, height)
	dc.SetRGB(1, 1, 1) // 背景色为白色
	dc.Clear()

	barWidth := float64(width-200) / float64(len(data)) // 调整每个数据点之间的间隔
	maxValue := 50.0                                    // 纵坐标最大值

	// 加载自定义字体
	if err := dc.LoadFontFace("_examples/word-template/msyh.ttf", 12); err != nil {
		return nil, nil
	}

	// 绘制折线图
	dc.SetRGB(0, 0, 0) // 线条颜色为黑色
	for i, d := range data {
		x := 50 + float64(i)*(barWidth+20)                       // x轴起始位置
		y := height - 50 - (d.value / maxValue * (height - 100)) // y轴起始位置减去数据点的高度

		if i == 0 {
			dc.MoveTo(x, y)
		} else {
			dc.LineTo(x, y)
		}
	}
	dc.Stroke()

	// 绘制坐标轴
	dc.SetRGB(0, 0, 0)                              // 黑色
	dc.DrawLine(50, height-50, width-50, height-50) // X轴
	dc.DrawLine(50, height-50, 50, 50)              // Y轴
	dc.Stroke()

	// 添加横坐标标签
	for i, d := range data {
		dc.DrawStringAnchored(d.label, 50+float64(i)*(barWidth+20), height-30, 0.5, 1)
	}

	// 添加纵坐标标签
	for i := 0; i <= int(maxValue); i += 5 {
		y := height - 50 - (float64(i) / maxValue * (height - 100))
		dc.DrawStringAnchored(fmt.Sprintf("%d", int(i)), 30, y, 1, 0.5)
	}

	// 将图表保存到缓冲区
	var buf bytes.Buffer
	if err := dc.EncodePNG(&buf); err != nil {
		return nil, err
	}
	return &buf, nil
}

// 创建柱状图并将其存储到缓存中
func createBarChart() (*bytes.Buffer, error) {
	const (
		width  = 900 // 画布宽度
		height = 700 // 画布高度
	)

	// 数据
	data := []struct {
		label string
		value float64
	}{
		{"身份证", 15},
		{"电话号码", 20},
		{"地址信息", 25},
		{"银行卡号", 30},
		{"财务数据", 35},
		{"基础信息", 40},
		{"户籍信息", 45},
	}

	// 创建画布
	dc := gg.NewContext(width, height)
	dc.SetRGB(1, 1, 1) // 背景色为白色
	dc.Clear()

	barWidth := float64(width-200) / float64(len(data)) // 调整柱子的宽度
	barSpacing := 10.0                                  // 增加柱子之间的间隔
	maxValue := 50.0                                    // 纵坐标最大值

	// 定义颜色
	colors := []struct{ R, G, B float64 }{
		{0.8, 0.2, 0.2}, // 红色
		{0.2, 0.8, 0.2}, // 绿色
		{0.2, 0.2, 0.8}, // 蓝色
		{0.8, 0.8, 0.2}, // 黄色
		{0.8, 0.2, 0.8}, // 紫色
		{0.2, 0.8, 0.8}, // 青色
		{0.8, 0.8, 0.8}, // 灰色
	}

	// 加载自定义字体
	if err := dc.LoadFontFace("_examples/word-template/msyh.ttf", 12); err != nil {
		return nil, nil
	}

	// 绘制柱状图
	for i, d := range data {
		x := 50 + float64(i)*(barWidth+barSpacing)               // x轴起始位置
		y := height - 50 - (d.value / maxValue * (height - 100)) // y轴起始位置减去柱子的高度
		color := colors[i%len(colors)]                           // 循环使用颜色
		dc.SetRGB(color.R, color.G, color.B)
		dc.DrawRectangle(x, y, barWidth, (d.value / maxValue * (height - 100)))
		dc.Fill()
	}

	// 绘制坐标轴
	dc.SetRGB(0, 0, 0)                              // 黑色
	dc.DrawLine(50, height-50, width-50, height-50) // X轴
	dc.DrawLine(50, height-50, 50, 50)              // Y轴
	dc.Stroke()

	// 添加横坐标标签
	for i, d := range data {
		dc.DrawStringAnchored(d.label, 50+float64(i)*(barWidth+barSpacing)+barWidth/2, height-30, 0.5, 1)
	}

	// 添加纵坐标标签
	for i := 0; i <= int(maxValue); i += 5 {
		y := height - 50 - (float64(i) / maxValue * (height - 100))
		dc.DrawStringAnchored(fmt.Sprintf("%d", int(i)), 30, y, 1, 0.5)
	}

	// 将图表保存到缓冲区
	var buf bytes.Buffer
	if err := dc.EncodePNG(&buf); err != nil {
		return nil, err
	}
	return &buf, nil
}

// 创建饼图并将其存储到缓存中
func createPieChart() (*bytes.Buffer, error) {
	const (
		width  = 640
		height = 600
		radius = 200 // 半径
	)

	dc := gg.NewContext(width, height)
	dc.SetRGB(1, 1, 1) // 背景色为白色
	dc.Clear()

	data := []struct {
		label string
		value float64
	}{
		{"身份证", 15}, {"电话号码", 20}, {"地址信息", 25}, {"银行卡号", 30}, {"财务数据", 35}, {"基础信息", 40}, {"户籍信息", 45},
	}

	colors := []struct{ R, G, B float64 }{
		{0.9, 0.3, 0.3}, // 红色
		{0.3, 0.9, 0.3}, // 绿色
		{0.3, 0.3, 0.9}, // 蓝色
		{0.9, 0.9, 0.3}, // 黄色
		{0.9, 0.3, 0.9}, // 紫色
		{0.3, 0.9, 0.9}, // 青色
		{0.9, 0.6, 0.3}, // 橙色
	}

	// 设置字体(选择支持中文的字体)
	if err := dc.LoadFontFace("_examples/word-template/msyh.ttf", 12); err != nil {
		return nil, nil
	}

	var total float64
	for _, d := range data {
		total += d.value
	}

	startAngle := -math.Pi / 2 // 从顶部开始绘制
	for i, d := range data {
		percentage := d.value / total
		angle := percentage * 2 * math.Pi

		// 设置扇形颜色
		color := colors[i%len(colors)]
		dc.SetRGB(color.R, color.G, color.B)
		dc.DrawArc(width/2, height/2, radius, startAngle, startAngle+angle)
		dc.LineTo(width/2, height/2)
		dc.Fill()

		// 计算标签位置
		midAngle := startAngle + angle/2
		labelX := width/2 + (radius+20)*math.Cos(midAngle)
		labelY := height/2 + (radius+20)*math.Sin(midAngle)

		// 绘制标签和数据
		labelText := fmt.Sprintf("%s: %.0f", d.label, d.value)
		dc.SetRGB(0, 0, 0) // 文字颜色(黑色)
		dc.DrawStringAnchored(labelText, labelX, labelY, 0.5, 0.5)

		startAngle += angle
	}

	// 将图表保存到缓冲区
	var buf bytes.Buffer
	if err := dc.EncodePNG(&buf); err != nil {
		return nil, err
	}
	return &buf, nil
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495

The end …

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号