Files
wordZero/pkg/document/document.go

1105 lines
29 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package document 提供Word文档的核心操作功能
package document
import (
"archive/zip"
"bytes"
"encoding/xml"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/ZeroHawkeye/wordZero/pkg/style"
)
// Document 表示一个Word文档
type Document struct {
// 文档的主要内容
Body *Body
// 文档关系
relationships *Relationships
// 内容类型
contentTypes *ContentTypes
// 样式管理器
styleManager *style.StyleManager
// 临时存储文档部件
parts map[string][]byte
}
// Body 表示文档主体
type Body struct {
XMLName xml.Name `xml:"w:body"`
Paragraphs []Paragraph `xml:"w:p"`
}
// Paragraph 表示一个段落
type Paragraph struct {
XMLName xml.Name `xml:"w:p"`
Properties *ParagraphProperties `xml:"w:pPr,omitempty"`
Runs []Run `xml:"w:r"`
}
// ParagraphProperties 段落属性
type ParagraphProperties struct {
XMLName xml.Name `xml:"w:pPr"`
ParagraphStyle *ParagraphStyle `xml:"w:pStyle,omitempty"`
Spacing *Spacing `xml:"w:spacing,omitempty"`
Justification *Justification `xml:"w:jc,omitempty"`
Indentation *Indentation `xml:"w:ind,omitempty"`
}
// Spacing 间距设置
type Spacing struct {
XMLName xml.Name `xml:"w:spacing"`
Before string `xml:"w:before,attr,omitempty"`
After string `xml:"w:after,attr,omitempty"`
Line string `xml:"w:line,attr,omitempty"`
}
// Justification 对齐方式
type Justification struct {
XMLName xml.Name `xml:"w:jc"`
Val string `xml:"w:val,attr"`
}
// Run 表示一段文本
type Run struct {
XMLName xml.Name `xml:"w:r"`
Properties *RunProperties `xml:"w:rPr,omitempty"`
Text Text `xml:"w:t"`
}
// RunProperties 文本属性
type RunProperties struct {
XMLName xml.Name `xml:"w:rPr"`
Bold *Bold `xml:"w:b,omitempty"`
Italic *Italic `xml:"w:i,omitempty"`
FontSize *FontSize `xml:"w:sz,omitempty"`
Color *Color `xml:"w:color,omitempty"`
FontFamily *FontFamily `xml:"w:rFonts,omitempty"`
}
// Bold 粗体
type Bold struct {
XMLName xml.Name `xml:"w:b"`
}
// Italic 斜体
type Italic struct {
XMLName xml.Name `xml:"w:i"`
}
// FontSize 字体大小
type FontSize struct {
XMLName xml.Name `xml:"w:sz"`
Val string `xml:"w:val,attr"`
}
// Color 颜色
type Color struct {
XMLName xml.Name `xml:"w:color"`
Val string `xml:"w:val,attr"`
}
// Text 文本内容
type Text struct {
XMLName xml.Name `xml:"w:t"`
Space string `xml:"xml:space,attr,omitempty"`
Content string `xml:",chardata"`
}
// Relationships 文档关系
type Relationships struct {
XMLName xml.Name `xml:"Relationships"`
Xmlns string `xml:"xmlns,attr"`
Relationships []Relationship `xml:"Relationship"`
}
// Relationship 单个关系
type Relationship struct {
ID string `xml:"Id,attr"`
Type string `xml:"Type,attr"`
Target string `xml:"Target,attr"`
}
// ContentTypes 内容类型
type ContentTypes struct {
XMLName xml.Name `xml:"Types"`
Xmlns string `xml:"xmlns,attr"`
Defaults []Default `xml:"Default"`
Overrides []Override `xml:"Override"`
}
// Default 默认内容类型
type Default struct {
Extension string `xml:"Extension,attr"`
ContentType string `xml:"ContentType,attr"`
}
// Override 覆盖内容类型
type Override struct {
PartName string `xml:"PartName,attr"`
ContentType string `xml:"ContentType,attr"`
}
// FontFamily 字体族
type FontFamily struct {
XMLName xml.Name `xml:"w:rFonts"`
ASCII string `xml:"w:ascii,attr,omitempty"`
}
// TextFormat 文本格式配置
type TextFormat struct {
Bold bool // 是否粗体
Italic bool // 是否斜体
FontSize int // 字体大小(磅)
FontColor string // 字体颜色(十六进制,如 "FF0000" 表示红色)
FontName string // 字体名称
}
// AlignmentType 对齐类型
type AlignmentType string
const (
// AlignLeft 左对齐
AlignLeft AlignmentType = "left"
// AlignCenter 居中对齐
AlignCenter AlignmentType = "center"
// AlignRight 右对齐
AlignRight AlignmentType = "right"
// AlignJustify 两端对齐
AlignJustify AlignmentType = "both"
)
// SpacingConfig 间距配置
type SpacingConfig struct {
LineSpacing float64 // 行间距倍数如1.5表示1.5倍行距)
BeforePara int // 段前间距(磅)
AfterPara int // 段后间距(磅)
FirstLineIndent int // 首行缩进(磅)
}
// Indentation 缩进设置
type Indentation struct {
XMLName xml.Name `xml:"w:ind"`
FirstLine string `xml:"w:firstLine,attr,omitempty"`
Left string `xml:"w:left,attr,omitempty"`
Right string `xml:"w:right,attr,omitempty"`
}
// ParagraphStyle 段落样式引用
type ParagraphStyle struct {
XMLName xml.Name `xml:"w:pStyle"`
Val string `xml:"w:val,attr"`
}
// New 创建一个新的空文档
func New() *Document {
Debugf("创建新文档")
doc := &Document{
Body: &Body{
Paragraphs: make([]Paragraph, 0),
},
styleManager: style.NewStyleManager(),
parts: make(map[string][]byte),
}
doc.initializeStructure()
return doc
}
// Open 打开一个现有的Word文档。
//
// 参数 filename 是要打开的 .docx 文件路径。
// 该函数会解析整个文档结构,包括文本内容、格式和属性。
//
// 如果文件不存在、格式错误或解析失败,会返回相应的错误。
//
// 示例:
//
// doc, err := document.Open("existing.docx")
// if err != nil {
// log.Fatal(err)
// }
//
// // 打印所有段落内容
// for i, para := range doc.Body.Paragraphs {
// fmt.Printf("段落 %d: ", i+1)
// for _, run := range para.Runs {
// fmt.Print(run.Text.Content)
// }
// fmt.Println()
// }
func Open(filename string) (*Document, error) {
Infof("正在打开文档: %s", filename)
reader, err := zip.OpenReader(filename)
if err != nil {
Errorf("无法打开文件: %s", filename)
return nil, WrapErrorWithContext("open_file", err, filename)
}
defer reader.Close()
doc := &Document{
parts: make(map[string][]byte),
}
// 读取所有文件部件
for _, file := range reader.File {
rc, err := file.Open()
if err != nil {
Errorf("无法打开文件部件: %s", file.Name)
return nil, WrapErrorWithContext("open_part", err, file.Name)
}
data, err := io.ReadAll(rc)
rc.Close()
if err != nil {
Errorf("无法读取文件部件: %s", file.Name)
return nil, WrapErrorWithContext("read_part", err, file.Name)
}
doc.parts[file.Name] = data
Debugf("已读取文件部件: %s (%d 字节)", file.Name, len(data))
}
// 解析主文档
if err := doc.parseDocument(); err != nil {
Errorf("解析文档失败: %s", filename)
return nil, WrapErrorWithContext("parse_document", err, filename)
}
Infof("成功打开文档: %s", filename)
return doc, nil
}
// Save 将文档保存到指定的文件路径。
//
// 参数 filename 是保存文件的路径,包含文件名和扩展名。
// 如果目录不存在,会自动创建所需的目录结构。
//
// 保存过程包括序列化所有文档内容、压缩为ZIP格式
// 并写入到文件系统。
//
// 示例:
//
// doc := document.New()
// doc.AddParagraph("示例内容")
//
// // 保存到当前目录
// err := doc.Save("example.docx")
//
// // 保存到子目录(会自动创建目录)
// err = doc.Save("output/documents/example.docx")
//
// if err != nil {
// log.Fatal(err)
// }
func (d *Document) Save(filename string) error {
Infof("正在保存文档: %s", filename)
// 确保目录存在
dir := filepath.Dir(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
Errorf("无法创建目录: %s", dir)
return WrapErrorWithContext("create_dir", err, dir)
}
// 创建文件
file, err := os.Create(filename)
if err != nil {
Errorf("无法创建文件: %s", filename)
return WrapErrorWithContext("create_file", err, filename)
}
defer file.Close()
// 创建ZIP写入器
zipWriter := zip.NewWriter(file)
defer zipWriter.Close()
// 序列化主文档
if err := d.serializeDocument(); err != nil {
Errorf("序列化文档失败")
return WrapError("serialize_document", err)
}
// 序列化样式
if err := d.serializeStyles(); err != nil {
Errorf("序列化样式失败")
return WrapError("serialize_styles", err)
}
// 序列化内容类型
d.serializeContentTypes()
// 序列化关系
d.serializeRelationships()
// 序列化文档关系
d.serializeDocumentRelationships()
// 写入所有部件
for name, data := range d.parts {
writer, err := zipWriter.Create(name)
if err != nil {
Errorf("无法创建ZIP条目: %s", name)
return WrapErrorWithContext("create_zip_entry", err, name)
}
if _, err := writer.Write(data); err != nil {
Errorf("无法写入ZIP条目: %s", name)
return WrapErrorWithContext("write_zip_entry", err, name)
}
Debugf("已写入ZIP条目: %s (%d 字节)", name, len(data))
}
Infof("成功保存文档: %s", filename)
return nil
}
// AddParagraph 向文档添加一个普通段落。
//
// 参数 text 是段落的文本内容。段落会使用默认格式,
// 可以后续通过返回的 Paragraph 指针设置格式和属性。
//
// 返回新创建段落的指针,可用于进一步格式化。
//
// 示例:
//
// doc := document.New()
//
// // 添加普通段落
// para := doc.AddParagraph("这是一个段落")
//
// // 设置段落属性
// para.SetAlignment(document.AlignCenter)
// para.SetSpacing(&document.SpacingConfig{
// LineSpacing: 1.5,
// BeforePara: 12,
// })
func (d *Document) AddParagraph(text string) *Paragraph {
Debugf("添加段落: %s", text)
p := Paragraph{
Runs: []Run{
{
Text: Text{
Content: text,
Space: "preserve",
},
},
},
}
d.Body.Paragraphs = append(d.Body.Paragraphs, p)
return &d.Body.Paragraphs[len(d.Body.Paragraphs)-1]
}
// AddFormattedParagraph 向文档添加一个格式化段落。
//
// 参数 text 是段落的文本内容。
// 参数 format 指定文本格式,如果为 nil 则使用默认格式。
//
// 返回新创建段落的指针,可用于进一步设置段落属性。
//
// 示例:
//
// doc := document.New()
//
// // 创建格式配置
// titleFormat := &document.TextFormat{
// Bold: true,
// FontSize: 18,
// FontColor: "FF0000", // 红色
// FontName: "微软雅黑",
// }
//
// // 添加格式化标题
// title := doc.AddFormattedParagraph("文档标题", titleFormat)
// title.SetAlignment(document.AlignCenter)
func (d *Document) AddFormattedParagraph(text string, format *TextFormat) *Paragraph {
Debugf("添加格式化段落: %s", text)
// 创建运行属性
runProps := &RunProperties{}
if format != nil {
if format.Bold {
runProps.Bold = &Bold{}
}
if format.Italic {
runProps.Italic = &Italic{}
}
if format.FontSize > 0 {
// Word中字体大小是半磅为单位所以需要乘以2
runProps.FontSize = &FontSize{Val: strconv.Itoa(format.FontSize * 2)}
}
if format.FontColor != "" {
// 确保颜色格式正确(移除#前缀)
color := strings.TrimPrefix(format.FontColor, "#")
runProps.Color = &Color{Val: color}
}
if format.FontName != "" {
runProps.FontFamily = &FontFamily{ASCII: format.FontName}
}
}
p := Paragraph{
Runs: []Run{
{
Properties: runProps,
Text: Text{
Content: text,
Space: "preserve",
},
},
},
}
d.Body.Paragraphs = append(d.Body.Paragraphs, p)
return &d.Body.Paragraphs[len(d.Body.Paragraphs)-1]
}
// SetAlignment 设置段落的对齐方式。
//
// 参数 alignment 指定对齐类型,支持以下值:
// - AlignLeft: 左对齐(默认)
// - AlignCenter: 居中对齐
// - AlignRight: 右对齐
// - AlignJustify: 两端对齐
//
// 示例:
//
// para := doc.AddParagraph("居中标题")
// para.SetAlignment(document.AlignCenter)
//
// para2 := doc.AddParagraph("右对齐文本")
// para2.SetAlignment(document.AlignRight)
func (p *Paragraph) SetAlignment(alignment AlignmentType) {
if p.Properties == nil {
p.Properties = &ParagraphProperties{}
}
p.Properties.Justification = &Justification{Val: string(alignment)}
Debugf("设置段落对齐方式: %s", alignment)
}
// SetSpacing 设置段落的间距配置。
//
// 参数 config 包含各种间距设置,如果为 nil 则不进行任何设置。
// 配置选项包括:
// - LineSpacing: 行间距倍数(如 1.5 表示1.5倍行距)
// - BeforePara: 段前间距(磅)
// - AfterPara: 段后间距(磅)
// - FirstLineIndent: 首行缩进(磅)
//
// 注意:间距值会自动转换为 Word 内部使用的 TWIPs 单位1磅=20TWIPs
//
// 示例:
//
// para := doc.AddParagraph("带间距的段落")
//
// // 设置复杂间距
// para.SetSpacing(&document.SpacingConfig{
// LineSpacing: 1.5, // 1.5倍行距
// BeforePara: 12, // 段前12磅
// AfterPara: 6, // 段后6磅
// FirstLineIndent: 24, // 首行缩进24磅
// })
//
// // 只设置行间距
// para2 := doc.AddParagraph("双倍行距")
// para2.SetSpacing(&document.SpacingConfig{
// LineSpacing: 2.0,
// })
func (p *Paragraph) SetSpacing(config *SpacingConfig) {
if p.Properties == nil {
p.Properties = &ParagraphProperties{}
}
if config != nil {
spacing := &Spacing{}
if config.BeforePara > 0 {
// 转换为TWIPs (1/20磅)
spacing.Before = strconv.Itoa(config.BeforePara * 20)
}
if config.AfterPara > 0 {
// 转换为TWIPs (1/20磅)
spacing.After = strconv.Itoa(config.AfterPara * 20)
}
if config.LineSpacing > 0 {
// 行间距240表示单倍行距
spacing.Line = strconv.Itoa(int(config.LineSpacing * 240))
}
p.Properties.Spacing = spacing
if config.FirstLineIndent > 0 {
if p.Properties.Indentation == nil {
p.Properties.Indentation = &Indentation{}
}
// 转换为TWIPs (1/20磅)
p.Properties.Indentation.FirstLine = strconv.Itoa(config.FirstLineIndent * 20)
}
Debugf("设置段落间距: 段前=%d, 段后=%d, 行距=%.1f, 首行缩进=%d",
config.BeforePara, config.AfterPara, config.LineSpacing, config.FirstLineIndent)
}
}
// AddFormattedText 向段落添加格式化的文本内容。
//
// 此方法允许在一个段落中混合使用不同格式的文本。
// 新的文本会作为一个新的 Run 添加到段落中。
//
// 参数 text 是要添加的文本内容。
// 参数 format 指定文本格式,如果为 nil 则使用默认格式。
//
// 示例:
//
// para := doc.AddParagraph("这个段落包含")
//
// // 添加粗体红色文本
// para.AddFormattedText("粗体红色", &document.TextFormat{
// Bold: true,
// FontColor: "FF0000",
// })
//
// // 添加普通文本
// para.AddFormattedText("和普通文本", nil)
//
// // 添加斜体蓝色文本
// para.AddFormattedText("以及斜体蓝色", &document.TextFormat{
// Italic: true,
// FontColor: "0000FF",
// FontSize: 14,
// })
func (p *Paragraph) AddFormattedText(text string, format *TextFormat) {
// 创建运行属性
runProps := &RunProperties{}
if format != nil {
if format.Bold {
runProps.Bold = &Bold{}
}
if format.Italic {
runProps.Italic = &Italic{}
}
if format.FontSize > 0 {
runProps.FontSize = &FontSize{Val: strconv.Itoa(format.FontSize * 2)}
}
if format.FontColor != "" {
color := strings.TrimPrefix(format.FontColor, "#")
runProps.Color = &Color{Val: color}
}
if format.FontName != "" {
runProps.FontFamily = &FontFamily{ASCII: format.FontName}
}
}
run := Run{
Properties: runProps,
Text: Text{
Content: text,
Space: "preserve",
},
}
p.Runs = append(p.Runs, run)
Debugf("向段落添加格式化文本: %s", text)
}
// AddHeadingParagraph 向文档添加一个标题段落。
//
// 参数 text 是标题的文本内容。
// 参数 level 是标题级别1-9对应 Heading1 到 Heading9。
//
// 返回新创建段落的指针,可用于进一步设置段落属性。
// 此方法会自动设置正确的样式引用,确保标题能被 Word 导航窗格识别。
//
// 示例:
//
// doc := document.New()
//
// // 添加一级标题
// h1 := doc.AddHeadingParagraph("第一章:概述", 1)
//
// // 添加二级标题
// h2 := doc.AddHeadingParagraph("1.1 背景", 2)
//
// // 添加三级标题
// h3 := doc.AddHeadingParagraph("1.1.1 研究目标", 3)
func (d *Document) AddHeadingParagraph(text string, level int) *Paragraph {
if level < 1 || level > 9 {
Debugf("标题级别 %d 超出范围,使用默认级别 1", level)
level = 1
}
styleID := fmt.Sprintf("Heading%d", level)
Debugf("添加标题段落: %s (级别: %d, 样式: %s)", text, level, styleID)
// 获取样式管理器中的样式
headingStyle := d.styleManager.GetStyle(styleID)
if headingStyle == nil {
Debugf("警告:找不到样式 %s使用默认样式", styleID)
return d.AddParagraph(text)
}
// 创建运行属性,应用样式中的字符格式
runProps := &RunProperties{}
if headingStyle.RunPr != nil {
if headingStyle.RunPr.Bold != nil {
runProps.Bold = &Bold{}
}
if headingStyle.RunPr.Italic != nil {
runProps.Italic = &Italic{}
}
if headingStyle.RunPr.FontSize != nil {
runProps.FontSize = &FontSize{Val: headingStyle.RunPr.FontSize.Val}
}
if headingStyle.RunPr.Color != nil {
runProps.Color = &Color{Val: headingStyle.RunPr.Color.Val}
}
if headingStyle.RunPr.FontFamily != nil {
runProps.FontFamily = &FontFamily{ASCII: headingStyle.RunPr.FontFamily.ASCII}
}
}
// 创建段落属性,应用样式中的段落格式
paraProps := &ParagraphProperties{
ParagraphStyle: &ParagraphStyle{Val: styleID},
}
// 应用样式中的段落格式
if headingStyle.ParagraphPr != nil {
if headingStyle.ParagraphPr.Spacing != nil {
paraProps.Spacing = &Spacing{
Before: headingStyle.ParagraphPr.Spacing.Before,
After: headingStyle.ParagraphPr.Spacing.After,
Line: headingStyle.ParagraphPr.Spacing.Line,
}
}
if headingStyle.ParagraphPr.Justification != nil {
paraProps.Justification = &Justification{
Val: headingStyle.ParagraphPr.Justification.Val,
}
}
if headingStyle.ParagraphPr.Indentation != nil {
paraProps.Indentation = &Indentation{
FirstLine: headingStyle.ParagraphPr.Indentation.FirstLine,
Left: headingStyle.ParagraphPr.Indentation.Left,
Right: headingStyle.ParagraphPr.Indentation.Right,
}
}
}
// 创建段落
p := Paragraph{
Properties: paraProps,
Runs: []Run{
{
Properties: runProps,
Text: Text{
Content: text,
Space: "preserve",
},
},
},
}
d.Body.Paragraphs = append(d.Body.Paragraphs, p)
return &d.Body.Paragraphs[len(d.Body.Paragraphs)-1]
}
// SetStyle 设置段落的样式。
//
// 参数 styleID 是要应用的样式ID如 "Heading1"、"Normal" 等。
// 此方法会设置段落的样式引用,确保段落使用指定的样式。
//
// 示例:
//
// para := doc.AddParagraph("这是一个段落")
// para.SetStyle("Heading2") // 设置为二级标题样式
func (p *Paragraph) SetStyle(styleID string) {
if p.Properties == nil {
p.Properties = &ParagraphProperties{}
}
p.Properties.ParagraphStyle = &ParagraphStyle{Val: styleID}
Debugf("设置段落样式: %s", styleID)
}
// GetStyleManager 获取文档的样式管理器。
//
// 返回文档的样式管理器,可用于访问和管理样式。
//
// 示例:
//
// doc := document.New()
// styleManager := doc.GetStyleManager()
// headingStyle := styleManager.GetStyle("Heading1")
func (d *Document) GetStyleManager() *style.StyleManager {
return d.styleManager
}
// initializeStructure 初始化文档基础结构
func (d *Document) initializeStructure() {
// 初始化 content types
d.contentTypes = &ContentTypes{
Xmlns: "http://schemas.openxmlformats.org/package/2006/content-types",
Defaults: []Default{
{Extension: "rels", ContentType: "application/vnd.openxmlformats-package.relationships+xml"},
{Extension: "xml", ContentType: "application/xml"},
},
Overrides: []Override{
{PartName: "/word/document.xml", ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"},
{PartName: "/word/styles.xml", ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"},
},
}
// 初始化主关系
d.relationships = &Relationships{
Xmlns: "http://schemas.openxmlformats.org/package/2006/relationships",
Relationships: []Relationship{
{
ID: "rId1",
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
Target: "word/document.xml",
},
},
}
// 添加基础部件
d.serializeContentTypes()
d.serializeRelationships()
d.serializeDocumentRelationships()
}
// parseDocument 解析文档内容
func (d *Document) parseDocument() error {
Debugf("开始解析文档内容")
// 解析主文档
docData, ok := d.parts["word/document.xml"]
if !ok {
return WrapError("parse_document", ErrDocumentNotFound)
}
// 创建一个临时结构来解析完整的文档
var doc struct {
XMLName xml.Name `xml:"document"`
Body struct {
XMLName xml.Name `xml:"body"`
Paragraphs []struct {
XMLName xml.Name `xml:"p"`
Properties *struct {
XMLName xml.Name `xml:"pPr"`
Spacing *struct {
XMLName xml.Name `xml:"spacing"`
Before string `xml:"before,attr,omitempty"`
After string `xml:"after,attr,omitempty"`
Line string `xml:"line,attr,omitempty"`
} `xml:"spacing,omitempty"`
Justification *struct {
XMLName xml.Name `xml:"jc"`
Val string `xml:"val,attr"`
} `xml:"jc,omitempty"`
Indentation *struct {
XMLName xml.Name `xml:"ind"`
FirstLine string `xml:"firstLine,attr,omitempty"`
Left string `xml:"left,attr,omitempty"`
Right string `xml:"right,attr,omitempty"`
} `xml:"ind,omitempty"`
} `xml:"pPr,omitempty"`
Runs []struct {
XMLName xml.Name `xml:"r"`
Properties *struct {
XMLName xml.Name `xml:"rPr"`
Bold *struct {
XMLName xml.Name `xml:"b"`
} `xml:"b,omitempty"`
Italic *struct {
XMLName xml.Name `xml:"i"`
} `xml:"i,omitempty"`
FontSize *struct {
XMLName xml.Name `xml:"sz"`
Val string `xml:"val,attr"`
} `xml:"sz,omitempty"`
Color *struct {
XMLName xml.Name `xml:"color"`
Val string `xml:"val,attr"`
} `xml:"color,omitempty"`
FontFamily *struct {
XMLName xml.Name `xml:"rFonts"`
ASCII string `xml:"ascii,attr,omitempty"`
} `xml:"rFonts,omitempty"`
} `xml:"rPr,omitempty"`
Text struct {
XMLName xml.Name `xml:"t"`
Space string `xml:"space,attr,omitempty"`
Content string `xml:",chardata"`
} `xml:"t"`
} `xml:"r"`
} `xml:"p"`
} `xml:"body"`
}
if err := xml.Unmarshal(docData, &doc); err != nil {
Errorf("XML解析失败: %v", err)
return WrapError("unmarshal_xml", err)
}
// 转换为内部结构
d.Body = &Body{
Paragraphs: make([]Paragraph, len(doc.Body.Paragraphs)),
}
for i, p := range doc.Body.Paragraphs {
paragraph := Paragraph{
Runs: make([]Run, len(p.Runs)),
}
// 复制段落属性
if p.Properties != nil {
paragraph.Properties = &ParagraphProperties{}
if p.Properties.Spacing != nil {
paragraph.Properties.Spacing = &Spacing{
Before: p.Properties.Spacing.Before,
After: p.Properties.Spacing.After,
Line: p.Properties.Spacing.Line,
}
}
if p.Properties.Justification != nil {
paragraph.Properties.Justification = &Justification{
Val: p.Properties.Justification.Val,
}
}
if p.Properties.Indentation != nil {
paragraph.Properties.Indentation = &Indentation{
FirstLine: p.Properties.Indentation.FirstLine,
Left: p.Properties.Indentation.Left,
Right: p.Properties.Indentation.Right,
}
}
}
// 复制运行和运行属性
for j, r := range p.Runs {
run := Run{
Text: Text{
Space: r.Text.Space,
Content: r.Text.Content,
},
}
// 复制运行属性
if r.Properties != nil {
run.Properties = &RunProperties{}
if r.Properties.Bold != nil {
run.Properties.Bold = &Bold{}
}
if r.Properties.Italic != nil {
run.Properties.Italic = &Italic{}
}
if r.Properties.FontSize != nil {
run.Properties.FontSize = &FontSize{
Val: r.Properties.FontSize.Val,
}
}
if r.Properties.Color != nil {
run.Properties.Color = &Color{
Val: r.Properties.Color.Val,
}
}
if r.Properties.FontFamily != nil {
run.Properties.FontFamily = &FontFamily{
ASCII: r.Properties.FontFamily.ASCII,
}
}
}
paragraph.Runs[j] = run
}
d.Body.Paragraphs[i] = paragraph
}
Infof("解析完成,共 %d 个段落", len(d.Body.Paragraphs))
return nil
}
// serializeDocument 序列化文档内容
func (d *Document) serializeDocument() error {
Debugf("开始序列化文档")
// 创建文档结构
type documentXML struct {
XMLName xml.Name `xml:"w:document"`
Xmlns string `xml:"xmlns:w,attr"`
Body Body `xml:"w:body"`
}
doc := documentXML{
Xmlns: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
Body: *d.Body,
}
// 序列化为XML
data, err := xml.MarshalIndent(doc, "", " ")
if err != nil {
Errorf("XML序列化失败: %v", err)
return WrapError("marshal_xml", err)
}
// 添加XML声明
d.parts["word/document.xml"] = append([]byte(xml.Header), data...)
Debugf("文档序列化完成")
return nil
}
// serializeContentTypes 序列化内容类型
func (d *Document) serializeContentTypes() {
data, _ := xml.MarshalIndent(d.contentTypes, "", " ")
d.parts["[Content_Types].xml"] = append([]byte(xml.Header), data...)
}
// serializeRelationships 序列化关系
func (d *Document) serializeRelationships() {
data, _ := xml.MarshalIndent(d.relationships, "", " ")
d.parts["_rels/.rels"] = append([]byte(xml.Header), data...)
}
// serializeDocumentRelationships 序列化文档关系
func (d *Document) serializeDocumentRelationships() {
// 创建文档关系
docRels := &Relationships{
Xmlns: "http://schemas.openxmlformats.org/package/2006/relationships",
Relationships: []Relationship{
{
ID: "rId1",
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
Target: "styles.xml",
},
},
}
data, _ := xml.MarshalIndent(docRels, "", " ")
d.parts["word/_rels/document.xml.rels"] = append([]byte(xml.Header), data...)
}
// serializeStyles 序列化样式
func (d *Document) serializeStyles() error {
Debugf("开始序列化样式")
// 创建样式结构,包含完整的命名空间
type stylesXML struct {
XMLName xml.Name `xml:"w:styles"`
XmlnsW string `xml:"xmlns:w,attr"`
XmlnsMC string `xml:"xmlns:mc,attr"`
XmlnsO string `xml:"xmlns:o,attr"`
XmlnsR string `xml:"xmlns:r,attr"`
XmlnsM string `xml:"xmlns:m,attr"`
XmlnsV string `xml:"xmlns:v,attr"`
XmlnsW14 string `xml:"xmlns:w14,attr"`
XmlnsW10 string `xml:"xmlns:w10,attr"`
XmlnsSL string `xml:"xmlns:sl,attr"`
XmlnsWPS string `xml:"xmlns:wpsCustomData,attr"`
MCIgnorable string `xml:"mc:Ignorable,attr"`
Styles []*style.Style `xml:"w:style"`
}
doc := stylesXML{
XmlnsW: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
XmlnsMC: "http://schemas.openxmlformats.org/markup-compatibility/2006",
XmlnsO: "urn:schemas-microsoft-com:office:office",
XmlnsR: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
XmlnsM: "http://schemas.openxmlformats.org/officeDocument/2006/math",
XmlnsV: "urn:schemas-microsoft-com:vml",
XmlnsW14: "http://schemas.microsoft.com/office/word/2010/wordml",
XmlnsW10: "urn:schemas-microsoft-com:office:word",
XmlnsSL: "http://schemas.openxmlformats.org/schemaLibrary/2006/main",
XmlnsWPS: "http://www.wps.cn/officeDocument/2013/wpsCustomData",
MCIgnorable: "w14",
Styles: d.styleManager.GetAllStyles(),
}
// 序列化为XML
data, err := xml.MarshalIndent(doc, "", " ")
if err != nil {
Errorf("XML序列化失败: %v", err)
return WrapError("marshal_xml", err)
}
// 添加XML声明
d.parts["word/styles.xml"] = append([]byte(xml.Header), data...)
Debugf("样式序列化完成")
return nil
}
// ToBytes 将文档转换为字节数组
func (d *Document) ToBytes() ([]byte, error) {
buf := new(bytes.Buffer)
zipWriter := zip.NewWriter(buf)
// 序列化文档
if err := d.serializeDocument(); err != nil {
return nil, err
}
// 序列化样式
if err := d.serializeStyles(); err != nil {
return nil, err
}
// 序列化内容类型
d.serializeContentTypes()
// 序列化关系
d.serializeRelationships()
// 序列化文档关系
d.serializeDocumentRelationships()
// 写入所有部件
for name, data := range d.parts {
writer, err := zipWriter.Create(name)
if err != nil {
return nil, err
}
if _, err := writer.Write(data); err != nil {
return nil, err
}
}
if err := zipWriter.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}