🎉 Initial commit

This commit is contained in:
2025-04-16 16:12:33 +08:00
commit af9b26fa6c
28 changed files with 6318 additions and 0 deletions

106
.gitignore vendored Normal file
View File

@@ -0,0 +1,106 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

73
LICENSE Normal file
View File

@@ -0,0 +1,73 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright 2025 landaiqing
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

2
README.md Normal file
View File

@@ -0,0 +1,2 @@
# go-dockit

184
document/body.go Normal file
View File

@@ -0,0 +1,184 @@
package document
import "fmt"
// Body 表示Word文档的主体部分
type Body struct {
Content []interface{} // 可以是段落、表格等元素
SectionProperties *SectionProperties
}
// SectionProperties 表示节属性
type SectionProperties struct {
PageSize *PageSize
PageMargin *PageMargin
Columns *Columns
DocGrid *DocGrid
HeaderReference []*HeaderFooterReference
FooterReference []*HeaderFooterReference
}
// PageSize 表示页面大小
type PageSize struct {
Width int // 页面宽度单位为twip
Height int // 页面高度单位为twip
Orientation string // 页面方向portrait, landscape
}
// PageMargin 表示页面边距
type PageMargin struct {
Top int // 上边距单位为twip
Right int // 右边距单位为twip
Bottom int // 下边距单位为twip
Left int // 左边距单位为twip
Header int // 页眉边距单位为twip
Footer int // 页脚边距单位为twip
Gutter int // 装订线单位为twip
}
// Columns 表示分栏
type Columns struct {
Num int // 栏数
Space int // 栏间距单位为twip
}
// DocGrid 表示文档网格
type DocGrid struct {
LinePitch int // 行距单位为twip
}
// HeaderFooterReference 表示页眉页脚引用
type HeaderFooterReference struct {
Type string // 类型default, first, even
ID string // 引用ID
}
// NewBody 创建一个新的文档主体
func NewBody() *Body {
return &Body{
Content: make([]interface{}, 0),
SectionProperties: &SectionProperties{
PageSize: &PageSize{
Width: 12240, // 8.5英寸 = 12240 twip
Height: 15840, // 11英寸 = 15840 twip
Orientation: "portrait",
},
PageMargin: &PageMargin{
Top: 1440, // 1英寸 = 1440 twip
Right: 1440,
Bottom: 1440,
Left: 1440,
Header: 720,
Footer: 720,
Gutter: 0,
},
Columns: &Columns{
Num: 1,
Space: 720,
},
DocGrid: &DocGrid{
LinePitch: 360,
},
HeaderReference: make([]*HeaderFooterReference, 0),
FooterReference: make([]*HeaderFooterReference, 0),
},
}
}
// AddParagraph 向文档主体添加一个段落并返回它
func (b *Body) AddParagraph() *Paragraph {
p := NewParagraph()
b.Content = append(b.Content, p)
return p
}
// AddTable 向文档主体添加一个表格并返回它
func (b *Body) AddTable(rows, cols int) *Table {
t := NewTable(rows, cols)
b.Content = append(b.Content, t)
return t
}
// AddPageBreak 向文档主体添加一个分页符
func (b *Body) AddPageBreak() *Paragraph {
p := NewParagraph()
p.AddRun().AddBreak(BreakTypePage)
b.Content = append(b.Content, p)
return p
}
// AddSectionBreak 向文档主体添加一个分节符
func (b *Body) AddSectionBreak() *Paragraph {
p := NewParagraph()
p.AddRun().AddBreak(BreakTypeSection)
b.Content = append(b.Content, p)
return p
}
// ToXML 将Body转换为XML
func (b *Body) ToXML() string {
xml := "<w:body>"
// 添加所有内容元素的XML
for _, content := range b.Content {
switch v := content.(type) {
case *Paragraph:
xml += v.ToXML()
case *Table:
xml += v.ToXML()
}
}
// 添加节属性
xml += "<w:sectPr>"
// 页面大小
if b.SectionProperties.PageSize != nil {
xml += fmt.Sprintf("<w:pgSz w:w=\"%d\" w:h=\"%d\" w:orient=\"%s\" />",
b.SectionProperties.PageSize.Width,
b.SectionProperties.PageSize.Height,
b.SectionProperties.PageSize.Orientation)
}
// 页面边距
if b.SectionProperties.PageMargin != nil {
xml += fmt.Sprintf("<w:pgMar w:top=\"%d\" w:right=\"%d\" w:bottom=\"%d\" w:left=\"%d\" w:header=\"%d\" w:footer=\"%d\" w:gutter=\"%d\" />",
b.SectionProperties.PageMargin.Top,
b.SectionProperties.PageMargin.Right,
b.SectionProperties.PageMargin.Bottom,
b.SectionProperties.PageMargin.Left,
b.SectionProperties.PageMargin.Header,
b.SectionProperties.PageMargin.Footer,
b.SectionProperties.PageMargin.Gutter)
}
// 分栏
if b.SectionProperties.Columns != nil {
xml += fmt.Sprintf("<w:cols w:num=\"%d\" w:space=\"%d\" />",
b.SectionProperties.Columns.Num,
b.SectionProperties.Columns.Space)
}
// 文档网格
if b.SectionProperties.DocGrid != nil {
xml += fmt.Sprintf("<w:docGrid w:linePitch=\"%d\" />",
b.SectionProperties.DocGrid.LinePitch)
}
// 页眉引用
for _, headerRef := range b.SectionProperties.HeaderReference {
xml += fmt.Sprintf("<w:headerReference w:type=\"%s\" r:id=\"%s\" />",
headerRef.Type, headerRef.ID)
}
// 页脚引用
for _, footerRef := range b.SectionProperties.FooterReference {
xml += fmt.Sprintf("<w:footerReference w:type=\"%s\" r:id=\"%s\" />",
footerRef.Type, footerRef.ID)
}
xml += "</w:sectPr>"
xml += "</w:body>"
return xml
}

112
document/content_types.go Normal file
View File

@@ -0,0 +1,112 @@
package document
import (
"fmt"
)
// ContentTypes 表示Word文档中的内容类型集合
type ContentTypes struct {
Defaults []*Default
Overrides []*Override
}
// Default 表示默认的内容类型
type Default struct {
Extension string
ContentType string
}
// Override 表示覆盖的内容类型
type Override struct {
PartName string
ContentType string
}
// NewContentTypes 创建一个新的内容类型集合
func NewContentTypes() *ContentTypes {
ct := &ContentTypes{
Defaults: make([]*Default, 0),
Overrides: make([]*Override, 0),
}
// 添加默认的内容类型
ct.AddDefault("xml", "application/xml")
ct.AddDefault("rels", "application/vnd.openxmlformats-package.relationships+xml")
ct.AddDefault("png", "image/png")
ct.AddDefault("jpeg", "image/jpeg")
ct.AddDefault("jpg", "image/jpeg")
ct.AddDefault("gif", "image/gif")
ct.AddDefault("bmp", "image/bmp")
ct.AddDefault("tiff", "image/tiff")
ct.AddDefault("tif", "image/tiff")
ct.AddDefault("wmf", "image/x-wmf")
ct.AddDefault("emf", "image/x-emf")
// 添加覆盖的内容类型
ct.AddOverride("/document/document.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml")
ct.AddOverride("/document/styles.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml")
ct.AddOverride("/document/numbering.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml")
ct.AddOverride("/document/settings.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml")
ct.AddOverride("/document/theme/theme1.xml", "application/vnd.openxmlformats-officedocument.theme+xml")
ct.AddOverride("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml")
ct.AddOverride("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml")
return ct
}
// AddDefault 添加一个默认的内容类型
func (c *ContentTypes) AddDefault(extension, contentType string) *Default {
def := &Default{
Extension: extension,
ContentType: contentType,
}
c.Defaults = append(c.Defaults, def)
return def
}
// AddOverride 添加一个覆盖的内容类型
func (c *ContentTypes) AddOverride(partName, contentType string) *Override {
override := &Override{
PartName: partName,
ContentType: contentType,
}
c.Overrides = append(c.Overrides, override)
return override
}
// AddHeaderOverride 添加一个页眉的内容类型
func (c *ContentTypes) AddHeaderOverride(index int) *Override {
return c.AddOverride(
"/document/header"+fmt.Sprintf("%d", index)+".xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
)
}
// AddFooterOverride 添加一个页脚的内容类型
func (c *ContentTypes) AddFooterOverride(index int) *Override {
return c.AddOverride(
"/document/footer"+fmt.Sprintf("%d", index)+".xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
)
}
// ToXML 将内容类型集合转换为XML
func (c *ContentTypes) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">"
// 添加所有默认的内容类型
for _, def := range c.Defaults {
xml += "<Default Extension=\"" + def.Extension + "\""
xml += " ContentType=\"" + def.ContentType + "\" />"
}
// 添加所有覆盖的内容类型
for _, override := range c.Overrides {
xml += "<Override PartName=\"" + override.PartName + "\""
xml += " ContentType=\"" + override.ContentType + "\" />"
}
xml += "</Types>"
return xml
}

685
document/document.go Normal file
View File

@@ -0,0 +1,685 @@
package document
import (
"archive/zip"
"fmt"
"os"
"path/filepath"
"time"
)
// Document 表示一个Word文档
type Document struct {
Body *Body
Properties *DocumentProperties
Relationships *Relationships
Styles *Styles
Numbering *Numbering
Footers []*Footer
Headers []*Header
Theme *Theme
Settings *Settings
ContentTypes *ContentTypes
Rels *DocumentRels
}
// DocumentProperties 包含文档的元数据
type DocumentProperties struct {
Title string
Subject string
Creator string
Keywords string
Description string
LastModifiedBy string
Revision int
Created time.Time
Modified time.Time
}
// NewDocument 创建一个新的Word文档
func NewDocument() *Document {
return &Document{
Body: NewBody(),
Properties: &DocumentProperties{
Created: time.Now(),
Modified: time.Now(),
Revision: 1,
},
Relationships: NewRelationships(),
Styles: NewStyles(),
Numbering: NewNumbering(),
Footers: make([]*Footer, 0),
Headers: make([]*Header, 0),
Theme: NewTheme(),
Settings: NewSettings(),
ContentTypes: NewContentTypes(),
Rels: NewDocumentRels(),
}
}
// Save 将文档保存到指定路径
func (d *Document) Save(path string) error {
// 创建一个新的zip文件
zipFile, err := os.Create(path)
if err != nil {
return err
}
defer zipFile.Close()
// 创建一个zip writer
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// 添加[Content_Types].xml
if err := d.addContentTypes(zipWriter); err != nil {
return err
}
// 添加_rels/.rels
if err := d.addRels(zipWriter); err != nil {
return err
}
// 添加docProps/app.xml和docProps/core.xml
if err := d.addDocProps(zipWriter); err != nil {
return err
}
// 添加word/document.xml
if err := d.addDocument(zipWriter); err != nil {
return err
}
// 添加word/styles.xml
if err := d.addStyles(zipWriter); err != nil {
return err
}
// 添加word/numbering.xml
if err := d.addNumbering(zipWriter); err != nil {
return err
}
// 添加word/_rels/document.xml.rels
if err := d.addDocumentRels(zipWriter); err != nil {
return err
}
// 添加word/theme/theme1.xml
if err := d.addTheme(zipWriter); err != nil {
return err
}
// 添加word/settings.xml
if err := d.addSettings(zipWriter); err != nil {
return err
}
// 添加页眉
for i, header := range d.Headers {
if err := d.addHeader(zipWriter, header, i+1); err != nil {
return err
}
}
// 添加页脚
for i, footer := range d.Footers {
if err := d.addFooter(zipWriter, footer, i+1); err != nil {
return err
}
}
// 添加图片
for _, rel := range d.Rels.Relationships.GetRelationshipsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/image") {
if err := d.addImage(zipWriter, rel); err != nil {
return err
}
}
return nil
}
// 以下是内部方法用于将各个部分添加到zip文件中
func (d *Document) addContentTypes(zipWriter *zip.Writer) error {
// 添加[Content_Types].xml
w, err := zipWriter.Create("[Content_Types].xml")
if err != nil {
return err
}
_, err = w.Write([]byte(d.ContentTypes.ToXML()))
return err
}
func (d *Document) addRels(zipWriter *zip.Writer) error {
// 添加_rels/.rels
w, err := zipWriter.Create("_rels/.rels")
if err != nil {
return err
}
// 创建基本关系
rels := NewRelationships()
rels.AddRelationship("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "document/document.xml")
rels.AddRelationship("rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml")
rels.AddRelationship("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml")
_, err = w.Write([]byte(rels.ToXML()))
return err
}
func (d *Document) addDocProps(zipWriter *zip.Writer) error {
// 添加docProps/app.xml
w1, err := zipWriter.Create("docProps/app.xml")
if err != nil {
return err
}
// 创建app.xml内容
appXML := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
appXML += "<Properties xmlns=\"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties\" xmlns:vt=\"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes\">\n"
appXML += "<Application>go-flowdoc</Application>\n"
appXML += "<AppVersion>1.0.0</AppVersion>\n"
appXML += "</Properties>\n"
_, err = w1.Write([]byte(appXML))
if err != nil {
return err
}
// 添加docProps/core.xml
w2, err := zipWriter.Create("docProps/core.xml")
if err != nil {
return err
}
// 创建core.xml内容
coreXML := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
coreXML += "<cp:coreProperties xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" "
coreXML += "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
coreXML += "xmlns:dcterms=\"http://purl.org/dc/terms/\" "
coreXML += "xmlns:dcmitype=\"http://purl.org/dc/dcmitype/\" "
coreXML += "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
if d.Properties.Title != "" {
coreXML += "<dc:title>" + d.Properties.Title + "</dc:title>\n"
}
if d.Properties.Subject != "" {
coreXML += "<dc:subject>" + d.Properties.Subject + "</dc:subject>\n"
}
if d.Properties.Creator != "" {
coreXML += "<dc:creator>" + d.Properties.Creator + "</dc:creator>\n"
}
if d.Properties.Keywords != "" {
coreXML += "<cp:keywords>" + d.Properties.Keywords + "</cp:keywords>\n"
}
if d.Properties.Description != "" {
coreXML += "<dc:description>" + d.Properties.Description + "</dc:description>\n"
}
if d.Properties.LastModifiedBy != "" {
coreXML += "<cp:lastModifiedBy>" + d.Properties.LastModifiedBy + "</cp:lastModifiedBy>\n"
}
if d.Properties.Revision > 0 {
coreXML += "<cp:revision>" + fmt.Sprintf("%d", d.Properties.Revision) + "</cp:revision>\n"
}
// 格式化时间
createdTime := d.Properties.Created.Format("2006-01-02T15:04:05Z")
modifiedTime := d.Properties.Modified.Format("2006-01-02T15:04:05Z")
coreXML += "<dcterms:created xsi:type=\"dcterms:W3CDTF\">" + createdTime + "</dcterms:created>\n"
coreXML += "<dcterms:modified xsi:type=\"dcterms:W3CDTF\">" + modifiedTime + "</dcterms:modified>\n"
coreXML += "</cp:coreProperties>\n"
_, err = w2.Write([]byte(coreXML))
return err
}
func (d *Document) addDocument(zipWriter *zip.Writer) error {
// 添加word/document.xml
w, err := zipWriter.Create("document/document.xml")
if err != nil {
return err
}
// 创建document.xml内容
docXML := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
docXML += "<w:document xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" "
docXML += "xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" "
docXML += "xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\">\n"
// 添加文档主体
docXML += d.Body.ToXML()
docXML += "</w:document>"
_, err = w.Write([]byte(docXML))
return err
}
func (d *Document) addStyles(zipWriter *zip.Writer) error {
// 添加word/styles.xml
w, err := zipWriter.Create("document/styles.xml")
if err != nil {
return err
}
_, err = w.Write([]byte(d.Styles.ToXML()))
return err
}
func (d *Document) addNumbering(zipWriter *zip.Writer) error {
// 添加word/numbering.xml
w, err := zipWriter.Create("document/numbering.xml")
if err != nil {
return err
}
_, err = w.Write([]byte(d.Numbering.ToXML()))
return err
}
func (d *Document) addDocumentRels(zipWriter *zip.Writer) error {
// 添加word/_rels/document.xml.rels
w, err := zipWriter.Create("document/_rels/document.xml.rels")
if err != nil {
return err
}
_, err = w.Write([]byte(d.Rels.ToXML()))
return err
}
func (d *Document) addTheme(zipWriter *zip.Writer) error {
// 添加word/theme/theme1.xml
w, err := zipWriter.Create("document/theme/theme1.xml")
if err != nil {
return err
}
_, err = w.Write([]byte(d.Theme.ToXML()))
return err
}
func (d *Document) addSettings(zipWriter *zip.Writer) error {
// 添加word/settings.xml
w, err := zipWriter.Create("document/settings.xml")
if err != nil {
return err
}
_, err = w.Write([]byte(d.Settings.ToXML()))
return err
}
func (d *Document) addHeader(zipWriter *zip.Writer, header *Header, index int) error {
// 添加word/header{index}.xml
headerPath := fmt.Sprintf("document/header%d.xml", index)
w, err := zipWriter.Create(headerPath)
if err != nil {
return err
}
_, err = w.Write([]byte(header.ToXML()))
return err
}
func (d *Document) addFooter(zipWriter *zip.Writer, footer *Footer, index int) error {
// 添加word/footer{index}.xml
footerPath := fmt.Sprintf("document/footer%d.xml", index)
w, err := zipWriter.Create(footerPath)
if err != nil {
return err
}
_, err = w.Write([]byte(footer.ToXML()))
return err
}
func (d *Document) addImage(zipWriter *zip.Writer, rel *Relationship) error {
// 从关系中提取图片ID和路径
imageID := rel.ID
imagePath := rel.Target
// 查找对应的Drawing对象
var imageData []byte
// 在文档主体中查找
for _, para := range d.Body.Content {
if p, ok := para.(*Paragraph); ok {
for _, run := range p.Runs {
if run.Drawing != nil && run.Drawing.ID == imageID {
imageData = run.Drawing.ImageData
break
}
}
}
}
// 在页眉中查找
if len(imageData) == 0 {
for _, header := range d.Headers {
for _, content := range header.Content {
if p, ok := content.(*Paragraph); ok {
for _, run := range p.Runs {
if run.Drawing != nil && run.Drawing.ID == imageID {
imageData = run.Drawing.ImageData
break
}
}
}
}
}
}
// 在页脚中查找
if len(imageData) == 0 {
for _, footer := range d.Footers {
for _, content := range footer.Content {
if p, ok := content.(*Paragraph); ok {
for _, run := range p.Runs {
if run.Drawing != nil && run.Drawing.ID == imageID {
imageData = run.Drawing.ImageData
break
}
}
}
}
}
}
if len(imageData) == 0 {
return fmt.Errorf("未找到图片数据: %s", imageID)
}
// 添加图片文件
w, err := zipWriter.Create("document/" + imagePath)
if err != nil {
return err
}
_, err = w.Write(imageData)
return err
}
// AddParagraph 向文档添加一个段落
func (d *Document) AddParagraph() *Paragraph {
return d.Body.AddParagraph()
}
// AddTable 向文档添加一个表格
func (d *Document) AddTable(rows, cols int) *Table {
return d.Body.AddTable(rows, cols)
}
// AddPageBreak 向文档添加一个分页符
func (d *Document) AddPageBreak() *Paragraph {
return d.Body.AddPageBreak()
}
// AddSectionBreak 向文档添加一个分节符
func (d *Document) AddSectionBreak() *Paragraph {
return d.Body.AddSectionBreak()
}
// AddHeader 向文档添加一个页眉并返回它
func (d *Document) AddHeader() *Header {
header := NewHeader()
d.Headers = append(d.Headers, header)
// 添加页眉关系
headerID := fmt.Sprintf("rId%d", len(d.Rels.Relationships.Relationships)+1)
headerPath := fmt.Sprintf("header%d.xml", len(d.Headers))
d.Rels.AddHeader(headerID, headerPath)
// 添加页眉内容类型
d.ContentTypes.AddHeaderOverride(len(d.Headers))
return header
}
// AddFooter 向文档添加一个页脚并返回它
func (d *Document) AddFooter() *Footer {
footer := NewFooter()
d.Footers = append(d.Footers, footer)
// 添加页脚关系
footerID := fmt.Sprintf("rId%d", len(d.Rels.Relationships.Relationships)+1)
footerPath := fmt.Sprintf("footer%d.xml", len(d.Footers))
d.Rels.AddFooter(footerID, footerPath)
// 添加页脚内容类型
d.ContentTypes.AddFooterOverride(len(d.Footers))
return footer
}
// AddImage 向文档添加一个图片
func (d *Document) AddImage(path string, width, height int) (*Run, error) {
// 创建一个新段落和运行
para := d.AddParagraph()
run := para.AddRun()
// 创建图片
drawing := NewDrawing()
err := drawing.SetImagePath(path)
if err != nil {
return nil, err
}
// 设置图片大小
drawing.SetSize(width, height)
// 添加图片关系
imageID := fmt.Sprintf("rId%d", len(d.Rels.Relationships.Relationships)+1)
imageName := filepath.Base(path)
imagePath := fmt.Sprintf("media/%s", imageName)
d.Rels.AddImage(imageID, imagePath)
// 设置图片ID
drawing.ID = imageID
// 添加图片到运行
run.AddDrawing(drawing)
return run, nil
}
// AddImageBytes 通过字节数据添加图片
func (d *Document) AddImageBytes(data []byte, format, name string, width, height int) (*Run, error) {
// 创建一个新段落和运行
para := d.AddParagraph()
run := para.AddRun()
// 创建图片
drawing := NewDrawing()
drawing.SetImageData(data)
drawing.SetName(name)
// 设置图片大小
drawing.SetSize(width, height)
// 添加图片关系
imageID := fmt.Sprintf("rId%d", len(d.Rels.Relationships.Relationships)+1)
imagePath := fmt.Sprintf("media/%s.%s", name, format)
d.Rels.AddImage(imageID, imagePath)
// 设置图片ID
drawing.ID = imageID
// 添加图片到运行
run.AddDrawing(drawing)
return run, nil
}
// SetTitle 设置文档标题
func (d *Document) SetTitle(title string) *Document {
d.Properties.Title = title
return d
}
// SetSubject 设置文档主题
func (d *Document) SetSubject(subject string) *Document {
d.Properties.Subject = subject
return d
}
// SetCreator 设置文档创建者
func (d *Document) SetCreator(creator string) *Document {
d.Properties.Creator = creator
d.Properties.LastModifiedBy = creator
return d
}
// SetKeywords 设置文档关键词
func (d *Document) SetKeywords(keywords string) *Document {
d.Properties.Keywords = keywords
return d
}
// SetDescription 设置文档描述
func (d *Document) SetDescription(description string) *Document {
d.Properties.Description = description
return d
}
// SetLastModifiedBy 设置文档最后修改者
func (d *Document) SetLastModifiedBy(lastModifiedBy string) *Document {
d.Properties.LastModifiedBy = lastModifiedBy
return d
}
// SetRevision 设置文档修订版本
func (d *Document) SetRevision(revision int) *Document {
d.Properties.Revision = revision
return d
}
// SetCreated 设置文档创建时间
func (d *Document) SetCreated(created time.Time) *Document {
d.Properties.Created = created
return d
}
// SetModified 设置文档修改时间
func (d *Document) SetModified(modified time.Time) *Document {
d.Properties.Modified = modified
return d
}
// SetPageSize 设置页面大小
func (d *Document) SetPageSize(width, height int, orientation string) *Document {
d.Body.SectionProperties.PageSize.Width = width
d.Body.SectionProperties.PageSize.Height = height
d.Body.SectionProperties.PageSize.Orientation = orientation
return d
}
// SetPageSizeA4 设置页面大小为A4
func (d *Document) SetPageSizeA4(landscape bool) *Document {
if landscape {
return d.SetPageSize(16838, 11906, "landscape")
}
return d.SetPageSize(11906, 16838, "portrait")
}
// SetPageSizeA5 设置页面大小为A5
func (d *Document) SetPageSizeA5(landscape bool) *Document {
if landscape {
return d.SetPageSize(11906, 8419, "landscape")
}
return d.SetPageSize(8419, 11906, "portrait")
}
// SetPageSizeLetter 设置页面大小为Letter
func (d *Document) SetPageSizeLetter(landscape bool) *Document {
if landscape {
return d.SetPageSize(15840, 12240, "landscape")
}
return d.SetPageSize(12240, 15840, "portrait")
}
// SetPageMargin 设置页面边距
func (d *Document) SetPageMargin(top, right, bottom, left, header, footer, gutter int) *Document {
d.Body.SectionProperties.PageMargin.Top = top
d.Body.SectionProperties.PageMargin.Right = right
d.Body.SectionProperties.PageMargin.Bottom = bottom
d.Body.SectionProperties.PageMargin.Left = left
d.Body.SectionProperties.PageMargin.Header = header
d.Body.SectionProperties.PageMargin.Footer = footer
d.Body.SectionProperties.PageMargin.Gutter = gutter
return d
}
// SetColumns 设置分栏
func (d *Document) SetColumns(num, space int) *Document {
d.Body.SectionProperties.Columns.Num = num
d.Body.SectionProperties.Columns.Space = space
return d
}
// AddHeaderReference 添加页眉引用
func (d *Document) AddHeaderReference(headerType, id string) *Document {
headerRef := &HeaderFooterReference{
Type: headerType,
ID: id,
}
d.Body.SectionProperties.HeaderReference = append(d.Body.SectionProperties.HeaderReference, headerRef)
return d
}
// AddFooterReference 添加页脚引用
func (d *Document) AddFooterReference(footerType, id string) *Document {
footerRef := &HeaderFooterReference{
Type: footerType,
ID: id,
}
d.Body.SectionProperties.FooterReference = append(d.Body.SectionProperties.FooterReference, footerRef)
return d
}
// AddHeaderWithReference 添加页眉并同时添加页眉引用
func (d *Document) AddHeaderWithReference(headerType string) *Header {
header := NewHeader()
d.Headers = append(d.Headers, header)
// 添加页眉关系
headerID := fmt.Sprintf("rId%d", len(d.Rels.Relationships.Relationships)+1)
headerPath := fmt.Sprintf("header%d.xml", len(d.Headers))
d.Rels.AddHeader(headerID, headerPath)
// 添加页眉内容类型
d.ContentTypes.AddHeaderOverride(len(d.Headers))
// 添加页眉引用
d.AddHeaderReference(headerType, headerID)
return header
}
// AddFooterWithReference 添加页脚并同时添加页脚引用
func (d *Document) AddFooterWithReference(footerType string) *Footer {
footer := NewFooter()
d.Footers = append(d.Footers, footer)
// 添加页脚关系
footerID := fmt.Sprintf("rId%d", len(d.Rels.Relationships.Relationships)+1)
footerPath := fmt.Sprintf("footer%d.xml", len(d.Footers))
d.Rels.AddFooter(footerID, footerPath)
// 添加页脚内容类型
d.ContentTypes.AddFooterOverride(len(d.Footers))
// 添加页脚引用
d.AddFooterReference(footerType, footerID)
return footer
}

336
document/drawing.go Normal file
View File

@@ -0,0 +1,336 @@
package document
import (
"encoding/base64"
"fmt"
"os"
"path/filepath"
"strings"
)
// Drawing 表示Word文档中的图形
type Drawing struct {
ID string
Name string
Description string
ImagePath string
ImageData []byte
Width int // 单位为EMU (English Metric Unit)
Height int // 单位为EMU (1厘米 = 360000 EMU)
WrapType string // 文字环绕方式inline, square, tight, through, topAndBottom, behind, inFront
PositionH *DrawingPosition
PositionV *DrawingPosition
}
// DrawingPosition 表示图形的位置
type DrawingPosition struct {
RelativeFrom string // 相对位置page, margin, column, paragraph, line, character
Align string // 对齐方式left, center, right, inside, outside
Offset int // 偏移量单位为EMU
}
// NewDrawing 创建一个新的图形
func NewDrawing() *Drawing {
return &Drawing{
ID: generateUniqueID(),
WrapType: "inline",
}
}
// SetImagePath 设置图片路径
func (d *Drawing) SetImagePath(path string) *Drawing {
d.ImagePath = path
// 设置图片名称
if d.Name == "" {
d.Name = filepath.Base(path)
}
// 读取图片数据
data, err := os.ReadFile(path)
if err == nil {
d.ImageData = data
}
return d
}
// SetImageData 设置图片数据
func (d *Drawing) SetImageData(data []byte) *Drawing {
d.ImageData = data
return d
}
// SetSize 设置图片大小
func (d *Drawing) SetSize(width, height int) *Drawing {
d.Width = width
d.Height = height
return d
}
// SetName 设置图片名称
func (d *Drawing) SetName(name string) *Drawing {
d.Name = name
return d
}
// SetDescription 设置图片描述
func (d *Drawing) SetDescription(description string) *Drawing {
d.Description = description
return d
}
// SetWrapType 设置文字环绕方式
func (d *Drawing) SetWrapType(wrapType string) *Drawing {
d.WrapType = wrapType
return d
}
// SetPositionH 设置水平位置
func (d *Drawing) SetPositionH(relativeFrom, align string, offset int) *Drawing {
d.PositionH = &DrawingPosition{
RelativeFrom: relativeFrom,
Align: align,
Offset: offset,
}
return d
}
// SetPositionV 设置垂直位置
func (d *Drawing) SetPositionV(relativeFrom, align string, offset int) *Drawing {
d.PositionV = &DrawingPosition{
RelativeFrom: relativeFrom,
Align: align,
Offset: offset,
}
return d
}
// ToXML 将图形转换为XML
func (d *Drawing) ToXML() string {
xml := "<w:drawing>"
// 内联图片
if d.WrapType == "inline" {
xml += "<wp:inline distT=\"0\" distB=\"0\" distL=\"0\" distR=\"0\">"
// 图片大小
xml += "<wp:extent cx=\"" + fmt.Sprintf("%d", d.Width) + "\" cy=\"" + fmt.Sprintf("%d", d.Height) + "\" />"
// 图片效果
xml += "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\" />"
// 文档中的图片
xml += "<wp:docPr id=\"" + d.ID + "\" name=\"" + d.Name + "\" descr=\"" + d.Description + "\" />"
// 图片属性
xml += "<wp:cNvGraphicFramePr>"
xml += "<a:graphicFrameLocks xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" noChangeAspect=\"1\" />"
xml += "</wp:cNvGraphicFramePr>"
// 图片
xml += "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
xml += "<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
xml += "<pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
// 图片信息
xml += "<pic:nvPicPr>"
xml += "<pic:cNvPr id=\"0\" name=\"" + d.Name + "\" descr=\"" + d.Description + "\" />"
xml += "<pic:cNvPicPr>"
xml += "<a:picLocks noChangeAspect=\"1\" noChangeArrowheads=\"1\" />"
xml += "</pic:cNvPicPr>"
xml += "</pic:nvPicPr>"
// 图片填充
xml += "<pic:blipFill>"
xml += "<a:blip r:embed=\"rId" + d.ID + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" />"
xml += "<a:stretch>"
xml += "<a:fillRect />"
xml += "</a:stretch>"
xml += "</pic:blipFill>"
// 图片形状
xml += "<pic:spPr>"
xml += "<a:xfrm>"
xml += "<a:off x=\"0\" y=\"0\" />"
xml += "<a:ext cx=\"" + fmt.Sprintf("%d", d.Width) + "\" cy=\"" + fmt.Sprintf("%d", d.Height) + "\" />"
xml += "</a:xfrm>"
xml += "<a:prstGeom prst=\"rect\">"
xml += "<a:avLst />"
xml += "</a:prstGeom>"
xml += "</pic:spPr>"
xml += "</pic:pic>"
xml += "</a:graphicData>"
xml += "</a:graphic>"
xml += "</wp:inline>"
} else {
// 浮动图片
xml += "<wp:anchor distT=\"0\" distB=\"0\" distL=\"0\" distR=\"0\" simplePos=\"0\" relativeHeight=\"0\" behindDoc=\"" + boolToString(d.WrapType == "behind") + "\" locked=\"0\" layoutInCell=\"1\" allowOverlap=\"1\">"
// 简单位置
xml += "<wp:simplePos x=\"0\" y=\"0\" />"
// 水平位置
if d.PositionH != nil {
xml += "<wp:positionH relativeFrom=\"" + d.PositionH.RelativeFrom + "\">"
if d.PositionH.Align != "" {
xml += "<wp:align>" + d.PositionH.Align + "</wp:align>"
} else {
xml += "<wp:posOffset>" + fmt.Sprintf("%d", d.PositionH.Offset) + "</wp:posOffset>"
}
xml += "</wp:positionH>"
} else {
xml += "<wp:positionH relativeFrom=\"column\">"
xml += "<wp:align>left</wp:align>"
xml += "</wp:positionH>"
}
// 垂直位置
if d.PositionV != nil {
xml += "<wp:positionV relativeFrom=\"" + d.PositionV.RelativeFrom + "\">"
if d.PositionV.Align != "" {
xml += "<wp:align>" + d.PositionV.Align + "</wp:align>"
} else {
xml += "<wp:posOffset>" + fmt.Sprintf("%d", d.PositionV.Offset) + "</wp:posOffset>"
}
xml += "</wp:positionV>"
} else {
xml += "<wp:positionV relativeFrom=\"paragraph\">"
xml += "<wp:align>top</wp:align>"
xml += "</wp:positionV>"
}
// 图片大小
xml += "<wp:extent cx=\"" + fmt.Sprintf("%d", d.Width) + "\" cy=\"" + fmt.Sprintf("%d", d.Height) + "\" />"
// 图片效果
xml += "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\" />"
// 文字环绕方式
switch d.WrapType {
case "square":
xml += "<wp:wrapSquare wrapText=\"bothSides\" />"
case "tight":
xml += "<wp:wrapTight wrapText=\"bothSides\" />"
case "through":
xml += "<wp:wrapThrough wrapText=\"bothSides\" />"
case "topAndBottom":
xml += "<wp:wrapTopAndBottom />"
case "behind":
xml += "<wp:wrapNone />"
case "inFront":
xml += "<wp:wrapNone />"
default:
xml += "<wp:wrapSquare wrapText=\"bothSides\" />"
}
// 文档中的图片
xml += "<wp:docPr id=\"" + d.ID + "\" name=\"" + d.Name + "\" descr=\"" + d.Description + "\" />"
// 图片属性
xml += "<wp:cNvGraphicFramePr>"
xml += "<a:graphicFrameLocks xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" noChangeAspect=\"1\" />"
xml += "</wp:cNvGraphicFramePr>"
// 图片
xml += "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
xml += "<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
xml += "<pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
// 图片信息
xml += "<pic:nvPicPr>"
xml += "<pic:cNvPr id=\"0\" name=\"" + d.Name + "\" descr=\"" + d.Description + "\" />"
xml += "<pic:cNvPicPr>"
xml += "<a:picLocks noChangeAspect=\"1\" noChangeArrowheads=\"1\" />"
xml += "</pic:cNvPicPr>"
xml += "</pic:nvPicPr>"
// 图片填充
xml += "<pic:blipFill>"
xml += "<a:blip r:embed=\"rId" + d.ID + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" />"
xml += "<a:stretch>"
xml += "<a:fillRect />"
xml += "</a:stretch>"
xml += "</pic:blipFill>"
// 图片形状
xml += "<pic:spPr>"
xml += "<a:xfrm>"
xml += "<a:off x=\"0\" y=\"0\" />"
xml += "<a:ext cx=\"" + fmt.Sprintf("%d", d.Width) + "\" cy=\"" + fmt.Sprintf("%d", d.Height) + "\" />"
xml += "</a:xfrm>"
xml += "<a:prstGeom prst=\"rect\">"
xml += "<a:avLst />"
xml += "</a:prstGeom>"
xml += "</pic:spPr>"
xml += "</pic:pic>"
xml += "</a:graphicData>"
xml += "</a:graphic>"
xml += "</wp:anchor>"
}
xml += "</w:drawing>"
return xml
}
// GetImageData 获取图片数据的Base64编码
func (d *Drawing) GetImageData() string {
return base64.StdEncoding.EncodeToString(d.ImageData)
}
// GetImageType 获取图片类型
func (d *Drawing) GetImageType() string {
if d.ImagePath != "" {
ext := strings.ToLower(filepath.Ext(d.ImagePath))
switch ext {
case ".jpg", ".jpeg":
return "jpeg"
case ".png":
return "png"
case ".gif":
return "gif"
case ".bmp":
return "bmp"
case ".tif", ".tiff":
return "tiff"
case ".wmf":
return "x-wmf"
case ".emf":
return "x-emf"
default:
return "jpeg"
}
}
return "jpeg"
}
// GetContentType 获取图片的Content-Type
func (d *Drawing) GetContentType() string {
switch d.GetImageType() {
case "jpeg":
return "image/jpeg"
case "png":
return "image/png"
case "gif":
return "image/gif"
case "bmp":
return "image/bmp"
case "tiff":
return "image/tiff"
case "x-wmf":
return "image/x-wmf"
case "x-emf":
return "image/x-emf"
default:
return "image/jpeg"
}
}
// Error 实现error接口
func (d *Drawing) Error() string {
return fmt.Sprintf("Drawing error: %s", d.Description)
}

View File

@@ -0,0 +1,150 @@
package main
import (
"fmt"
"github.com/landaiqing/go-dockit/document"
"log"
)
func main() {
// 创建一个新的Word文档
doc := document.NewDocument()
// 设置文档属性
doc.SetTitle("示例文档")
doc.SetCreator("FlowDoc库")
doc.SetDescription("这是一个使用go-dockit库创建的示例文档")
// 添加一个标题段落
titlePara := doc.AddParagraph()
titlePara.SetAlignment("center")
titleRun := titlePara.AddRun()
titleRun.AddText("FlowDoc示例文档")
titleRun.SetBold(true)
titleRun.SetFontSize(32) // 16磅
titleRun.SetFontFamily("黑体")
// 添加一个普通段落
para1 := doc.AddParagraph()
para1.SetAlignment("left")
para1.SetIndentFirstLine(420) // 首行缩进0.3厘米
run1 := para1.AddRun()
run1.AddText("这是一个使用go-dockit库创建的示例文档。该库提供了一种简单的方式来生成Word文档支持段落、表格、列表、图片等元素。")
// 添加一个带样式的段落
para2 := doc.AddParagraph()
para2.SetAlignment("left")
para2.SetIndentFirstLine(420)
para2.SetSpacingAfter(200) // 段后间距
run2 := para2.AddRun()
run2.AddText("这个段落演示了不同的文本样式:")
// 添加不同样式的文本
para2.AddRun().AddText("粗体").SetBold(true)
para2.AddRun().AddText("、")
para2.AddRun().AddText("斜体").SetItalic(true)
para2.AddRun().AddText("、")
para2.AddRun().AddText("下划线").SetUnderline("single")
para2.AddRun().AddText("、")
para2.AddRun().AddText("红色文本").SetColor("FF0000")
para2.AddRun().AddText("、")
para2.AddRun().AddText("黄色高亮").SetHighlight("yellow")
// 添加一个标题
headingPara := doc.AddParagraph()
headingPara.SetSpacingBefore(400)
headingPara.SetSpacingAfter(200)
headingRun := headingPara.AddRun()
headingRun.AddText("表格示例")
headingRun.SetBold(true)
headingRun.SetFontSize(28) // 14磅
// 添加一个表格
table := doc.AddTable(3, 3)
table.SetWidth(8000, "dxa") // 约14厘米宽
table.SetAlignment("center")
// 设置表头
headerRow := table.Rows[0]
headerRow.SetIsHeader(true)
// 填充表头单元格
headerRow.Cells[0].AddParagraph().AddRun().AddText("产品名称").SetBold(true)
headerRow.Cells[1].AddParagraph().AddRun().AddText("数量").SetBold(true)
headerRow.Cells[2].AddParagraph().AddRun().AddText("单价").SetBold(true)
// 填充表格数据
table.Rows[1].Cells[0].AddParagraph().AddRun().AddText("产品A")
table.Rows[1].Cells[1].AddParagraph().AddRun().AddText("10")
table.Rows[1].Cells[2].AddParagraph().AddRun().AddText("¥100.00")
table.Rows[2].Cells[0].AddParagraph().AddRun().AddText("产品B")
table.Rows[2].Cells[1].AddParagraph().AddRun().AddText("5")
table.Rows[2].Cells[2].AddParagraph().AddRun().AddText("¥200.00")
// 添加一个分页符
doc.Body.AddPageBreak()
// 添加一个标题
listHeadingPara := doc.AddParagraph()
listHeadingPara.SetSpacingBefore(400)
listHeadingPara.SetSpacingAfter(200)
listHeadingRun := listHeadingPara.AddRun()
listHeadingRun.AddText("列表示例")
listHeadingRun.SetBold(true)
listHeadingRun.SetFontSize(28) // 14磅
// 创建一个项目符号列表
bulletListId := doc.Numbering.CreateBulletList()
// 添加列表项
listItem1 := doc.AddParagraph()
listItem1.SetNumbering(bulletListId, 0)
listItem1.AddRun().AddText("这是第一个列表项")
listItem2 := doc.AddParagraph()
listItem2.SetNumbering(bulletListId, 0)
listItem2.AddRun().AddText("这是第二个列表项")
listItem3 := doc.AddParagraph()
listItem3.SetNumbering(bulletListId, 0)
listItem3.AddRun().AddText("这是第三个列表项")
// 创建一个数字列表
numberListId := doc.Numbering.CreateNumberList()
// 添加列表项
numListItem1 := doc.AddParagraph()
numListItem1.SetNumbering(numberListId, 0)
numListItem1.AddRun().AddText("这是第一个数字列表项")
numListItem2 := doc.AddParagraph()
numListItem2.SetNumbering(numberListId, 0)
numListItem2.AddRun().AddText("这是第二个数字列表项")
numListItem3 := doc.AddParagraph()
numListItem3.SetNumbering(numberListId, 0)
numListItem3.AddRun().AddText("这是第三个数字列表项")
// 添加页眉并同时添加页眉引用
header := doc.AddHeaderWithReference("default")
headerPara := header.AddParagraph()
headerPara.SetAlignment("right")
headerPara.AddRun().AddText("FlowDoc示例文档 - 页眉")
// 添加页脚并同时添加页脚引用
footer := doc.AddFooterWithReference("default")
footerPara := footer.AddParagraph()
footerPara.SetAlignment("center")
footerPara.AddRun().AddText("第 ")
footerPara.AddRun().AddPageNumber()
footerPara.AddRun().AddText(" 页")
// 保存文档
err := doc.Save("./document/examples/simple/example.docx")
if err != nil {
log.Fatalf("保存文档时出错: %v", err)
}
fmt.Println("文档已成功保存为 example.docx")
}

95
document/header_footer.go Normal file
View File

@@ -0,0 +1,95 @@
package document
// Header 表示Word文档中的页眉
type Header struct {
ID string
Content []interface{} // 可以是段落、表格等元素
}
// Footer 表示Word文档中的页脚
type Footer struct {
ID string
Content []interface{} // 可以是段落、表格等元素
}
// NewHeader 创建一个新的页眉
func NewHeader() *Header {
return &Header{
ID: generateUniqueID(),
Content: make([]interface{}, 0),
}
}
// NewFooter 创建一个新的页脚
func NewFooter() *Footer {
return &Footer{
ID: generateUniqueID(),
Content: make([]interface{}, 0),
}
}
// AddParagraph 向页眉添加一个段落并返回它
func (h *Header) AddParagraph() *Paragraph {
p := NewParagraph()
h.Content = append(h.Content, p)
return p
}
// AddTable 向页眉添加一个表格并返回它
func (h *Header) AddTable(rows, cols int) *Table {
t := NewTable(rows, cols)
h.Content = append(h.Content, t)
return t
}
// AddParagraph 向页脚添加一个段落并返回它
func (f *Footer) AddParagraph() *Paragraph {
p := NewParagraph()
f.Content = append(f.Content, p)
return p
}
// AddTable 向页脚添加一个表格并返回它
func (f *Footer) AddTable(rows, cols int) *Table {
t := NewTable(rows, cols)
f.Content = append(f.Content, t)
return t
}
// ToXML 将页眉转换为XML
func (h *Header) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<w:hdr xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
// 添加所有内容元素的XML
for _, content := range h.Content {
switch v := content.(type) {
case *Paragraph:
xml += v.ToXML()
case *Table:
xml += v.ToXML()
}
}
xml += "</w:hdr>"
return xml
}
// ToXML 将页脚转换为XML
func (f *Footer) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<w:ftr xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
// 添加所有内容元素的XML
for _, content := range f.Content {
switch v := content.(type) {
case *Paragraph:
xml += v.ToXML()
case *Table:
xml += v.ToXML()
}
}
xml += "</w:ftr>"
return xml
}

375
document/numbering.go Normal file
View File

@@ -0,0 +1,375 @@
package document
import (
"fmt"
)
// Numbering 表示Word文档中的编号集合
type Numbering struct {
AbstractNums []*AbstractNum
Nums []*Num
}
// AbstractNum 表示抽象编号
type AbstractNum struct {
ID int
Levels []*NumberingLevel
}
// Num 表示具体编号
type Num struct {
ID int
AbstractNumID int
LevelOverrides []*LevelOverride
}
// LevelOverride 表示级别覆盖
type LevelOverride struct {
Level int
StartAt int
NumberingLevel *NumberingLevel
}
// NumberingLevel 表示编号级别
type NumberingLevel struct {
Level int
Start int
NumberingFormat string // decimal, upperRoman, lowerRoman, upperLetter, lowerLetter, bullet, etc.
Text string // 编号文本,如 "%1."
Justification string // left, center, right
ParagraphStyle string // 段落样式ID
Font string // 字体
Indent int // 缩进
HangingIndent int // 悬挂缩进
TabStop int // 制表位
Suffix string // tab, space, nothing
}
// NewNumbering 创建一个新的编号集合
func NewNumbering() *Numbering {
return &Numbering{
AbstractNums: make([]*AbstractNum, 0),
Nums: make([]*Num, 0),
}
}
// AddAbstractNum 添加一个抽象编号
func (n *Numbering) AddAbstractNum() *AbstractNum {
abstractNum := &AbstractNum{
ID: len(n.AbstractNums) + 1,
Levels: make([]*NumberingLevel, 0),
}
n.AbstractNums = append(n.AbstractNums, abstractNum)
return abstractNum
}
// AddNum 添加一个具体编号
func (n *Numbering) AddNum(abstractNumID int) *Num {
num := &Num{
ID: len(n.Nums) + 1,
AbstractNumID: abstractNumID,
LevelOverrides: make([]*LevelOverride, 0),
}
n.Nums = append(n.Nums, num)
return num
}
// AddLevel 向抽象编号添加一个级别
func (a *AbstractNum) AddLevel(level int) *NumberingLevel {
numberingLevel := &NumberingLevel{
Level: level,
Start: 1,
NumberingFormat: "decimal",
Text: "%" + fmt.Sprintf("%d", level+1) + ".",
Justification: "left",
Indent: 720 * (level + 1), // 720 twip = 0.5 inch
HangingIndent: 360, // 360 twip = 0.25 inch
TabStop: 720 * (level + 1),
Suffix: "tab",
}
a.Levels = append(a.Levels, numberingLevel)
return numberingLevel
}
// AddLevelOverride 向具体编号添加一个级别覆盖
func (n *Num) AddLevelOverride(level int) *LevelOverride {
levelOverride := &LevelOverride{
Level: level,
StartAt: 1,
}
n.LevelOverrides = append(n.LevelOverrides, levelOverride)
return levelOverride
}
// SetStart 设置编号级别的起始值
func (l *NumberingLevel) SetStart(start int) *NumberingLevel {
l.Start = start
return l
}
// SetNumberingFormat 设置编号级别的格式
func (l *NumberingLevel) SetNumberingFormat(format string) *NumberingLevel {
l.NumberingFormat = format
return l
}
// SetText 设置编号级别的文本
func (l *NumberingLevel) SetText(text string) *NumberingLevel {
l.Text = text
return l
}
// SetJustification 设置编号级别的对齐方式
func (l *NumberingLevel) SetJustification(justification string) *NumberingLevel {
l.Justification = justification
return l
}
// SetParagraphStyle 设置编号级别的段落样式
func (l *NumberingLevel) SetParagraphStyle(style string) *NumberingLevel {
l.ParagraphStyle = style
return l
}
// SetFont 设置编号级别的字体
func (l *NumberingLevel) SetFont(font string) *NumberingLevel {
l.Font = font
return l
}
// SetIndent 设置编号级别的缩进
func (l *NumberingLevel) SetIndent(indent int) *NumberingLevel {
l.Indent = indent
return l
}
// SetHangingIndent 设置编号级别的悬挂缩进
func (l *NumberingLevel) SetHangingIndent(hangingIndent int) *NumberingLevel {
l.HangingIndent = hangingIndent
return l
}
// SetTabStop 设置编号级别的制表位
func (l *NumberingLevel) SetTabStop(tabStop int) *NumberingLevel {
l.TabStop = tabStop
return l
}
// SetSuffix 设置编号级别的后缀
func (l *NumberingLevel) SetSuffix(suffix string) *NumberingLevel {
l.Suffix = suffix
return l
}
// SetStartAt 设置级别覆盖的起始值
func (l *LevelOverride) SetStartAt(startAt int) *LevelOverride {
l.StartAt = startAt
return l
}
// SetNumberingLevel 设置级别覆盖的编号级别
func (l *LevelOverride) SetNumberingLevel(level *NumberingLevel) *LevelOverride {
l.NumberingLevel = level
return l
}
// CreateBulletList 创建一个项目符号列表
func (n *Numbering) CreateBulletList() int {
// 创建抽象编号
abstractNum := n.AddAbstractNum()
// 添加9个级别
for i := 0; i < 9; i++ {
level := abstractNum.AddLevel(i)
level.SetNumberingFormat("bullet")
// 根据级别设置不同的项目符号
switch i % 3 {
case 0:
level.SetText("•")
level.SetFont("Symbol")
case 1:
level.SetText("○")
level.SetFont("Courier New")
case 2:
level.SetText("▪")
level.SetFont("Wingdings")
}
}
// 创建具体编号
num := n.AddNum(abstractNum.ID)
return num.ID
}
// CreateNumberList 创建一个数字列表
func (n *Numbering) CreateNumberList() int {
// 创建抽象编号
abstractNum := n.AddAbstractNum()
// 添加9个级别
for i := 0; i < 9; i++ {
level := abstractNum.AddLevel(i)
// 根据级别设置不同的编号格式
switch i % 3 {
case 0:
level.SetNumberingFormat("decimal")
level.SetText("%" + fmt.Sprintf("%d", i+1) + ".")
case 1:
level.SetNumberingFormat("lowerLetter")
level.SetText("%" + fmt.Sprintf("%d", i+1) + ").")
case 2:
level.SetNumberingFormat("lowerRoman")
level.SetText("%" + fmt.Sprintf("%d", i+1) + ").")
}
}
// 创建具体编号
num := n.AddNum(abstractNum.ID)
return num.ID
}
// ToXML 将编号集合转换为XML
func (n *Numbering) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<w:numbering xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
// 添加所有抽象编号
for _, abstractNum := range n.AbstractNums {
xml += "<w:abstractNum w:abstractNumId=\"" + fmt.Sprintf("%d", abstractNum.ID) + "\">"
// 添加所有级别
for _, level := range abstractNum.Levels {
xml += "<w:lvl w:ilvl=\"" + fmt.Sprintf("%d", level.Level) + "\">"
// 起始值
xml += "<w:start w:val=\"" + fmt.Sprintf("%d", level.Start) + "\" />"
// 编号格式
xml += "<w:numFmt w:val=\"" + level.NumberingFormat + "\" />"
// 编号文本
xml += "<w:lvlText w:val=\"" + level.Text + "\" />"
// 对齐方式
xml += "<w:lvlJc w:val=\"" + level.Justification + "\" />"
// 段落属性
xml += "<w:pPr>"
// 缩进
xml += "<w:ind w:left=\"" + fmt.Sprintf("%d", level.Indent) + "\""
xml += " w:hanging=\"" + fmt.Sprintf("%d", level.HangingIndent) + "\" />"
// 制表位
if level.TabStop > 0 {
xml += "<w:tabs>"
xml += "<w:tab w:val=\"num\" w:pos=\"" + fmt.Sprintf("%d", level.TabStop) + "\" />"
xml += "</w:tabs>"
}
xml += "</w:pPr>"
// 文本运行属性
xml += "<w:rPr>"
// 字体
if level.Font != "" {
xml += "<w:rFonts w:ascii=\"" + level.Font + "\""
xml += " w:hAnsi=\"" + level.Font + "\""
xml += " w:hint=\"default\" />"
}
xml += "</w:rPr>"
// 后缀
if level.Suffix != "" {
xml += "<w:suff w:val=\"" + level.Suffix + "\" />"
}
xml += "</w:lvl>"
}
xml += "</w:abstractNum>"
}
// 添加所有具体编号
for _, num := range n.Nums {
xml += "<w:num w:numId=\"" + fmt.Sprintf("%d", num.ID) + "\">"
// 抽象编号ID
xml += "<w:abstractNumId w:val=\"" + fmt.Sprintf("%d", num.AbstractNumID) + "\" />"
// 添加所有级别覆盖
for _, levelOverride := range num.LevelOverrides {
xml += "<w:lvlOverride w:ilvl=\"" + fmt.Sprintf("%d", levelOverride.Level) + "\">"
// 起始值
if levelOverride.StartAt > 0 {
xml += "<w:startOverride w:val=\"" + fmt.Sprintf("%d", levelOverride.StartAt) + "\" />"
}
// 编号级别
if levelOverride.NumberingLevel != nil {
xml += "<w:lvl w:ilvl=\"" + fmt.Sprintf("%d", levelOverride.NumberingLevel.Level) + "\">"
// 起始值
xml += "<w:start w:val=\"" + fmt.Sprintf("%d", levelOverride.NumberingLevel.Start) + "\" />"
// 编号格式
xml += "<w:numFmt w:val=\"" + levelOverride.NumberingLevel.NumberingFormat + "\" />"
// 编号文本
xml += "<w:lvlText w:val=\"" + levelOverride.NumberingLevel.Text + "\" />"
// 对齐方式
xml += "<w:lvlJc w:val=\"" + levelOverride.NumberingLevel.Justification + "\" />"
// 段落属性
xml += "<w:pPr>"
// 缩进
xml += "<w:ind w:left=\"" + fmt.Sprintf("%d", levelOverride.NumberingLevel.Indent) + "\""
xml += " w:hanging=\"" + fmt.Sprintf("%d", levelOverride.NumberingLevel.HangingIndent) + "\" />"
// 制表位
if levelOverride.NumberingLevel.TabStop > 0 {
xml += "<w:tabs>"
xml += "<w:tab w:val=\"num\" w:pos=\"" + fmt.Sprintf("%d", levelOverride.NumberingLevel.TabStop) + "\" />"
xml += "</w:tabs>"
}
xml += "</w:pPr>"
// 文本运行属性
xml += "<w:rPr>"
// 字体
if levelOverride.NumberingLevel.Font != "" {
xml += "<w:rFonts w:ascii=\"" + levelOverride.NumberingLevel.Font + "\""
xml += " w:hAnsi=\"" + levelOverride.NumberingLevel.Font + "\""
xml += " w:hint=\"default\" />"
}
xml += "</w:rPr>"
// 后缀
if levelOverride.NumberingLevel.Suffix != "" {
xml += "<w:suff w:val=\"" + levelOverride.NumberingLevel.Suffix + "\" />"
}
xml += "</w:lvl>"
}
xml += "</w:lvlOverride>"
}
xml += "</w:num>"
}
xml += "</w:numbering>"
return xml
}

299
document/paragraph.go Normal file
View File

@@ -0,0 +1,299 @@
package document
import "fmt"
// Paragraph 表示Word文档中的段落
type Paragraph struct {
Runs []*Run
Properties *ParagraphProperties
}
// ParagraphProperties 表示段落的属性
type ParagraphProperties struct {
Alignment string // left, center, right, justified
IndentLeft int // 左缩进单位为twip (1/20 point)
IndentRight int // 右缩进
IndentFirstLine int // 首行缩进
SpacingBefore int // 段前间距
SpacingAfter int // 段后间距
SpacingLine int // 行间距
SpacingLineRule string // auto, exact, atLeast
KeepNext bool // 与下段同页
KeepLines bool // 段中不分页
PageBreakBefore bool // 段前分页
WidowControl bool // 孤行控制
OutlineLevel int // 大纲级别
StyleID string // 样式ID
NumID int // 编号ID
NumLevel int // 编号级别
BorderTop *Border
BorderBottom *Border
BorderLeft *Border
BorderRight *Border
Shading *Shading
}
// Border 表示边框
type Border struct {
Style string // single, double, dotted, dashed, etc.
Size int // 边框宽度单位为1/8点
Color string // 边框颜色格式为RRGGBB
Space int // 边框与文本的距离
}
// Shading 表示底纹
type Shading struct {
Fill string // 填充颜色
Color string // 文本颜色
Pattern string // 底纹样式
}
// NewParagraph 创建一个新的段落
func NewParagraph() *Paragraph {
return &Paragraph{
Runs: make([]*Run, 0),
Properties: &ParagraphProperties{
Alignment: "left",
WidowControl: true,
SpacingLineRule: "auto",
},
}
}
// AddRun 向段落添加一个文本运行并返回它
func (p *Paragraph) AddRun() *Run {
r := NewRun()
p.Runs = append(p.Runs, r)
return r
}
// AddText 向段落添加文本
func (p *Paragraph) AddText(text string) *Run {
return p.AddRun().AddText(text)
}
// SetAlignment 设置段落对齐方式
func (p *Paragraph) SetAlignment(alignment string) *Paragraph {
p.Properties.Alignment = alignment
return p
}
// SetIndentLeft 设置左缩进
func (p *Paragraph) SetIndentLeft(indent int) *Paragraph {
p.Properties.IndentLeft = indent
return p
}
// SetIndentRight 设置右缩进
func (p *Paragraph) SetIndentRight(indent int) *Paragraph {
p.Properties.IndentRight = indent
return p
}
// SetIndentFirstLine 设置首行缩进
func (p *Paragraph) SetIndentFirstLine(indent int) *Paragraph {
p.Properties.IndentFirstLine = indent
return p
}
// SetSpacingBefore 设置段前间距
func (p *Paragraph) SetSpacingBefore(spacing int) *Paragraph {
p.Properties.SpacingBefore = spacing
return p
}
// SetSpacingAfter 设置段后间距
func (p *Paragraph) SetSpacingAfter(spacing int) *Paragraph {
p.Properties.SpacingAfter = spacing
return p
}
// SetSpacingLine 设置行间距
func (p *Paragraph) SetSpacingLine(spacing int, rule string) *Paragraph {
p.Properties.SpacingLine = spacing
p.Properties.SpacingLineRule = rule
return p
}
// SetKeepNext 设置与下段同页
func (p *Paragraph) SetKeepNext(keepNext bool) *Paragraph {
p.Properties.KeepNext = keepNext
return p
}
// SetKeepLines 设置段中不分页
func (p *Paragraph) SetKeepLines(keepLines bool) *Paragraph {
p.Properties.KeepLines = keepLines
return p
}
// SetPageBreakBefore 设置段前分页
func (p *Paragraph) SetPageBreakBefore(pageBreakBefore bool) *Paragraph {
p.Properties.PageBreakBefore = pageBreakBefore
return p
}
// SetStyleID 设置样式ID
func (p *Paragraph) SetStyleID(styleID string) *Paragraph {
p.Properties.StyleID = styleID
return p
}
// SetNumbering 设置编号
func (p *Paragraph) SetNumbering(numID, numLevel int) *Paragraph {
p.Properties.NumID = numID
p.Properties.NumLevel = numLevel
return p
}
// SetBorder 设置边框
func (p *Paragraph) SetBorder(position string, style string, size int, color string, space int) *Paragraph {
border := &Border{
Style: style,
Size: size,
Color: color,
Space: space,
}
switch position {
case "top":
p.Properties.BorderTop = border
case "bottom":
p.Properties.BorderBottom = border
case "left":
p.Properties.BorderLeft = border
case "right":
p.Properties.BorderRight = border
}
return p
}
// SetShading 设置底纹
func (p *Paragraph) SetShading(fill, color, pattern string) *Paragraph {
p.Properties.Shading = &Shading{
Fill: fill,
Color: color,
Pattern: pattern,
}
return p
}
// ToXML 将段落转换为XML
func (p *Paragraph) ToXML() string {
xml := "<w:p>"
// 添加段落属性
xml += "<w:pPr>"
// 对齐方式
if p.Properties.Alignment != "" {
xml += "<w:jc w:val=\"" + p.Properties.Alignment + "\" />"
}
// 缩进
if p.Properties.IndentLeft > 0 || p.Properties.IndentRight > 0 || p.Properties.IndentFirstLine > 0 {
xml += "<w:ind"
if p.Properties.IndentLeft > 0 {
xml += " w:left=\"" + fmt.Sprintf("%d", p.Properties.IndentLeft) + "\""
}
if p.Properties.IndentRight > 0 {
xml += " w:right=\"" + fmt.Sprintf("%d", p.Properties.IndentRight) + "\""
}
if p.Properties.IndentFirstLine > 0 {
xml += " w:firstLine=\"" + fmt.Sprintf("%d", p.Properties.IndentFirstLine) + "\""
}
xml += " />"
}
// 间距
if p.Properties.SpacingBefore > 0 || p.Properties.SpacingAfter > 0 || p.Properties.SpacingLine > 0 {
xml += "<w:spacing"
if p.Properties.SpacingBefore > 0 {
xml += " w:before=\"" + fmt.Sprintf("%d", p.Properties.SpacingBefore) + "\""
}
if p.Properties.SpacingAfter > 0 {
xml += " w:after=\"" + fmt.Sprintf("%d", p.Properties.SpacingAfter) + "\""
}
if p.Properties.SpacingLine > 0 {
xml += " w:line=\"" + fmt.Sprintf("%d", p.Properties.SpacingLine) + "\""
xml += " w:lineRule=\"" + p.Properties.SpacingLineRule + "\""
}
xml += " />"
}
// 分页控制
if p.Properties.KeepNext {
xml += "<w:keepNext />"
}
if p.Properties.KeepLines {
xml += "<w:keepLines />"
}
if p.Properties.PageBreakBefore {
xml += "<w:pageBreakBefore />"
}
if p.Properties.WidowControl {
xml += "<w:widowControl />"
}
// 样式
if p.Properties.StyleID != "" {
xml += "<w:pStyle w:val=\"" + p.Properties.StyleID + "\" />"
}
// 编号
if p.Properties.NumID > 0 {
xml += "<w:numPr>"
xml += "<w:numId w:val=\"" + fmt.Sprintf("%d", p.Properties.NumID) + "\" />"
xml += "<w:ilvl w:val=\"" + fmt.Sprintf("%d", p.Properties.NumLevel) + "\" />"
xml += "</w:numPr>"
}
// 边框
if p.Properties.BorderTop != nil || p.Properties.BorderBottom != nil ||
p.Properties.BorderLeft != nil || p.Properties.BorderRight != nil {
xml += "<w:pBdr>"
if p.Properties.BorderTop != nil {
xml += "<w:top w:val=\"" + p.Properties.BorderTop.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", p.Properties.BorderTop.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", p.Properties.BorderTop.Space) + "\""
xml += " w:color=\"" + p.Properties.BorderTop.Color + "\" />"
}
if p.Properties.BorderBottom != nil {
xml += "<w:bottom w:val=\"" + p.Properties.BorderBottom.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", p.Properties.BorderBottom.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", p.Properties.BorderBottom.Space) + "\""
xml += " w:color=\"" + p.Properties.BorderBottom.Color + "\" />"
}
if p.Properties.BorderLeft != nil {
xml += "<w:left w:val=\"" + p.Properties.BorderLeft.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", p.Properties.BorderLeft.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", p.Properties.BorderLeft.Space) + "\""
xml += " w:color=\"" + p.Properties.BorderLeft.Color + "\" />"
}
if p.Properties.BorderRight != nil {
xml += "<w:right w:val=\"" + p.Properties.BorderRight.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", p.Properties.BorderRight.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", p.Properties.BorderRight.Space) + "\""
xml += " w:color=\"" + p.Properties.BorderRight.Color + "\" />"
}
xml += "</w:pBdr>"
}
// 底纹
if p.Properties.Shading != nil {
xml += "<w:shd w:val=\"" + p.Properties.Shading.Pattern + "\""
xml += " w:fill=\"" + p.Properties.Shading.Fill + "\""
xml += " w:color=\"" + p.Properties.Shading.Color + "\" />"
}
xml += "</w:pPr>"
// 添加所有Run的XML
for _, run := range p.Runs {
xml += run.ToXML()
}
xml += "</w:p>"
return xml
}

121
document/relationships.go Normal file
View File

@@ -0,0 +1,121 @@
package document
// Relationships 表示Word文档中的关系集合
type Relationships struct {
Relationships []*Relationship
}
// Relationship 表示Word文档中的关系
type Relationship struct {
ID string
Type string
Target string
TargetMode string // 目标模式Internal, External
}
// NewRelationships 创建一个新的关系集合
func NewRelationships() *Relationships {
return &Relationships{
Relationships: make([]*Relationship, 0),
}
}
// AddRelationship 添加一个关系
func (r *Relationships) AddRelationship(id, relType, target string) *Relationship {
rel := &Relationship{
ID: id,
Type: relType,
Target: target,
}
r.Relationships = append(r.Relationships, rel)
return rel
}
// AddExternalRelationship 添加一个外部关系
func (r *Relationships) AddExternalRelationship(id, relType, target string) *Relationship {
rel := &Relationship{
ID: id,
Type: relType,
Target: target,
TargetMode: "External",
}
r.Relationships = append(r.Relationships, rel)
return rel
}
// GetRelationshipByID 根据ID获取关系
func (r *Relationships) GetRelationshipByID(id string) *Relationship {
for _, rel := range r.Relationships {
if rel.ID == id {
return rel
}
}
return nil
}
// GetRelationshipsByType 根据类型获取关系
func (r *Relationships) GetRelationshipsByType(relType string) []*Relationship {
result := make([]*Relationship, 0)
for _, rel := range r.Relationships {
if rel.Type == relType {
result = append(result, rel)
}
}
return result
}
// ToXML 将关系集合转换为XML
func (r *Relationships) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"
for _, rel := range r.Relationships {
xml += "<Relationship Id=\"" + rel.ID + "\""
xml += " Type=\"" + rel.Type + "\""
xml += " Target=\"" + rel.Target + "\""
if rel.TargetMode != "" {
xml += " TargetMode=\"" + rel.TargetMode + "\""
}
xml += " />"
}
xml += "</Relationships>"
return xml
}
// DocumentRels 表示Word文档中的文档关系
type DocumentRels struct {
Relationships *Relationships
}
// NewDocumentRels 创建一个新的文档关系
func NewDocumentRels() *DocumentRels {
return &DocumentRels{
Relationships: NewRelationships(),
}
}
// AddImage 添加一个图片关系
func (d *DocumentRels) AddImage(id, target string) *Relationship {
return d.Relationships.AddRelationship(id, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", target)
}
// AddHyperlink 添加一个超链接关系
func (d *DocumentRels) AddHyperlink(id, target string) *Relationship {
return d.Relationships.AddExternalRelationship(id, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", target)
}
// AddHeader 添加一个页眉关系
func (d *DocumentRels) AddHeader(id, target string) *Relationship {
return d.Relationships.AddRelationship(id, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header", target)
}
// AddFooter 添加一个页脚关系
func (d *DocumentRels) AddFooter(id, target string) *Relationship {
return d.Relationships.AddRelationship(id, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer", target)
}
// ToXML 将文档关系转换为XML
func (d *DocumentRels) ToXML() string {
return d.Relationships.ToXML()
}

345
document/run.go Normal file
View File

@@ -0,0 +1,345 @@
package document
import (
"fmt"
)
// Run 表示Word文档中的文本运行
type Run struct {
Text string
Properties *RunProperties
BreakType string
Drawing *Drawing
Field *Field
}
// Field 表示Word文档中的域
type Field struct {
Type string // begin, separate, end
Code string // 域代码
}
// RunProperties 表示文本运行的属性
type RunProperties struct {
Bold bool // 粗体
Italic bool // 斜体
Underline string // 下划线类型single, double, thick, dotted, dash, etc.
Strike bool // 删除线
DoubleStrike bool // 双删除线
Superscript bool // 上标
Subscript bool // 下标
FontSize int // 字号,单位为半点
FontFamily string // 字体
Color string // 颜色格式为RRGGBB
Highlight string // 突出显示颜色
Caps bool // 全部大写
SmallCaps bool // 小型大写
CharacterSpacing int // 字符间距
Shading *Shading // 底纹
VertAlign string // 垂直对齐方式baseline, superscript, subscript
RTL bool // 从右到左文本方向
Language string // 语言
}
// BreakType 表示分隔符类型
const (
BreakTypePage = "page" // 分页符
BreakTypeColumn = "column" // 分栏符
BreakTypeSection = "section" // 分节符
BreakTypeLine = "textWrapping" // 换行符
)
// NewRun 创建一个新的文本运行
func NewRun() *Run {
return &Run{
Text: "",
Properties: &RunProperties{
FontSize: 22, // 默认11磅 (22半点)
FontFamily: "Calibri",
Color: "000000",
},
}
}
// AddText 向文本运行添加文本
func (r *Run) AddText(text string) *Run {
r.Text = text
return r
}
// AddBreak 向文本运行添加分隔符
func (r *Run) AddBreak(breakType string) *Run {
r.BreakType = breakType
return r
}
// AddDrawing 向文本运行添加图形
func (r *Run) AddDrawing(drawing *Drawing) *Run {
r.Drawing = drawing
return r
}
// SetBold 设置粗体
func (r *Run) SetBold(bold bool) *Run {
r.Properties.Bold = bold
return r
}
// SetItalic 设置斜体
func (r *Run) SetItalic(italic bool) *Run {
r.Properties.Italic = italic
return r
}
// SetUnderline 设置下划线
func (r *Run) SetUnderline(underline string) *Run {
r.Properties.Underline = underline
return r
}
// SetStrike 设置删除线
func (r *Run) SetStrike(strike bool) *Run {
r.Properties.Strike = strike
return r
}
// SetDoubleStrike 设置双删除线
func (r *Run) SetDoubleStrike(doubleStrike bool) *Run {
r.Properties.DoubleStrike = doubleStrike
return r
}
// SetSuperscript 设置上标
func (r *Run) SetSuperscript(superscript bool) *Run {
r.Properties.Superscript = superscript
return r
}
// SetSubscript 设置下标
func (r *Run) SetSubscript(subscript bool) *Run {
r.Properties.Subscript = subscript
return r
}
// SetFontSize 设置字号
func (r *Run) SetFontSize(fontSize int) *Run {
r.Properties.FontSize = fontSize
return r
}
// SetFontFamily 设置字体
func (r *Run) SetFontFamily(fontFamily string) *Run {
r.Properties.FontFamily = fontFamily
return r
}
// SetColor 设置颜色
func (r *Run) SetColor(color string) *Run {
r.Properties.Color = color
return r
}
// SetHighlight 设置突出显示颜色
func (r *Run) SetHighlight(highlight string) *Run {
r.Properties.Highlight = highlight
return r
}
// SetCaps 设置全部大写
func (r *Run) SetCaps(caps bool) *Run {
r.Properties.Caps = caps
return r
}
// SetSmallCaps 设置小型大写
func (r *Run) SetSmallCaps(smallCaps bool) *Run {
r.Properties.SmallCaps = smallCaps
return r
}
// SetCharacterSpacing 设置字符间距
func (r *Run) SetCharacterSpacing(spacing int) *Run {
r.Properties.CharacterSpacing = spacing
return r
}
// SetShading 设置底纹
func (r *Run) SetShading(fill, color, pattern string) *Run {
r.Properties.Shading = &Shading{
Fill: fill,
Color: color,
Pattern: pattern,
}
return r
}
// SetVertAlign 设置垂直对齐方式
func (r *Run) SetVertAlign(vertAlign string) *Run {
r.Properties.VertAlign = vertAlign
return r
}
// SetRTL 设置从右到左文本方向
func (r *Run) SetRTL(rtl bool) *Run {
r.Properties.RTL = rtl
return r
}
// SetLanguage 设置语言
func (r *Run) SetLanguage(language string) *Run {
r.Properties.Language = language
return r
}
// AddField 添加Word域
func (r *Run) AddField(fieldType string, fieldCode string) *Run {
r.Text = ""
r.Field = &Field{
Type: fieldType,
Code: fieldCode,
}
return r
}
// AddPageNumber 添加页码域
func (r *Run) AddPageNumber() *Run {
return r.AddField("begin", " PAGE ")
}
// ToXML 将文本运行转换为XML
func (r *Run) ToXML() string {
xml := "<w:r>"
// 添加文本运行属性
xml += "<w:rPr>"
// 字体
if r.Properties.FontFamily != "" {
xml += "<w:rFonts w:ascii=\"" + r.Properties.FontFamily + "\""
xml += " w:eastAsia=\"" + r.Properties.FontFamily + "\""
xml += " w:hAnsi=\"" + r.Properties.FontFamily + "\""
xml += " w:cs=\"" + r.Properties.FontFamily + "\" />"
}
// 字号
if r.Properties.FontSize > 0 {
xml += "<w:sz w:val=\"" + fmt.Sprintf("%d", r.Properties.FontSize) + "\" />"
xml += "<w:szCs w:val=\"" + fmt.Sprintf("%d", r.Properties.FontSize) + "\" />"
}
// 颜色
if r.Properties.Color != "" {
xml += "<w:color w:val=\"" + r.Properties.Color + "\" />"
}
// 粗体
if r.Properties.Bold {
xml += "<w:b />"
xml += "<w:bCs />"
}
// 斜体
if r.Properties.Italic {
xml += "<w:i />"
xml += "<w:iCs />"
}
// 下划线
if r.Properties.Underline != "" {
xml += "<w:u w:val=\"" + r.Properties.Underline + "\" />"
}
// 删除线
if r.Properties.Strike {
xml += "<w:strike />"
}
// 双删除线
if r.Properties.DoubleStrike {
xml += "<w:dstrike />"
}
// 突出显示颜色
if r.Properties.Highlight != "" {
xml += "<w:highlight w:val=\"" + r.Properties.Highlight + "\" />"
}
// 全部大写
if r.Properties.Caps {
xml += "<w:caps />"
}
// 小型大写
if r.Properties.SmallCaps {
xml += "<w:smallCaps />"
}
// 字符间距
if r.Properties.CharacterSpacing != 0 {
xml += "<w:spacing w:val=\"" + fmt.Sprintf("%d", r.Properties.CharacterSpacing) + "\" />"
}
// 底纹
if r.Properties.Shading != nil {
xml += "<w:shd w:val=\"" + r.Properties.Shading.Pattern + "\""
xml += " w:fill=\"" + r.Properties.Shading.Fill + "\""
xml += " w:color=\"" + r.Properties.Shading.Color + "\" />"
}
// 上标/下标
if r.Properties.Superscript {
xml += "<w:vertAlign w:val=\"superscript\" />"
} else if r.Properties.Subscript {
xml += "<w:vertAlign w:val=\"subscript\" />"
} else if r.Properties.VertAlign != "" {
xml += "<w:vertAlign w:val=\"" + r.Properties.VertAlign + "\" />"
}
// 从右到左文本方向
if r.Properties.RTL {
xml += "<w:rtl />"
}
// 语言
if r.Properties.Language != "" {
xml += "<w:lang w:val=\"" + r.Properties.Language + "\" />"
}
xml += "</w:rPr>"
// 添加分隔符
if r.BreakType != "" {
xml += "<w:br w:type=\"" + r.BreakType + "\" />"
}
// 添加文本
if r.Text != "" {
xml += "<w:t xml:space=\"preserve\">" + r.Text + "</w:t>"
}
// 添加图形
if r.Drawing != nil {
xml += r.Drawing.ToXML()
}
// 添加域
if r.Field != nil {
if r.Field.Type == "begin" {
xml += "<w:fldChar w:fldCharType=\"begin\" />"
} else if r.Field.Type == "separate" {
xml += "<w:fldChar w:fldCharType=\"separate\" />"
} else if r.Field.Type == "end" {
xml += "<w:fldChar w:fldCharType=\"end\" />"
}
if r.Field.Code != "" && r.Field.Type == "begin" {
// 添加域代码
xml += "</w:r><w:r><w:instrText xml:space=\"preserve\">" + r.Field.Code + "</w:instrText></w:r><w:r><w:fldChar w:fldCharType=\"separate\" />"
// 添加域结束标记
xml += "</w:r><w:r><w:fldChar w:fldCharType=\"end\" />"
}
}
xml += "</w:r>"
return xml
}

202
document/settings.go Normal file
View File

@@ -0,0 +1,202 @@
package document
import (
"fmt"
)
// Settings 表示Word文档中的设置
type Settings struct {
UpdateFields bool // 更新域
Zoom int // 缩放比例
DefaultTabStop int // 默认制表位
CharacterSpacingControl string // 字符间距控制
Compatibility *Compatibility
}
// Compatibility 表示兼容性设置
type Compatibility struct {
CompatibilityMode string // 兼容模式
DoNotExpandShiftReturn bool // 不展开Shift+Enter
DoNotBreakWrappedTables bool // 不断开环绕表格
DoNotSnapToGridInCell bool // 单元格中不对齐网格
DoNotWrapTextWithPunct bool // 不使用标点符号换行
DoNotUseEastAsianBreakRules bool // 不使用东亚换行规则
DoNotUseIndentAsNumberingTabStop bool // 不使用缩进作为编号制表位
UseAnsiKerningPairs bool // 使用ANSI字距调整对
DoNotAutofitConstrainedTables bool // 不自动调整受限表格
SplitPgBreakAndParaMark bool // 分割分页符和段落标记
DoNotVertAlignCellWithSp bool // 不垂直对齐带有形状的单元格
DoNotBreakConstrainedForcedTable bool // 不断开受限强制表格
DoNotVertAlignInTxbx bool // 不在文本框中垂直对齐
UseAnsiSpaceForEnglishInEastAsia bool // 在东亚语言中为英文使用ANSI空格
AllowSpaceOfSameStyleInTable bool // 允许表格中相同样式的空格
DoNotSuppressIndentation bool // 不抑制缩进
DoNotAutospaceEastAsianText bool // 不自动调整东亚文本间距
DoNotUseHTMLParagraphAutoSpacing bool // 不使用HTML段落自动间距
}
// NewSettings 创建一个新的设置
func NewSettings() *Settings {
return &Settings{
UpdateFields: true,
Zoom: 100,
DefaultTabStop: 720, // 720 twip = 0.5 inch
CharacterSpacingControl: "doNotCompress",
Compatibility: NewCompatibility(),
}
}
// NewCompatibility 创建一个新的兼容性设置
func NewCompatibility() *Compatibility {
return &Compatibility{
CompatibilityMode: "15", // Word 2013
}
}
// SetUpdateFields 设置是否更新域
func (s *Settings) SetUpdateFields(updateFields bool) *Settings {
s.UpdateFields = updateFields
return s
}
// SetZoom 设置缩放比例
func (s *Settings) SetZoom(zoom int) *Settings {
s.Zoom = zoom
return s
}
// SetDefaultTabStop 设置默认制表位
func (s *Settings) SetDefaultTabStop(defaultTabStop int) *Settings {
s.DefaultTabStop = defaultTabStop
return s
}
// SetCharacterSpacingControl 设置字符间距控制
func (s *Settings) SetCharacterSpacingControl(characterSpacingControl string) *Settings {
s.CharacterSpacingControl = characterSpacingControl
return s
}
// SetCompatibilityMode 设置兼容模式
func (s *Settings) SetCompatibilityMode(compatibilityMode string) *Settings {
s.Compatibility.CompatibilityMode = compatibilityMode
return s
}
// ToXML 将设置转换为XML
func (s *Settings) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<w:settings xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
// 更新域
if s.UpdateFields {
xml += "<w:updateFields w:val=\"true\" />"
}
// 缩放比例
xml += "<w:zoom w:percent=\"" + fmt.Sprintf("%d", s.Zoom) + "\" />"
// 默认制表位
xml += "<w:defaultTabStop w:val=\"" + fmt.Sprintf("%d", s.DefaultTabStop) + "\" />"
// 字符间距控制
xml += "<w:characterSpacingControl w:val=\"" + s.CharacterSpacingControl + "\" />"
// 兼容性设置
xml += "<w:compat>"
// 兼容模式
if s.Compatibility.CompatibilityMode != "" {
xml += "<w:compatSetting w:name=\"compatibilityMode\" w:uri=\"http://schemas.microsoft.com/office/document\" w:val=\"" + s.Compatibility.CompatibilityMode + "\" />"
}
// 不展开Shift+Enter
if s.Compatibility.DoNotExpandShiftReturn {
xml += "<w:doNotExpandShiftReturn />"
}
// 不断开环绕表格
if s.Compatibility.DoNotBreakWrappedTables {
xml += "<w:doNotBreakWrappedTables />"
}
// 单元格中不对齐网格
if s.Compatibility.DoNotSnapToGridInCell {
xml += "<w:doNotSnapToGridInCell />"
}
// 不使用标点符号换行
if s.Compatibility.DoNotWrapTextWithPunct {
xml += "<w:doNotWrapTextWithPunct />"
}
// 不使用东亚换行规则
if s.Compatibility.DoNotUseEastAsianBreakRules {
xml += "<w:doNotUseEastAsianBreakRules />"
}
// 不使用缩进作为编号制表位
if s.Compatibility.DoNotUseIndentAsNumberingTabStop {
xml += "<w:doNotUseIndentAsNumberingTabStop />"
}
// 使用ANSI字距调整对
if s.Compatibility.UseAnsiKerningPairs {
xml += "<w:useAnsiKerningPairs />"
}
// 不自动调整受限表格
if s.Compatibility.DoNotAutofitConstrainedTables {
xml += "<w:doNotAutofitConstrainedTables />"
}
// 分割分页符和段落标记
if s.Compatibility.SplitPgBreakAndParaMark {
xml += "<w:splitPgBreakAndParaMark />"
}
// 不垂直对齐带有形状的单元格
if s.Compatibility.DoNotVertAlignCellWithSp {
xml += "<w:doNotVertAlignCellWithSp />"
}
// 不断开受限强制表格
if s.Compatibility.DoNotBreakConstrainedForcedTable {
xml += "<w:doNotBreakConstrainedForcedTable />"
}
// 不在文本框中垂直对齐
if s.Compatibility.DoNotVertAlignInTxbx {
xml += "<w:doNotVertAlignInTxbx />"
}
// 在东亚语言中为英文使用ANSI空格
if s.Compatibility.UseAnsiSpaceForEnglishInEastAsia {
xml += "<w:useAnsiSpaceForEnglishInEastAsia />"
}
// 允许表格中相同样式的空格
if s.Compatibility.AllowSpaceOfSameStyleInTable {
xml += "<w:allowSpaceOfSameStyleInTable />"
}
// 不抑制缩进
if s.Compatibility.DoNotSuppressIndentation {
xml += "<w:doNotSuppressIndentation />"
}
// 不自动调整东亚文本间距
if s.Compatibility.DoNotAutospaceEastAsianText {
xml += "<w:doNotAutospaceEastAsianText />"
}
// 不使用HTML段落自动间距
if s.Compatibility.DoNotUseHTMLParagraphAutoSpacing {
xml += "<w:doNotUseHTMLParagraphAutoSpacing />"
}
xml += "</w:compat>"
xml += "</w:settings>"
return xml
}

399
document/styles.go Normal file
View File

@@ -0,0 +1,399 @@
package document
import (
"fmt"
)
// Styles 表示Word文档中的样式集合
type Styles struct {
Styles []*Style
}
// Style 表示Word文档中的样式
type Style struct {
ID string
Type string // paragraph, character, table, numbering
Name string
BasedOn string
Next string
Link string
Default bool
CustomStyle bool
ParagraphProperties *ParagraphProperties
RunProperties *RunProperties
TableProperties *TableProperties
}
// NewStyles 创建一个新的样式集合
func NewStyles() *Styles {
return &Styles{
Styles: make([]*Style, 0),
}
}
// AddStyle 添加一个样式
func (s *Styles) AddStyle(id, name, styleType string) *Style {
style := &Style{
ID: id,
Type: styleType,
Name: name,
CustomStyle: true,
ParagraphProperties: &ParagraphProperties{},
RunProperties: &RunProperties{},
}
s.Styles = append(s.Styles, style)
return style
}
// GetStyle 获取指定ID的样式
func (s *Styles) GetStyle(id string) *Style {
for _, style := range s.Styles {
if style.ID == id {
return style
}
}
return nil
}
// SetBasedOn 设置样式的基础样式
func (s *Style) SetBasedOn(basedOn string) *Style {
s.BasedOn = basedOn
return s
}
// SetNext 设置样式的下一个样式
func (s *Style) SetNext(next string) *Style {
s.Next = next
return s
}
// SetLink 设置样式的链接
func (s *Style) SetLink(link string) *Style {
s.Link = link
return s
}
// SetDefault 设置样式是否为默认样式
func (s *Style) SetDefault(isDefault bool) *Style {
s.Default = isDefault
return s
}
// SetParagraphProperties 设置段落属性
func (s *Style) SetParagraphProperties(props *ParagraphProperties) *Style {
s.ParagraphProperties = props
return s
}
// SetRunProperties 设置文本运行属性
func (s *Style) SetRunProperties(props *RunProperties) *Style {
s.RunProperties = props
return s
}
// SetTableProperties 设置表格属性
func (s *Style) SetTableProperties(props *TableProperties) *Style {
s.TableProperties = props
return s
}
// ToXML 将样式集合转换为XML
func (s *Styles) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<w:styles xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
// 添加默认样式
xml += "<w:docDefaults>"
xml += "<w:rPrDefault>"
xml += "<w:rPr>"
xml += "<w:rFonts w:ascii=\"Calibri\" w:eastAsia=\"Calibri\" w:hAnsi=\"Calibri\" w:cs=\"Calibri\" />"
xml += "<w:sz w:val=\"22\" />"
xml += "<w:szCs w:val=\"22\" />"
xml += "<w:lang w:val=\"en-US\" w:eastAsia=\"en-US\" w:bidi=\"ar-SA\" />"
xml += "</w:rPr>"
xml += "</w:rPrDefault>"
xml += "<w:pPrDefault>"
xml += "<w:pPr>"
xml += "<w:spacing w:after=\"200\" w:line=\"276\" w:lineRule=\"auto\" />"
xml += "</w:pPr>"
xml += "</w:pPrDefault>"
xml += "</w:docDefaults>"
// 添加所有样式
for _, style := range s.Styles {
xml += "<w:style w:type=\"" + style.Type + "\" w:styleId=\"" + style.ID + "\">"
// 样式名称
xml += "<w:name w:val=\"" + style.Name + "\" />"
// 基础样式
if style.BasedOn != "" {
xml += "<w:basedOn w:val=\"" + style.BasedOn + "\" />"
}
// 下一个样式
if style.Next != "" {
xml += "<w:next w:val=\"" + style.Next + "\" />"
}
// 链接
if style.Link != "" {
xml += "<w:link w:val=\"" + style.Link + "\" />"
}
// 默认样式
if style.Default {
xml += "<w:qFormat />"
}
// 自定义样式
if style.CustomStyle {
xml += "<w:customStyle w:val=\"1\" />"
}
// 段落属性
if style.ParagraphProperties != nil && style.Type == "paragraph" {
xml += "<w:pPr>"
// 对齐方式
if style.ParagraphProperties.Alignment != "" {
xml += "<w:jc w:val=\"" + style.ParagraphProperties.Alignment + "\" />"
}
// 缩进
if style.ParagraphProperties.IndentLeft > 0 || style.ParagraphProperties.IndentRight > 0 || style.ParagraphProperties.IndentFirstLine > 0 {
xml += "<w:ind"
if style.ParagraphProperties.IndentLeft > 0 {
xml += " w:left=\"" + fmt.Sprintf("%d", style.ParagraphProperties.IndentLeft) + "\""
}
if style.ParagraphProperties.IndentRight > 0 {
xml += " w:right=\"" + fmt.Sprintf("%d", style.ParagraphProperties.IndentRight) + "\""
}
if style.ParagraphProperties.IndentFirstLine > 0 {
xml += " w:firstLine=\"" + fmt.Sprintf("%d", style.ParagraphProperties.IndentFirstLine) + "\""
}
xml += " />"
}
// 间距
if style.ParagraphProperties.SpacingBefore > 0 || style.ParagraphProperties.SpacingAfter > 0 || style.ParagraphProperties.SpacingLine > 0 {
xml += "<w:spacing"
if style.ParagraphProperties.SpacingBefore > 0 {
xml += " w:before=\"" + fmt.Sprintf("%d", style.ParagraphProperties.SpacingBefore) + "\""
}
if style.ParagraphProperties.SpacingAfter > 0 {
xml += " w:after=\"" + fmt.Sprintf("%d", style.ParagraphProperties.SpacingAfter) + "\""
}
if style.ParagraphProperties.SpacingLine > 0 {
xml += " w:line=\"" + fmt.Sprintf("%d", style.ParagraphProperties.SpacingLine) + "\""
xml += " w:lineRule=\"" + style.ParagraphProperties.SpacingLineRule + "\""
}
xml += " />"
}
// 分页控制
if style.ParagraphProperties.KeepNext {
xml += "<w:keepNext />"
}
if style.ParagraphProperties.KeepLines {
xml += "<w:keepLines />"
}
if style.ParagraphProperties.PageBreakBefore {
xml += "<w:pageBreakBefore />"
}
if style.ParagraphProperties.WidowControl {
xml += "<w:widowControl />"
}
// 边框
if style.ParagraphProperties.BorderTop != nil || style.ParagraphProperties.BorderBottom != nil ||
style.ParagraphProperties.BorderLeft != nil || style.ParagraphProperties.BorderRight != nil {
xml += "<w:pBdr>"
if style.ParagraphProperties.BorderTop != nil {
xml += "<w:top w:val=\"" + style.ParagraphProperties.BorderTop.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderTop.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderTop.Space) + "\""
xml += " w:color=\"" + style.ParagraphProperties.BorderTop.Color + "\" />"
}
if style.ParagraphProperties.BorderBottom != nil {
xml += "<w:bottom w:val=\"" + style.ParagraphProperties.BorderBottom.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderBottom.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderBottom.Space) + "\""
xml += " w:color=\"" + style.ParagraphProperties.BorderBottom.Color + "\" />"
}
if style.ParagraphProperties.BorderLeft != nil {
xml += "<w:left w:val=\"" + style.ParagraphProperties.BorderLeft.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderLeft.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderLeft.Space) + "\""
xml += " w:color=\"" + style.ParagraphProperties.BorderLeft.Color + "\" />"
}
if style.ParagraphProperties.BorderRight != nil {
xml += "<w:right w:val=\"" + style.ParagraphProperties.BorderRight.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderRight.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.ParagraphProperties.BorderRight.Space) + "\""
xml += " w:color=\"" + style.ParagraphProperties.BorderRight.Color + "\" />"
}
xml += "</w:pBdr>"
}
// 底纹
if style.ParagraphProperties.Shading != nil {
xml += "<w:shd w:val=\"" + style.ParagraphProperties.Shading.Pattern + "\""
xml += " w:fill=\"" + style.ParagraphProperties.Shading.Fill + "\""
xml += " w:color=\"" + style.ParagraphProperties.Shading.Color + "\" />"
}
xml += "</w:pPr>"
}
// 文本运行属性
if style.RunProperties != nil {
xml += "<w:rPr>"
// 字体
if style.RunProperties.FontFamily != "" {
xml += "<w:rFonts w:ascii=\"" + style.RunProperties.FontFamily + "\""
xml += " w:eastAsia=\"" + style.RunProperties.FontFamily + "\""
xml += " w:hAnsi=\"" + style.RunProperties.FontFamily + "\""
xml += " w:cs=\"" + style.RunProperties.FontFamily + "\" />"
}
// 字号
if style.RunProperties.FontSize > 0 {
xml += "<w:sz w:val=\"" + fmt.Sprintf("%d", style.RunProperties.FontSize) + "\" />"
xml += "<w:szCs w:val=\"" + fmt.Sprintf("%d", style.RunProperties.FontSize) + "\" />"
}
// 颜色
if style.RunProperties.Color != "" {
xml += "<w:color w:val=\"" + style.RunProperties.Color + "\" />"
}
// 粗体
if style.RunProperties.Bold {
xml += "<w:b />"
xml += "<w:bCs />"
}
// 斜体
if style.RunProperties.Italic {
xml += "<w:i />"
xml += "<w:iCs />"
}
// 下划线
if style.RunProperties.Underline != "" {
xml += "<w:u w:val=\"" + style.RunProperties.Underline + "\" />"
}
// 删除线
if style.RunProperties.Strike {
xml += "<w:strike />"
}
// 双删除线
if style.RunProperties.DoubleStrike {
xml += "<w:dstrike />"
}
// 突出显示颜色
if style.RunProperties.Highlight != "" {
xml += "<w:highlight w:val=\"" + style.RunProperties.Highlight + "\" />"
}
// 全部大写
if style.RunProperties.Caps {
xml += "<w:caps />"
}
// 小型大写
if style.RunProperties.SmallCaps {
xml += "<w:smallCaps />"
}
// 字符间距
if style.RunProperties.CharacterSpacing != 0 {
xml += "<w:spacing w:val=\"" + fmt.Sprintf("%d", style.RunProperties.CharacterSpacing) + "\" />"
}
// 底纹
if style.RunProperties.Shading != nil {
xml += "<w:shd w:val=\"" + style.RunProperties.Shading.Pattern + "\""
xml += " w:fill=\"" + style.RunProperties.Shading.Fill + "\""
xml += " w:color=\"" + style.RunProperties.Shading.Color + "\" />"
}
// 垂直对齐方式
if style.RunProperties.VertAlign != "" {
xml += "<w:vertAlign w:val=\"" + style.RunProperties.VertAlign + "\" />"
}
xml += "</w:rPr>"
}
// 表格属性
if style.TableProperties != nil && style.Type == "table" {
xml += "<w:tblPr>"
// 表格宽度
if style.TableProperties.Width > 0 {
xml += "<w:tblW w:w=\"" + fmt.Sprintf("%d", style.TableProperties.Width) + "\""
xml += " w:type=\"" + style.TableProperties.WidthType + "\" />"
}
// 表格对齐方式
if style.TableProperties.Alignment != "" {
xml += "<w:jc w:val=\"" + style.TableProperties.Alignment + "\" />"
}
// 表格边框
if style.TableProperties.Borders != nil {
xml += "<w:tblBorders>"
if style.TableProperties.Borders.Top != nil {
xml += "<w:top w:val=\"" + style.TableProperties.Borders.Top.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Top.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Top.Space) + "\""
xml += " w:color=\"" + style.TableProperties.Borders.Top.Color + "\" />"
}
if style.TableProperties.Borders.Bottom != nil {
xml += "<w:bottom w:val=\"" + style.TableProperties.Borders.Bottom.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Bottom.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Bottom.Space) + "\""
xml += " w:color=\"" + style.TableProperties.Borders.Bottom.Color + "\" />"
}
if style.TableProperties.Borders.Left != nil {
xml += "<w:left w:val=\"" + style.TableProperties.Borders.Left.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Left.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Left.Space) + "\""
xml += " w:color=\"" + style.TableProperties.Borders.Left.Color + "\" />"
}
if style.TableProperties.Borders.Right != nil {
xml += "<w:right w:val=\"" + style.TableProperties.Borders.Right.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Right.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.Right.Space) + "\""
xml += " w:color=\"" + style.TableProperties.Borders.Right.Color + "\" />"
}
if style.TableProperties.Borders.InsideH != nil {
xml += "<w:insideH w:val=\"" + style.TableProperties.Borders.InsideH.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.InsideH.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.InsideH.Space) + "\""
xml += " w:color=\"" + style.TableProperties.Borders.InsideH.Color + "\" />"
}
if style.TableProperties.Borders.InsideV != nil {
xml += "<w:insideV w:val=\"" + style.TableProperties.Borders.InsideV.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.InsideV.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", style.TableProperties.Borders.InsideV.Space) + "\""
xml += " w:color=\"" + style.TableProperties.Borders.InsideV.Color + "\" />"
}
xml += "</w:tblBorders>"
}
xml += "</w:tblPr>"
}
xml += "</w:style>"
}
xml += "</w:styles>"
return xml
}

632
document/table.go Normal file
View File

@@ -0,0 +1,632 @@
package document
import (
"fmt"
)
// Table 表示Word文档中的表格
type Table struct {
Rows []*TableRow
Properties *TableProperties
}
// TableProperties 表示表格的属性
type TableProperties struct {
Width int // 表格宽度单位为twip
WidthType string // 宽度类型auto, dxa, pct
Alignment string // 对齐方式left, center, right
Indent int // 缩进
Borders *TableBorders
CellMargin *TableCellMargin
Layout string // 布局方式fixed, autofit
Look string // 表格外观
Style string // 表格样式ID
FirstRow bool // 首行特殊格式
LastRow bool // 末行特殊格式
FirstColumn bool // 首列特殊格式
LastColumn bool // 末列特殊格式
NoHBand bool // 无水平带状格式
NoVBand bool // 无垂直带状格式
}
// TableBorders 表示表格的边框
type TableBorders struct {
Top *Border
Bottom *Border
Left *Border
Right *Border
InsideH *Border
InsideV *Border
}
// TableCellMargin 表示表格单元格的边距
type TableCellMargin struct {
Top int
Bottom int
Left int
Right int
}
// TableRow 表示表格的行
type TableRow struct {
Cells []*TableCell
Properties *TableRowProperties
}
// TableRowProperties 表示表格行的属性
type TableRowProperties struct {
Height int // 行高单位为twip
HeightRule string // 行高规则atLeast, exact, auto
CantSplit bool // 不允许跨页分割
IsHeader bool // 是否为表头行
}
// TableCell 表示表格的单元格
type TableCell struct {
Content []interface{} // 可以是段落、表格等元素
Properties *TableCellProperties
}
// TableCellProperties 表示表格单元格的属性
type TableCellProperties struct {
Width int // 单元格宽度单位为twip
WidthType string // 宽度类型auto, dxa, pct
VertAlign string // 垂直对齐方式top, center, bottom
Borders *TableBorders
Shading *Shading
GridSpan int // 跨列数
VMerge string // 垂直合并restart, continue
NoWrap bool // 不换行
FitText bool // 适应文本
}
// NewTable 创建一个新的表格
func NewTable(rows, cols int) *Table {
t := &Table{
Rows: make([]*TableRow, 0),
Properties: &TableProperties{
Width: 0,
WidthType: "auto",
Alignment: "left",
Layout: "autofit",
Borders: &TableBorders{
Top: &Border{Style: "single", Size: 4, Color: "000000", Space: 0},
Bottom: &Border{Style: "single", Size: 4, Color: "000000", Space: 0},
Left: &Border{Style: "single", Size: 4, Color: "000000", Space: 0},
Right: &Border{Style: "single", Size: 4, Color: "000000", Space: 0},
InsideH: &Border{Style: "single", Size: 4, Color: "000000", Space: 0},
InsideV: &Border{Style: "single", Size: 4, Color: "000000", Space: 0},
},
CellMargin: &TableCellMargin{
Top: 0,
Bottom: 0,
Left: 108, // 约0.15厘米
Right: 108,
},
},
}
// 创建行和单元格
for i := 0; i < rows; i++ {
row := t.AddRow()
for j := 0; j < cols; j++ {
row.AddCell()
}
}
return t
}
// AddRow 向表格添加一行并返回它
func (t *Table) AddRow() *TableRow {
r := &TableRow{
Cells: make([]*TableCell, 0),
Properties: &TableRowProperties{
Height: 0,
HeightRule: "auto",
CantSplit: false,
IsHeader: false,
},
}
t.Rows = append(t.Rows, r)
return r
}
// SetWidth 设置表格宽度
func (t *Table) SetWidth(width int, widthType string) *Table {
t.Properties.Width = width
t.Properties.WidthType = widthType
return t
}
// SetAlignment 设置表格对齐方式
func (t *Table) SetAlignment(alignment string) *Table {
t.Properties.Alignment = alignment
return t
}
// SetIndent 设置表格缩进
func (t *Table) SetIndent(indent int) *Table {
t.Properties.Indent = indent
return t
}
// SetLayout 设置表格布局方式
func (t *Table) SetLayout(layout string) *Table {
t.Properties.Layout = layout
return t
}
// SetBorders 设置表格边框
func (t *Table) SetBorders(position string, style string, size int, color string) *Table {
border := &Border{
Style: style,
Size: size,
Color: color,
Space: 0,
}
switch position {
case "top":
t.Properties.Borders.Top = border
case "bottom":
t.Properties.Borders.Bottom = border
case "left":
t.Properties.Borders.Left = border
case "right":
t.Properties.Borders.Right = border
case "insideH":
t.Properties.Borders.InsideH = border
case "insideV":
t.Properties.Borders.InsideV = border
case "all":
t.Properties.Borders.Top = border
t.Properties.Borders.Bottom = border
t.Properties.Borders.Left = border
t.Properties.Borders.Right = border
t.Properties.Borders.InsideH = border
t.Properties.Borders.InsideV = border
}
return t
}
// SetCellMargin 设置表格单元格边距
func (t *Table) SetCellMargin(position string, margin int) *Table {
switch position {
case "top":
t.Properties.CellMargin.Top = margin
case "bottom":
t.Properties.CellMargin.Bottom = margin
case "left":
t.Properties.CellMargin.Left = margin
case "right":
t.Properties.CellMargin.Right = margin
case "all":
t.Properties.CellMargin.Top = margin
t.Properties.CellMargin.Bottom = margin
t.Properties.CellMargin.Left = margin
t.Properties.CellMargin.Right = margin
}
return t
}
// SetStyle 设置表格样式
func (t *Table) SetStyle(style string) *Table {
t.Properties.Style = style
return t
}
// SetLook 设置表格外观
func (t *Table) SetLook(firstRow, lastRow, firstColumn, lastColumn, noHBand, noVBand bool) *Table {
t.Properties.FirstRow = firstRow
t.Properties.LastRow = lastRow
t.Properties.FirstColumn = firstColumn
t.Properties.LastColumn = lastColumn
t.Properties.NoHBand = noHBand
t.Properties.NoVBand = noVBand
// 计算Look值
look := 0
if firstRow {
look |= 0x0020
}
if lastRow {
look |= 0x0040
}
if firstColumn {
look |= 0x0080
}
if lastColumn {
look |= 0x0100
}
if noHBand {
look |= 0x0200
}
if noVBand {
look |= 0x0400
}
t.Properties.Look = fmt.Sprintf("%04X", look)
return t
}
// AddCell 向表格行添加一个单元格并返回它
func (r *TableRow) AddCell() *TableCell {
c := &TableCell{
Content: make([]interface{}, 0),
Properties: &TableCellProperties{
Width: 0,
WidthType: "auto",
VertAlign: "top",
GridSpan: 1,
},
}
r.Cells = append(r.Cells, c)
return c
}
// SetHeight 设置行高
func (r *TableRow) SetHeight(height int, rule string) *TableRow {
r.Properties.Height = height
r.Properties.HeightRule = rule
return r
}
// SetCantSplit 设置不允许跨页分割
func (r *TableRow) SetCantSplit(cantSplit bool) *TableRow {
r.Properties.CantSplit = cantSplit
return r
}
// SetIsHeader 设置是否为表头行
func (r *TableRow) SetIsHeader(isHeader bool) *TableRow {
r.Properties.IsHeader = isHeader
return r
}
// AddParagraph 向单元格添加一个段落并返回它
func (c *TableCell) AddParagraph() *Paragraph {
p := NewParagraph()
c.Content = append(c.Content, p)
return p
}
// AddTable 向单元格添加一个表格并返回它
func (c *TableCell) AddTable(rows, cols int) *Table {
t := NewTable(rows, cols)
c.Content = append(c.Content, t)
return t
}
// SetWidth 设置单元格宽度
func (c *TableCell) SetWidth(width int, widthType string) *TableCell {
c.Properties.Width = width
c.Properties.WidthType = widthType
return c
}
// SetVertAlign 设置单元格垂直对齐方式
func (c *TableCell) SetVertAlign(vertAlign string) *TableCell {
c.Properties.VertAlign = vertAlign
return c
}
// SetBorders 设置单元格边框
func (c *TableCell) SetBorders(position string, style string, size int, color string) *TableCell {
if c.Properties.Borders == nil {
c.Properties.Borders = &TableBorders{}
}
border := &Border{
Style: style,
Size: size,
Color: color,
Space: 0,
}
switch position {
case "top":
c.Properties.Borders.Top = border
case "bottom":
c.Properties.Borders.Bottom = border
case "left":
c.Properties.Borders.Left = border
case "right":
c.Properties.Borders.Right = border
case "all":
c.Properties.Borders.Top = border
c.Properties.Borders.Bottom = border
c.Properties.Borders.Left = border
c.Properties.Borders.Right = border
}
return c
}
// SetShading 设置单元格底纹
func (c *TableCell) SetShading(fill, color, pattern string) *TableCell {
c.Properties.Shading = &Shading{
Fill: fill,
Color: color,
Pattern: pattern,
}
return c
}
// SetGridSpan 设置单元格跨列数
func (c *TableCell) SetGridSpan(gridSpan int) *TableCell {
c.Properties.GridSpan = gridSpan
return c
}
// SetVMerge 设置单元格垂直合并
func (c *TableCell) SetVMerge(vMerge string) *TableCell {
c.Properties.VMerge = vMerge
return c
}
// SetNoWrap 设置单元格不换行
func (c *TableCell) SetNoWrap(noWrap bool) *TableCell {
c.Properties.NoWrap = noWrap
return c
}
// SetFitText 设置单元格适应文本
func (c *TableCell) SetFitText(fitText bool) *TableCell {
c.Properties.FitText = fitText
return c
}
// ToXML 将表格转换为XML
func (t *Table) ToXML() string {
xml := "<w:tbl>"
// 添加表格属性
xml += "<w:tblPr>"
// 表格宽度
if t.Properties.Width > 0 {
xml += "<w:tblW w:w=\"" + fmt.Sprintf("%d", t.Properties.Width) + "\""
xml += " w:type=\"" + t.Properties.WidthType + "\" />"
} else {
xml += "<w:tblW w:w=\"0\" w:type=\"auto\" />"
}
// 表格对齐方式
if t.Properties.Alignment != "" {
xml += "<w:jc w:val=\"" + t.Properties.Alignment + "\" />"
}
// 表格缩进
if t.Properties.Indent > 0 {
xml += "<w:tblInd w:w=\"" + fmt.Sprintf("%d", t.Properties.Indent) + "\""
xml += " w:type=\"dxa\" />"
}
// 表格边框
if t.Properties.Borders != nil {
xml += "<w:tblBorders>"
if t.Properties.Borders.Top != nil {
xml += "<w:top w:val=\"" + t.Properties.Borders.Top.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", t.Properties.Borders.Top.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", t.Properties.Borders.Top.Space) + "\""
xml += " w:color=\"" + t.Properties.Borders.Top.Color + "\" />"
}
if t.Properties.Borders.Bottom != nil {
xml += "<w:bottom w:val=\"" + t.Properties.Borders.Bottom.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", t.Properties.Borders.Bottom.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", t.Properties.Borders.Bottom.Space) + "\""
xml += " w:color=\"" + t.Properties.Borders.Bottom.Color + "\" />"
}
if t.Properties.Borders.Left != nil {
xml += "<w:left w:val=\"" + t.Properties.Borders.Left.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", t.Properties.Borders.Left.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", t.Properties.Borders.Left.Space) + "\""
xml += " w:color=\"" + t.Properties.Borders.Left.Color + "\" />"
}
if t.Properties.Borders.Right != nil {
xml += "<w:right w:val=\"" + t.Properties.Borders.Right.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", t.Properties.Borders.Right.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", t.Properties.Borders.Right.Space) + "\""
xml += " w:color=\"" + t.Properties.Borders.Right.Color + "\" />"
}
if t.Properties.Borders.InsideH != nil {
xml += "<w:insideH w:val=\"" + t.Properties.Borders.InsideH.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", t.Properties.Borders.InsideH.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", t.Properties.Borders.InsideH.Space) + "\""
xml += " w:color=\"" + t.Properties.Borders.InsideH.Color + "\" />"
}
if t.Properties.Borders.InsideV != nil {
xml += "<w:insideV w:val=\"" + t.Properties.Borders.InsideV.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", t.Properties.Borders.InsideV.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", t.Properties.Borders.InsideV.Space) + "\""
xml += " w:color=\"" + t.Properties.Borders.InsideV.Color + "\" />"
}
xml += "</w:tblBorders>"
}
// 表格单元格边距
if t.Properties.CellMargin != nil {
xml += "<w:tblCellMar>"
if t.Properties.CellMargin.Top > 0 {
xml += "<w:top w:w=\"" + fmt.Sprintf("%d", t.Properties.CellMargin.Top) + "\""
xml += " w:type=\"dxa\" />"
}
if t.Properties.CellMargin.Bottom > 0 {
xml += "<w:bottom w:w=\"" + fmt.Sprintf("%d", t.Properties.CellMargin.Bottom) + "\""
xml += " w:type=\"dxa\" />"
}
if t.Properties.CellMargin.Left > 0 {
xml += "<w:left w:w=\"" + fmt.Sprintf("%d", t.Properties.CellMargin.Left) + "\""
xml += " w:type=\"dxa\" />"
}
if t.Properties.CellMargin.Right > 0 {
xml += "<w:right w:w=\"" + fmt.Sprintf("%d", t.Properties.CellMargin.Right) + "\""
xml += " w:type=\"dxa\" />"
}
xml += "</w:tblCellMar>"
}
// 表格布局方式
if t.Properties.Layout != "" {
xml += "<w:tblLayout w:type=\"" + t.Properties.Layout + "\" />"
}
// 表格样式
if t.Properties.Style != "" {
xml += "<w:tblStyle w:val=\"" + t.Properties.Style + "\" />"
}
// 表格外观
if t.Properties.Look != "" {
xml += "<w:tblLook w:val=\"" + t.Properties.Look + "\""
xml += " w:firstRow=\"" + fmt.Sprintf("%d", boolToInt(t.Properties.FirstRow)) + "\""
xml += " w:lastRow=\"" + fmt.Sprintf("%d", boolToInt(t.Properties.LastRow)) + "\""
xml += " w:firstColumn=\"" + fmt.Sprintf("%d", boolToInt(t.Properties.FirstColumn)) + "\""
xml += " w:lastColumn=\"" + fmt.Sprintf("%d", boolToInt(t.Properties.LastColumn)) + "\""
xml += " w:noHBand=\"" + fmt.Sprintf("%d", boolToInt(t.Properties.NoHBand)) + "\""
xml += " w:noVBand=\"" + fmt.Sprintf("%d", boolToInt(t.Properties.NoVBand)) + "\" />"
}
xml += "</w:tblPr>"
// 添加表格网格
xml += "<w:tblGrid>"
if len(t.Rows) > 0 && len(t.Rows[0].Cells) > 0 {
for i := 0; i < len(t.Rows[0].Cells); i++ {
xml += "<w:gridCol />"
}
}
xml += "</w:tblGrid>"
// 添加所有行的XML
for _, row := range t.Rows {
xml += "<w:tr>"
// 添加行属性
xml += "<w:trPr>"
// 行高
if row.Properties.Height > 0 {
xml += "<w:trHeight w:val=\"" + fmt.Sprintf("%d", row.Properties.Height) + "\""
xml += " w:hRule=\"" + row.Properties.HeightRule + "\" />"
}
// 不允许跨页分割
if row.Properties.CantSplit {
xml += "<w:cantSplit />"
}
// 表头行
if row.Properties.IsHeader {
xml += "<w:tblHeader />"
}
xml += "</w:trPr>"
// 添加所有单元格的XML
for _, cell := range row.Cells {
xml += "<w:tc>"
// 添加单元格属性
xml += "<w:tcPr>"
// 单元格宽度
if cell.Properties.Width > 0 {
xml += "<w:tcW w:w=\"" + fmt.Sprintf("%d", cell.Properties.Width) + "\""
xml += " w:type=\"" + cell.Properties.WidthType + "\" />"
} else {
xml += "<w:tcW w:w=\"0\" w:type=\"auto\" />"
}
// 垂直对齐方式
if cell.Properties.VertAlign != "" {
xml += "<w:vAlign w:val=\"" + cell.Properties.VertAlign + "\" />"
}
// 单元格边框
if cell.Properties.Borders != nil {
xml += "<w:tcBorders>"
if cell.Properties.Borders.Top != nil {
xml += "<w:top w:val=\"" + cell.Properties.Borders.Top.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Top.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Top.Space) + "\""
xml += " w:color=\"" + cell.Properties.Borders.Top.Color + "\" />"
}
if cell.Properties.Borders.Bottom != nil {
xml += "<w:bottom w:val=\"" + cell.Properties.Borders.Bottom.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Bottom.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Bottom.Space) + "\""
xml += " w:color=\"" + cell.Properties.Borders.Bottom.Color + "\" />"
}
if cell.Properties.Borders.Left != nil {
xml += "<w:left w:val=\"" + cell.Properties.Borders.Left.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Left.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Left.Space) + "\""
xml += " w:color=\"" + cell.Properties.Borders.Left.Color + "\" />"
}
if cell.Properties.Borders.Right != nil {
xml += "<w:right w:val=\"" + cell.Properties.Borders.Right.Style + "\""
xml += " w:sz=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Right.Size) + "\""
xml += " w:space=\"" + fmt.Sprintf("%d", cell.Properties.Borders.Right.Space) + "\""
xml += " w:color=\"" + cell.Properties.Borders.Right.Color + "\" />"
}
xml += "</w:tcBorders>"
}
// 底纹
if cell.Properties.Shading != nil {
xml += "<w:shd w:val=\"" + cell.Properties.Shading.Pattern + "\""
xml += " w:fill=\"" + cell.Properties.Shading.Fill + "\""
xml += " w:color=\"" + cell.Properties.Shading.Color + "\" />"
}
// 跨列数
if cell.Properties.GridSpan > 1 {
xml += "<w:gridSpan w:val=\"" + fmt.Sprintf("%d", cell.Properties.GridSpan) + "\" />"
}
// 垂直合并
if cell.Properties.VMerge != "" {
xml += "<w:vMerge w:val=\"" + cell.Properties.VMerge + "\" />"
}
// 不换行
if cell.Properties.NoWrap {
xml += "<w:noWrap />"
}
// 适应文本
if cell.Properties.FitText {
xml += "<w:fitText />"
}
xml += "</w:tcPr>"
// 添加所有内容元素的XML
for _, content := range cell.Content {
switch v := content.(type) {
case *Paragraph:
xml += v.ToXML()
case *Table:
xml += v.ToXML()
}
}
// 如果单元格没有内容,添加一个空段落
if len(cell.Content) == 0 {
xml += "<w:p><w:pPr></w:pPr></w:p>"
}
xml += "</w:tc>"
}
xml += "</w:tr>"
}
xml += "</w:tbl>"
return xml
}

164
document/theme.go Normal file
View File

@@ -0,0 +1,164 @@
package document
// Theme 表示Word文档中的主题
type Theme struct {
Name string
}
// NewTheme 创建一个新的主题
func NewTheme() *Theme {
return &Theme{
Name: "Office Theme",
}
}
// ToXML 将主题转换为XML
func (t *Theme) ToXML() string {
// 这里提供一个简化的Office主题XML
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
xml += "<a:theme xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" name=\"" + t.Name + "\">"
// 颜色方案
xml += "<a:themeElements>"
xml += "<a:clrScheme name=\"Office\">"
xml += "<a:dk1><a:sysClr val=\"windowText\" lastClr=\"000000\"/></a:dk1>"
xml += "<a:lt1><a:sysClr val=\"window\" lastClr=\"FFFFFF\"/></a:lt1>"
xml += "<a:dk2><a:srgbClr val=\"44546A\"/></a:dk2>"
xml += "<a:lt2><a:srgbClr val=\"E7E6E6\"/></a:lt2>"
xml += "<a:accent1><a:srgbClr val=\"4472C4\"/></a:accent1>"
xml += "<a:accent2><a:srgbClr val=\"ED7D31\"/></a:accent2>"
xml += "<a:accent3><a:srgbClr val=\"A5A5A5\"/></a:accent3>"
xml += "<a:accent4><a:srgbClr val=\"FFC000\"/></a:accent4>"
xml += "<a:accent5><a:srgbClr val=\"5B9BD5\"/></a:accent5>"
xml += "<a:accent6><a:srgbClr val=\"70AD47\"/></a:accent6>"
xml += "<a:hlink><a:srgbClr val=\"0563C1\"/></a:hlink>"
xml += "<a:folHlink><a:srgbClr val=\"954F72\"/></a:folHlink>"
xml += "</a:clrScheme>"
// 字体方案
xml += "<a:fontScheme name=\"Office\">"
xml += "<a:majorFont>"
xml += "<a:latin typeface=\"Calibri Light\" panose=\"020F0302020204030204\"/>"
xml += "<a:ea typeface=\"\"/>"
xml += "<a:cs typeface=\"\"/>"
xml += "<a:font script=\"Jpan\" typeface=\"游ゴシック Light\"/>"
xml += "<a:font script=\"Hang\" typeface=\"맑은 고딕\"/>"
xml += "<a:font script=\"Hans\" typeface=\"等线 Light\"/>"
xml += "<a:font script=\"Hant\" typeface=\"新細明體\"/>"
xml += "<a:font script=\"Arab\" typeface=\"Times New Roman\"/>"
xml += "<a:font script=\"Hebr\" typeface=\"Times New Roman\"/>"
xml += "<a:font script=\"Thai\" typeface=\"Angsana New\"/>"
xml += "<a:font script=\"Ethi\" typeface=\"Nyala\"/>"
xml += "<a:font script=\"Beng\" typeface=\"Vrinda\"/>"
xml += "<a:font script=\"Gujr\" typeface=\"Shruti\"/>"
xml += "<a:font script=\"Khmr\" typeface=\"MoolBoran\"/>"
xml += "<a:font script=\"Knda\" typeface=\"Tunga\"/>"
xml += "<a:font script=\"Guru\" typeface=\"Raavi\"/>"
xml += "<a:font script=\"Cans\" typeface=\"Euphemia\"/>"
xml += "<a:font script=\"Cher\" typeface=\"Plantagenet Cherokee\"/>"
xml += "<a:font script=\"Yiii\" typeface=\"Microsoft Yi Baiti\"/>"
xml += "<a:font script=\"Tibt\" typeface=\"Microsoft Himalaya\"/>"
xml += "<a:font script=\"Thaa\" typeface=\"MV Boli\"/>"
xml += "<a:font script=\"Deva\" typeface=\"Mangal\"/>"
xml += "<a:font script=\"Telu\" typeface=\"Gautami\"/>"
xml += "<a:font script=\"Taml\" typeface=\"Latha\"/>"
xml += "<a:font script=\"Syrc\" typeface=\"Estrangelo Edessa\"/>"
xml += "<a:font script=\"Orya\" typeface=\"Kalinga\"/>"
xml += "<a:font script=\"Mlym\" typeface=\"Kartika\"/>"
xml += "<a:font script=\"Laoo\" typeface=\"DokChampa\"/>"
xml += "<a:font script=\"Sinh\" typeface=\"Iskoola Pota\"/>"
xml += "<a:font script=\"Mong\" typeface=\"Mongolian Baiti\"/>"
xml += "<a:font script=\"Viet\" typeface=\"Times New Roman\"/>"
xml += "<a:font script=\"Uigh\" typeface=\"Microsoft Uighur\"/>"
xml += "<a:font script=\"Geor\" typeface=\"Sylfaen\"/>"
xml += "</a:majorFont>"
xml += "<a:minorFont>"
xml += "<a:latin typeface=\"Calibri\" panose=\"020F0502020204030204\"/>"
xml += "<a:ea typeface=\"\"/>"
xml += "<a:cs typeface=\"\"/>"
xml += "<a:font script=\"Jpan\" typeface=\"游ゴシック\"/>"
xml += "<a:font script=\"Hang\" typeface=\"맑은 고딕\"/>"
xml += "<a:font script=\"Hans\" typeface=\"等线\"/>"
xml += "<a:font script=\"Hant\" typeface=\"新細明體\"/>"
xml += "<a:font script=\"Arab\" typeface=\"Arial\"/>"
xml += "<a:font script=\"Hebr\" typeface=\"Arial\"/>"
xml += "<a:font script=\"Thai\" typeface=\"Cordia New\"/>"
xml += "<a:font script=\"Ethi\" typeface=\"Nyala\"/>"
xml += "<a:font script=\"Beng\" typeface=\"Vrinda\"/>"
xml += "<a:font script=\"Gujr\" typeface=\"Shruti\"/>"
xml += "<a:font script=\"Khmr\" typeface=\"DaunPenh\"/>"
xml += "<a:font script=\"Knda\" typeface=\"Tunga\"/>"
xml += "<a:font script=\"Guru\" typeface=\"Raavi\"/>"
xml += "<a:font script=\"Cans\" typeface=\"Euphemia\"/>"
xml += "<a:font script=\"Cher\" typeface=\"Plantagenet Cherokee\"/>"
xml += "<a:font script=\"Yiii\" typeface=\"Microsoft Yi Baiti\"/>"
xml += "<a:font script=\"Tibt\" typeface=\"Microsoft Himalaya\"/>"
xml += "<a:font script=\"Thaa\" typeface=\"MV Boli\"/>"
xml += "<a:font script=\"Deva\" typeface=\"Mangal\"/>"
xml += "<a:font script=\"Telu\" typeface=\"Gautami\"/>"
xml += "<a:font script=\"Taml\" typeface=\"Latha\"/>"
xml += "<a:font script=\"Syrc\" typeface=\"Estrangelo Edessa\"/>"
xml += "<a:font script=\"Orya\" typeface=\"Kalinga\"/>"
xml += "<a:font script=\"Mlym\" typeface=\"Kartika\"/>"
xml += "<a:font script=\"Laoo\" typeface=\"DokChampa\"/>"
xml += "<a:font script=\"Sinh\" typeface=\"Iskoola Pota\"/>"
xml += "<a:font script=\"Mong\" typeface=\"Mongolian Baiti\"/>"
xml += "<a:font script=\"Viet\" typeface=\"Arial\"/>"
xml += "<a:font script=\"Uigh\" typeface=\"Microsoft Uighur\"/>"
xml += "<a:font script=\"Geor\" typeface=\"Sylfaen\"/>"
xml += "</a:minorFont>"
xml += "</a:fontScheme>"
// 格式方案
xml += "<a:fmtScheme name=\"Office\">"
xml += "<a:fillStyleLst>"
xml += "<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>"
xml += "<a:gradFill rotWithShape=\"1\">"
xml += "<a:gsLst>"
xml += "<a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"110000\"/><a:satMod val=\"105000\"/><a:tint val=\"67000\"/></a:schemeClr></a:gs>"
xml += "<a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"105000\"/><a:satMod val=\"103000\"/><a:tint val=\"73000\"/></a:schemeClr></a:gs>"
xml += "<a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"105000\"/><a:satMod val=\"109000\"/><a:tint val=\"81000\"/></a:schemeClr></a:gs>"
xml += "</a:gsLst>"
xml += "<a:lin ang=\"5400000\" scaled=\"0\"/>"
xml += "</a:gradFill>"
xml += "<a:gradFill rotWithShape=\"1\">"
xml += "<a:gsLst>"
xml += "<a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:satMod val=\"103000\"/><a:lumMod val=\"102000\"/><a:tint val=\"94000\"/></a:schemeClr></a:gs>"
xml += "<a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:satMod val=\"110000\"/><a:lumMod val=\"100000\"/><a:shade val=\"100000\"/></a:schemeClr></a:gs>"
xml += "<a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"99000\"/><a:satMod val=\"120000\"/><a:shade val=\"78000\"/></a:schemeClr></a:gs>"
xml += "</a:gsLst>"
xml += "<a:lin ang=\"5400000\" scaled=\"0\"/>"
xml += "</a:gradFill>"
xml += "</a:fillStyleLst>"
xml += "<a:lnStyleLst>"
xml += "<a:ln w=\"6350\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln>"
xml += "<a:ln w=\"12700\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln>"
xml += "<a:ln w=\"19050\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln>"
xml += "</a:lnStyleLst>"
xml += "<a:effectStyleLst>"
xml += "<a:effectStyle><a:effectLst/></a:effectStyle>"
xml += "<a:effectStyle><a:effectLst/></a:effectStyle>"
xml += "<a:effectStyle><a:effectLst><a:outerShdw blurRad=\"57150\" dist=\"19050\" dir=\"5400000\" algn=\"ctr\" rotWithShape=\"0\"><a:srgbClr val=\"000000\"><a:alpha val=\"63000\"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle>"
xml += "</a:effectStyleLst>"
xml += "<a:bgFillStyleLst>"
xml += "<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>"
xml += "<a:solidFill><a:schemeClr val=\"phClr\"><a:tint val=\"95000\"/><a:satMod val=\"170000\"/></a:schemeClr></a:solidFill>"
xml += "<a:gradFill rotWithShape=\"1\">"
xml += "<a:gsLst>"
xml += "<a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:tint val=\"93000\"/><a:satMod val=\"150000\"/><a:shade val=\"98000\"/><a:lumMod val=\"102000\"/></a:schemeClr></a:gs>"
xml += "<a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:tint val=\"98000\"/><a:satMod val=\"130000\"/><a:shade val=\"90000\"/><a:lumMod val=\"103000\"/></a:schemeClr></a:gs>"
xml += "<a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:shade val=\"63000\"/><a:satMod val=\"120000\"/></a:schemeClr></a:gs>"
xml += "</a:gsLst>"
xml += "<a:lin ang=\"5400000\" scaled=\"0\"/>"
xml += "</a:gradFill>"
xml += "</a:bgFillStyleLst>"
xml += "</a:fmtScheme>"
xml += "</a:themeElements>"
// 其他主题元素
xml += "<a:objectDefaults/>"
xml += "<a:extraClrSchemeLst/>"
xml += "</a:theme>"
return xml
}

63
document/utils.go Normal file
View File

@@ -0,0 +1,63 @@
package document
import (
"fmt"
"math/rand"
"time"
)
// 初始化随机数生成器
func init() {
rand.New(rand.NewSource(time.Now().UnixNano()))
}
// generateUniqueID 生成一个唯一的ID
func generateUniqueID() string {
return fmt.Sprintf("%d", rand.Intn(1000000))
}
// boolToInt 将布尔值转换为整数
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
// boolToString 将布尔值转换为字符串
func boolToString(b bool) string {
if b {
return "1"
}
return "0"
}
// twipToCm 将twip转换为厘米
func twipToCm(twip int) float64 {
return float64(twip) / 1440.0
}
// cmToTwip 将厘米转换为twip
func cmToTwip(cm float64) int {
return int(cm * 1440.0)
}
// pointToTwip 将磅转换为twip
func pointToTwip(point float64) int {
return int(point * 20.0)
}
// twipToPoint 将twip转换为磅
func twipToPoint(twip int) float64 {
return float64(twip) / 20.0
}
// emuToPx 将EMU(English Metric Unit)转换为像素
func emuToPx(emu int) int {
return emu / 9525
}
// pxToEmu 将像素转换为EMU
func pxToEmu(px int) int {
return px * 9525
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/landaiqing/go-dockit
go 1.24.2

92
workbook/content_types.go Normal file
View File

@@ -0,0 +1,92 @@
package workbook
import (
"fmt"
)
// ContentTypes 表示Excel文档中的内容类型集合
type ContentTypes struct {
Defaults []*Default
Overrides []*Override
}
// Default 表示默认的内容类型
type Default struct {
Extension string
ContentType string
}
// Override 表示覆盖的内容类型
type Override struct {
PartName string
ContentType string
}
// NewContentTypes 创建一个新的内容类型集合
func NewContentTypes() *ContentTypes {
ct := &ContentTypes{
Defaults: make([]*Default, 0),
Overrides: make([]*Override, 0),
}
// 添加默认的内容类型
ct.AddDefault("xml", "application/xml")
ct.AddDefault("rels", "application/vnd.openxmlformats-package.relationships+xml")
ct.AddDefault("png", "image/png")
ct.AddDefault("jpeg", "image/jpeg")
ct.AddDefault("jpg", "image/jpeg")
// 添加覆盖的内容类型
ct.AddOverride("/xl/workbook.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml")
ct.AddOverride("/xl/styles.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml")
ct.AddOverride("/xl/theme/theme1.xml", "application/vnd.openxmlformats-officedocument.theme+xml")
ct.AddOverride("/xl/worksheets/sheet1.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml")
ct.AddOverride("/xl/sharedStrings.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml")
return ct
}
// AddDefault 添加一个默认的内容类型
func (ct *ContentTypes) AddDefault(extension, contentType string) *Default {
def := &Default{
Extension: extension,
ContentType: contentType,
}
ct.Defaults = append(ct.Defaults, def)
return def
}
// AddOverride 添加一个覆盖的内容类型
func (ct *ContentTypes) AddOverride(partName, contentType string) *Override {
ovr := &Override{
PartName: partName,
ContentType: contentType,
}
ct.Overrides = append(ct.Overrides, ovr)
return ovr
}
// AddWorksheetOverride 添加工作表的内容类型覆盖
func (ct *ContentTypes) AddWorksheetOverride(index int) *Override {
partName := fmt.Sprintf("/xl/worksheets/sheet%d.xml", index)
return ct.AddOverride(partName, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml")
}
// ToXML 将内容类型转换为XML
func (ct *ContentTypes) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
xml += "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n"
// 添加默认的内容类型
for _, def := range ct.Defaults {
xml += fmt.Sprintf(" <Default Extension=\"%s\" ContentType=\"%s\"/>\n", def.Extension, def.ContentType)
}
// 添加覆盖的内容类型
for _, ovr := range ct.Overrides {
xml += fmt.Sprintf(" <Override PartName=\"%s\" ContentType=\"%s\"/>\n", ovr.PartName, ovr.ContentType)
}
xml += "</Types>"
return xml
}

View File

@@ -0,0 +1,269 @@
package main
import (
"fmt"
"github.com/landaiqing/go-dockit/workbook"
"time"
)
func main() {
// 创建一个新的Excel工作簿
wb := workbook.NewWorkbook()
// 设置工作簿属性
wb.Properties.Title = "示例Excel文档"
wb.Properties.Creator = "Go-DocKit"
wb.Properties.Created = time.Now()
// 添加一个工作表
ws := wb.AddWorksheet("数据报表")
// 设置列宽
ws.AddColumn(1, 1, 10) // A列
ws.AddColumn(2, 2, 20) // B列
ws.AddColumn(3, 3, 15) // C列
ws.AddColumn(4, 4, 15) // D列
ws.AddColumn(5, 5, 20) // E列
ws.AddColumn(6, 6, 15) // F列 - 百分比列
ws.AddColumn(7, 7, 15) // G列 - 科学计数列
// 创建标题样式
headerStyleID := wb.Styles.CreateStyle(
"Arial", 12, true, false, false, "FF000000", // 字体
"solid", "FFD3D3D3", // 填充
"thin", "FF000000", // 边框
"", // 数字格式
"center", "center", false, // 对齐
)
// 创建日期格式样式 - 使用标准的Excel内置格式
dateStyleID := wb.Styles.CreateStyle(
"", 0, false, false, false, "", // 字体
"", "", // 填充
"thin", "FF000000", // 边框
"[$-804]yyyy\"年\"mm\"月\"dd\"日\"", // 中文日期格式
"center", "bottom", false, // 对齐
)
// 创建人民币货币格式样式
currencyStyleID := wb.Styles.CreateStyle(
"", 0, false, false, false, "", // 字体
"", "", // 填充
"thin", "FF000000", // 边框
"¥#,##0.00", // 人民币货币格式,不使用引号
"right", "bottom", false, // 对齐
)
// 创建百分比格式样式
percentStyleID := wb.Styles.CreateStyle(
"", 0, false, false, false, "", // 字体
"", "", // 填充
"thin", "FF000000", // 边框
"0.00%", // 百分比格式
"right", "bottom", false, // 对齐
)
// 创建科学计数格式样式 - 使用正确的内置格式
scientificStyleID := wb.Styles.CreateStyle(
"", 0, false, false, false, "", // 字体
"", "", // 填充
"thin", "FF000000", // 边框
"0.00E+00", // 科学计数格式
"right", "bottom", false, // 对齐
)
// 添加标题行
headers := []string{"编号", "产品名称", "单价(¥)", "数量", "日期", "利润率", "密度"}
for i, header := range headers {
cellRef := workbook.CellRef(0, i)
_ = ws.AddCell(cellRef, header)
// 设置标题单元格样式
_ = ws.SetCellStyle(cellRef, &workbook.CellStyle{
FontID: headerStyleID,
BorderID: 0,
Alignment: &workbook.Alignment{
Horizontal: "center",
Vertical: "center",
},
})
}
// 添加数据行
data := [][]interface{}{
{1, "笔记本电脑", 5999.99, 10, time.Now(), 0.15, 2500000},
{2, "智能手机", 3999.99, 20, time.Now().AddDate(0, 0, -5), 0.25, 1500000},
{3, "平板电脑", 2999.99, 15, time.Now().AddDate(0, 0, -10), 0.20, 500000},
{4, "智能手表", 1999.99, 30, time.Now().AddDate(0, 0, -15), 0.30, 80000},
{5, "无线耳机", 999.99, 50, time.Now().AddDate(0, 0, -20), 0.40, 5000},
}
// 添加数据
for rowIdx, rowData := range data {
row := ws.AddRow()
row.Height = 18
for colIdx, cellData := range rowData {
cellRef := workbook.CellRef(rowIdx+1, colIdx)
_ = ws.AddCell(cellRef, cellData)
// 根据列类型设置不同的样式
switch colIdx {
case 0: // 编号列
_ = ws.SetCellStyle(cellRef, &workbook.CellStyle{
Alignment: &workbook.Alignment{
Horizontal: "center",
},
})
case 2: // 单价列 - 使用人民币格式
_ = ws.SetCellStyle(cellRef, &workbook.CellStyle{
NumberFormatID: currencyStyleID,
Alignment: &workbook.Alignment{
Horizontal: "right",
},
})
case 4: // 日期列
_ = ws.SetCellStyle(cellRef, &workbook.CellStyle{
NumberFormatID: dateStyleID,
Alignment: &workbook.Alignment{
Horizontal: "center",
},
})
case 5: // 利润率列 - 使用百分比格式
_ = ws.SetCellStyle(cellRef, &workbook.CellStyle{
NumberFormatID: percentStyleID,
Alignment: &workbook.Alignment{
Horizontal: "right",
},
})
case 6: // 密度列 - 使用科学计数格式
_ = ws.SetCellStyle(cellRef, &workbook.CellStyle{
NumberFormatID: scientificStyleID,
Alignment: &workbook.Alignment{
Horizontal: "right",
},
})
}
}
}
// 添加合计行
ws.AddCell("A7", "合计")
ws.SetCellFormula("C7", "SUM(C2:C6)")
ws.SetCellFormula("D7", "SUM(D2:D6)")
ws.SetCellFormula("F7", "AVERAGE(F2:F6)") // 计算平均利润率
// 设置合计行样式
ws.SetCellStyle("A7", &workbook.CellStyle{
FontID: 0,
Alignment: &workbook.Alignment{
Horizontal: "right",
},
})
ws.SetCellStyle("C7", &workbook.CellStyle{
FontID: 0,
NumberFormatID: currencyStyleID, // 使用人民币格式
Alignment: &workbook.Alignment{
Horizontal: "right",
},
})
ws.SetCellStyle("D7", &workbook.CellStyle{
FontID: 0,
Alignment: &workbook.Alignment{
Horizontal: "center",
},
})
ws.SetCellStyle("F7", &workbook.CellStyle{
FontID: 0,
NumberFormatID: percentStyleID, // 使用百分比格式
Alignment: &workbook.Alignment{
Horizontal: "right",
},
})
// 添加第二个工作表 - 用于额外的测试
testSheet := wb.AddWorksheet("格式测试")
// 设置标题样式
testSheet.AddColumn(1, 7, 15) // 统一设置列宽
// 测试表标题
testHeaders := []string{"类型", "值", "描述", "格式ID", "样式", "显示效果", "备注"}
for i, header := range testHeaders {
cellRef := workbook.CellRef(0, i)
testSheet.AddCell(cellRef, header)
testSheet.SetCellStyle(cellRef, &workbook.CellStyle{
FontID: headerStyleID,
Alignment: &workbook.Alignment{
Horizontal: "center",
},
})
}
// 1. 测试日期格式
testSheet.AddCell("A2", "日期")
testNow := time.Now()
_ = testSheet.AddCell("B2", testNow)
testSheet.AddCell("C2", "当前日期时间")
testSheet.AddCell("D2", fmt.Sprintf("%d", dateStyleID))
testSheet.AddCell("E2", "[$-804]yyyy\"年\"mm\"月\"dd\"日\"")
testSheet.AddCell("G2", "应显示为中文日期格式")
testSheet.SetCellStyle("B2", &workbook.CellStyle{
NumberFormatID: dateStyleID,
})
// 2. 测试科学计数格式 - 大数值
testSheet.AddCell("A3", "科学计数")
testSheet.AddCell("B3", 12345678.9)
testSheet.AddCell("C3", "大数值")
testSheet.AddCell("D3", fmt.Sprintf("%d", scientificStyleID))
testSheet.AddCell("E3", "0.00E+00")
testSheet.AddCell("G3", "应显示为1.23E+07")
testSheet.SetCellStyle("B3", &workbook.CellStyle{
NumberFormatID: scientificStyleID,
})
// 3. 测试科学计数格式 - 小数值
testSheet.AddCell("A4", "科学计数")
testSheet.AddCell("B4", 0.00000123)
testSheet.AddCell("C4", "小数值")
testSheet.AddCell("D4", fmt.Sprintf("%d", scientificStyleID))
testSheet.AddCell("E4", "0.00E+00")
testSheet.AddCell("G4", "应显示为1.23E-06")
testSheet.SetCellStyle("B4", &workbook.CellStyle{
NumberFormatID: scientificStyleID,
})
// 4. 测试百分比格式
testSheet.AddCell("A5", "百分比")
testSheet.AddCell("B5", 0.1234)
testSheet.AddCell("C5", "小数值")
testSheet.AddCell("D5", fmt.Sprintf("%d", percentStyleID))
testSheet.AddCell("E5", "0.00%")
testSheet.AddCell("G5", "应显示为12.34%")
testSheet.SetCellStyle("B5", &workbook.CellStyle{
NumberFormatID: percentStyleID,
})
// 合并单元格示例
ws.MergeCells("A9", "G9")
ws.AddCell("A9", "销售数据分析报表")
// 设置合并单元格的样式
ws.SetCellStyle("A9", &workbook.CellStyle{
FontID: 0,
Alignment: &workbook.Alignment{
Horizontal: "center",
Vertical: "center",
},
})
// 保存Excel文件
err := wb.Save("sales_report.xlsx")
if err != nil {
fmt.Println("保存Excel文件时出错:", err)
return
}
fmt.Println("Excel文件已成功创建: sales_report.xlsx")
}

71
workbook/relationships.go Normal file
View File

@@ -0,0 +1,71 @@
package workbook
// Relationships 表示Excel文档中的关系集合
type Relationships struct {
Relationships []*Relationship
}
// Relationship 表示Excel文档中的关系
type Relationship struct {
ID string
Type string
Target string
TargetMode string // 目标模式Internal, External
}
// NewRelationships 创建一个新的关系集合
func NewRelationships() *Relationships {
return &Relationships{
Relationships: make([]*Relationship, 0),
}
}
// AddRelationship 添加一个关系
func (r *Relationships) AddRelationship(id, relType, target string) *Relationship {
rel := &Relationship{
ID: id,
Type: relType,
Target: target,
}
r.Relationships = append(r.Relationships, rel)
return rel
}
// AddExternalRelationship 添加一个外部关系
func (r *Relationships) AddExternalRelationship(id, relType, target string) *Relationship {
rel := &Relationship{
ID: id,
Type: relType,
Target: target,
TargetMode: "External",
}
r.Relationships = append(r.Relationships, rel)
return rel
}
// GetRelationshipByID 根据ID获取关系
func (r *Relationships) GetRelationshipByID(id string) *Relationship {
for _, rel := range r.Relationships {
if rel.ID == id {
return rel
}
}
return nil
}
// ToXML 将关系转换为XML
func (r *Relationships) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
xml += "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n"
for _, rel := range r.Relationships {
if rel.TargetMode == "External" {
xml += " <Relationship Id=\"" + rel.ID + "\" Type=\"" + rel.Type + "\" Target=\"" + rel.Target + "\" TargetMode=\"External\"/>\n"
} else {
xml += " <Relationship Id=\"" + rel.ID + "\" Type=\"" + rel.Type + "\" Target=\"" + rel.Target + "\"/>\n"
}
}
xml += "</Relationships>"
return xml
}

View File

@@ -0,0 +1,68 @@
package workbook
import (
"fmt"
"strings"
)
// SharedStrings 表示Excel文档中的共享字符串表
type SharedStrings struct {
Strings []string
Map map[string]int
}
// NewSharedStrings 创建一个新的共享字符串表
func NewSharedStrings() *SharedStrings {
return &SharedStrings{
Strings: make([]string, 0),
Map: make(map[string]int),
}
}
// AddString 添加一个字符串到共享字符串表,并返回其索引
func (ss *SharedStrings) AddString(s string) int {
// 检查字符串是否已存在
if index, ok := ss.Map[s]; ok {
return index
}
// 添加新字符串
index := len(ss.Strings)
ss.Strings = append(ss.Strings, s)
ss.Map[s] = index
return index
}
// GetString 根据索引获取字符串
func (ss *SharedStrings) GetString(index int) (string, error) {
if index < 0 || index >= len(ss.Strings) {
return "", fmt.Errorf("index out of range: %d", index)
}
return ss.Strings[index], nil
}
// Count 返回共享字符串表中的字符串数量
func (ss *SharedStrings) Count() int {
return len(ss.Strings)
}
// ToXML 将共享字符串表转换为XML
func (ss *SharedStrings) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
xml += fmt.Sprintf("<sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" count=\"%d\" uniqueCount=\"%d\">\n",
ss.Count(), ss.Count())
for _, s := range ss.Strings {
// 转义XML特殊字符
s = strings.Replace(s, "&", "&amp;", -1)
s = strings.Replace(s, "<", "&lt;", -1)
s = strings.Replace(s, ">", "&gt;", -1)
s = strings.Replace(s, "\"", "&quot;", -1)
s = strings.Replace(s, "'", "&apos;", -1)
xml += " <si><t>" + s + "</t></si>\n"
}
xml += "</sst>"
return xml
}

604
workbook/styles.go Normal file
View File

@@ -0,0 +1,604 @@
package workbook
import (
"fmt"
"strings"
)
// Styles 表示Excel文档中的样式集合
type Styles struct {
Fonts []*Font
Fills []*Fill
Borders []*Border
NumberFormats []*NumberFormat
CellStyles []*CellStyleDef
CellStyleXfs []*CellStyleXf
CellXfs []*CellXf
}
// NewStyles 创建一个新的样式集合
func NewStyles() *Styles {
s := &Styles{
Fonts: make([]*Font, 0),
Fills: make([]*Fill, 0),
Borders: make([]*Border, 0),
NumberFormats: make([]*NumberFormat, 0),
CellStyles: make([]*CellStyleDef, 0),
CellStyleXfs: make([]*CellStyleXf, 0),
CellXfs: make([]*CellXf, 0),
}
// 添加默认字体
s.AddFont("Calibri", 11, false, false, false, "")
// 添加默认填充
s.AddFill("none", "")
s.AddFill("gray125", "")
// 添加默认边框
s.AddBorder()
// 添加默认数字格式
s.AddNumberFormat(0, "General")
// 添加默认单元格样式XF
s.AddCellStyleXf(0, 0, 0, 0, nil)
// 添加默认单元格样式
// 第二个参数是XF ID指向已创建的CellStyleXfs中的索引
s.AddCellStyle("Normal", 0, 0)
// 添加默认单元格XF
s.AddCellXf(0, 0, 0, 0, nil)
return s
}
// Font 表示字体
type Font struct {
Name string
Size float64
Bold bool
Italic bool
Underline bool
Color string
}
// AddFont 添加字体
func (s *Styles) AddFont(name string, size float64, bold, italic, underline bool, color string) *Font {
font := &Font{
Name: name,
Size: size,
Bold: bold,
Italic: italic,
Underline: underline,
Color: color,
}
s.Fonts = append(s.Fonts, font)
return font
}
// Fill 表示填充
type Fill struct {
PatternType string
FgColor string
BgColor string
}
// AddFill 添加填充
func (s *Styles) AddFill(patternType, fgColor string) *Fill {
fill := &Fill{
PatternType: patternType,
FgColor: fgColor,
}
s.Fills = append(s.Fills, fill)
return fill
}
// Border 表示边框
type Border struct {
Left *BorderStyle
Right *BorderStyle
Top *BorderStyle
Bottom *BorderStyle
}
// BorderStyle 表示边框样式
type BorderStyle struct {
Style string
Color string
}
// AddBorder 添加边框
func (s *Styles) AddBorder() *Border {
border := &Border{
Left: &BorderStyle{},
Right: &BorderStyle{},
Top: &BorderStyle{},
Bottom: &BorderStyle{},
}
s.Borders = append(s.Borders, border)
return border
}
// NumberFormat 表示数字格式
type NumberFormat struct {
ID int
Code string
}
// AddNumberFormat 添加数字格式
func (s *Styles) AddNumberFormat(id int, code string) *NumberFormat {
nf := &NumberFormat{
ID: id,
Code: code,
}
s.NumberFormats = append(s.NumberFormats, nf)
return nf
}
// CellStyleDef 表示单元格样式定义
type CellStyleDef struct {
Name string
XfId int
BuiltinId int
CustomBuiltin bool
}
// AddCellStyle 添加单元格样式
func (s *Styles) AddCellStyle(name string, xfId int, builtinId int) *CellStyleDef {
cs := &CellStyleDef{
Name: name,
XfId: xfId,
BuiltinId: builtinId,
}
s.CellStyles = append(s.CellStyles, cs)
return cs
}
// CellStyleXf 表示单元格样式XF
type CellStyleXf struct {
FontId int
FillId int
BorderId int
NumFmtId int
Alignment *Alignment
ApplyFont bool
ApplyFill bool
ApplyBorder bool
ApplyNumberFormat bool
ApplyAlignment bool
}
// AddCellStyleXf 添加单元格样式XF
func (s *Styles) AddCellStyleXf(fontId, fillId, borderId, numFmtId int, alignment *Alignment) *CellStyleXf {
csx := &CellStyleXf{
FontId: fontId,
FillId: fillId,
BorderId: borderId,
NumFmtId: numFmtId,
Alignment: alignment,
ApplyFont: fontId > 0,
ApplyFill: fillId > 0,
ApplyBorder: borderId > 0,
ApplyNumberFormat: numFmtId > 0,
ApplyAlignment: alignment != nil,
}
s.CellStyleXfs = append(s.CellStyleXfs, csx)
return csx
}
// CellXf 表示单元格XF
type CellXf struct {
FontId int
FillId int
BorderId int
NumFmtId int
Alignment *Alignment
ApplyFont bool
ApplyFill bool
ApplyBorder bool
ApplyNumberFormat bool
ApplyAlignment bool
}
// AddCellXf 添加单元格XF
func (s *Styles) AddCellXf(fontId, fillId, borderId, numFmtId int, alignment *Alignment) int {
cx := &CellXf{
FontId: fontId,
FillId: fillId,
BorderId: borderId,
NumFmtId: numFmtId,
Alignment: alignment,
ApplyFont: fontId > 0,
ApplyFill: fillId > 0,
ApplyBorder: borderId > 0,
ApplyNumberFormat: numFmtId > 0,
ApplyAlignment: alignment != nil,
}
s.CellXfs = append(s.CellXfs, cx)
return len(s.CellXfs) - 1
}
// CreateStyle 创建一个完整的单元格样式并返回样式ID
func (s *Styles) CreateStyle(fontName string, fontSize float64, bold, italic, underline bool, fontColor string,
fillPattern, fillColor string, borderStyle string, borderColor string, numFmtCode string,
hAlign, vAlign string, wrapText bool) int {
// 添加字体
fontId := 0
if fontName != "" || fontSize > 0 || bold || italic || underline || fontColor != "" {
s.AddFont(fontName, fontSize, bold, italic, underline, fontColor)
fontId = len(s.Fonts) - 1
}
// 添加填充
fillId := 0
if fillPattern != "" {
s.AddFill(fillPattern, fillColor)
fillId = len(s.Fills) - 1
}
// 添加边框
borderId := 0
if borderStyle != "" {
border := s.AddBorder()
border.Left.Style = borderStyle
border.Left.Color = borderColor
border.Right.Style = borderStyle
border.Right.Color = borderColor
border.Top.Style = borderStyle
border.Top.Color = borderColor
border.Bottom.Style = borderStyle
border.Bottom.Color = borderColor
borderId = len(s.Borders) - 1
}
// 添加数字格式 - 改进
numFmtId := 0
if numFmtCode != "" {
// 处理货币格式中的引号问题
if strings.Contains(numFmtCode, "\"¥\"") {
// 人民币格式使用编号7
numFmtId = 7
} else if numFmtCode == "0.00E+00" || numFmtCode == "##0.0E+0" {
// 使用Excel内置的科学计数格式ID
numFmtId = 11
} else {
// 检查是否是其他内置格式
builtinId := getBuiltinNumberFormatId(numFmtCode)
if builtinId > 0 {
numFmtId = builtinId
} else {
// 如果不是内置格式,创建自定义格式
// Excel自定义格式从164开始
numFmtId = 164 + len(s.NumberFormats)
s.AddNumberFormat(numFmtId, numFmtCode)
}
}
}
var alignment *Alignment
if hAlign != "" || vAlign != "" || wrapText {
alignment = &Alignment{
Horizontal: hAlign,
Vertical: vAlign,
WrapText: wrapText,
}
}
// 创建单元格XF并返回其索引
styleIndex := s.AddCellXf(fontId, fillId, borderId, numFmtId, alignment)
return styleIndex
}
// 获取内置数字格式ID
func getBuiltinNumberFormatId(format string) int {
builtinFormats := map[string]int{
// 常规格式
"General": 0,
// 数值格式
"0": 1, // 整数
"0.00": 2, // 小数
"#,##0": 3, // 千位分隔的整数
"#,##0.00": 4, // 千位分隔的小数
// 货币格式
"¥#,##0;¥\\-#,##0": 7, // 人民币格式
"¥#,##0;[Red]¥\\-#,##0": 8, // 人民币格式(负数为红色)
"\"¥\"#,##0.00": 7, // 人民币格式带小数 - 这个格式有问题
"$#,##0.00": 44, // 美元格式
"$#,##0.00_);($#,##0.00)": 43, // 会计专用美元格式
"_(\"$\"* #,##0.00_)": 42, // 会计专用美元格式(负数带括号)
"_-* #,##0.00_-": 4, // 会计专用格式
// 百分比格式
"0%": 9, // 整数百分比
"0.00%": 10, // 小数百分比
// 科学计数格式
"0.00E+00": 11, // 科学计数 - 确保正确提供此格式
"##0.0E+0": 11, // 科学计数 - 另一种写法
// 分数格式
"# ?/?": 12, // 分数 (例如:1/4)
"# ??/??": 13, // 分数 (例如:3/16)
// 日期格式 - 增加更多常见日期格式
"mm-dd-yy": 14, // 月-日-年
"d-mmm-yy": 15, // 日-月缩写-年
"d-mmm": 16, // 日-月缩写
"mmm-yy": 17, // 月缩写-年
"mm/dd/yy": 30, // 月/日/年
"mm/dd/yyyy": 22, // 月/日/完整年份
"yyyy/mm/dd": 20, // ISO 8601 格式
"dd/mm/yyyy": 21, // 欧洲日期格式
"yyyy-mm-dd": 22, // ISO 日期格式
"yyyy-mm-dd;@": 22, // ISO 日期格式(带文本)
"yyyy/mm/dd;@": 22, // ISO 日期格式(带文本)
"[$-804]yyyy\"年\"mm\"月\"dd\"日\"": 31, // 中文日期格式
// 时间格式
"h:mm AM/PM": 18, // 12小时制时间
"h:mm:ss AM/PM": 19, // 12小时制时间(带秒)
"h:mm": 20, // 24小时制时间
"h:mm:ss": 21, // 24小时制时间(带秒)
"m/d/yy h:mm": 22, // 日期和时间
"mm:ss": 45, // 分:秒
"[h]:mm:ss": 46, // 超过24小时的时间
"mmss.0": 47, // 分秒.毫秒
// 其他格式
"#,##0 ;(#,##0)": 37, // 带括号的负数
"#,##0 ;[Red](#,##0)": 38, // 红色负数
"#,##0.00;(#,##0.00)": 39, // 带括号的负小数
"#,##0.00;[Red](#,##0.00)": 40, // 红色负小数
"@": 49, // 文本
}
if id, ok := builtinFormats[format]; ok {
return id
}
return 0
}
// ToXML 将样式转换为XML
func (s *Styles) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
xml += "<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\n"
// 数字格式
if len(s.NumberFormats) > 0 {
xml += fmt.Sprintf(" <numFmts count=\"%d\">\n", len(s.NumberFormats))
for _, nf := range s.NumberFormats {
// 处理格式代码中的引号将其转换为XML实体引用
formatCode := nf.Code
formatCode = strings.Replace(formatCode, "\"", "&quot;", -1)
xml += fmt.Sprintf(" <numFmt numFmtId=\"%d\" formatCode=\"%s\" />\n", nf.ID, formatCode)
}
xml += " </numFmts>\n"
}
// 字体
xml += fmt.Sprintf(" <fonts count=\"%d\">\n", len(s.Fonts))
for _, font := range s.Fonts {
xml += " <font>\n"
if font.Bold {
xml += " <b />\n"
}
if font.Italic {
xml += " <i />\n"
}
if font.Underline {
xml += " <u />\n"
}
xml += fmt.Sprintf(" <sz val=\"%f\" />\n", font.Size)
if font.Color != "" {
xml += fmt.Sprintf(" <color rgb=\"%s\" />\n", font.Color)
}
xml += fmt.Sprintf(" <name val=\"%s\" />\n", font.Name)
xml += " </font>\n"
}
xml += " </fonts>\n"
// 填充
xml += fmt.Sprintf(" <fills count=\"%d\">\n", len(s.Fills))
for _, fill := range s.Fills {
xml += " <fill>\n"
xml += fmt.Sprintf(" <patternFill patternType=\"%s\">\n", fill.PatternType)
if fill.FgColor != "" {
xml += fmt.Sprintf(" <fgColor rgb=\"%s\" />\n", fill.FgColor)
}
if fill.BgColor != "" {
xml += fmt.Sprintf(" <bgColor rgb=\"%s\" />\n", fill.BgColor)
}
xml += " </patternFill>\n"
xml += " </fill>\n"
}
xml += " </fills>\n"
// 边框
xml += fmt.Sprintf(" <borders count=\"%d\">\n", len(s.Borders))
for _, border := range s.Borders {
xml += " <border>\n"
// 左边框
xml += " <left"
if border.Left.Style != "" {
xml += fmt.Sprintf(" style=\"%s\"", border.Left.Style)
}
xml += ">\n"
if border.Left.Color != "" {
xml += fmt.Sprintf(" <color rgb=\"%s\" />\n", border.Left.Color)
}
xml += " </left>\n"
// 右边框
xml += " <right"
if border.Right.Style != "" {
xml += fmt.Sprintf(" style=\"%s\"", border.Right.Style)
}
xml += ">\n"
if border.Right.Color != "" {
xml += fmt.Sprintf(" <color rgb=\"%s\" />\n", border.Right.Color)
}
xml += " </right>\n"
// 上边框
xml += " <top"
if border.Top.Style != "" {
xml += fmt.Sprintf(" style=\"%s\"", border.Top.Style)
}
xml += ">\n"
if border.Top.Color != "" {
xml += fmt.Sprintf(" <color rgb=\"%s\" />\n", border.Top.Color)
}
xml += " </top>\n"
// 下边框
xml += " <bottom"
if border.Bottom.Style != "" {
xml += fmt.Sprintf(" style=\"%s\"", border.Bottom.Style)
}
xml += ">\n"
if border.Bottom.Color != "" {
xml += fmt.Sprintf(" <color rgb=\"%s\" />\n", border.Bottom.Color)
}
xml += " </bottom>\n"
xml += " </border>\n"
}
xml += " </borders>\n"
// 单元格样式XF
xml += fmt.Sprintf(" <cellStyleXfs count=\"%d\">\n", len(s.CellStyleXfs))
for _, xf := range s.CellStyleXfs {
xml += " <xf"
if xf.FontId > 0 {
xml += fmt.Sprintf(" fontId=\"%d\" applyFont=\"1\"", xf.FontId)
}
if xf.FillId > 0 {
xml += fmt.Sprintf(" fillId=\"%d\" applyFill=\"1\"", xf.FillId)
}
if xf.BorderId > 0 {
xml += fmt.Sprintf(" borderId=\"%d\" applyBorder=\"1\"", xf.BorderId)
}
if xf.NumFmtId > 0 {
xml += fmt.Sprintf(" numFmtId=\"%d\" applyNumberFormat=\"1\"", xf.NumFmtId)
}
if xf.Alignment != nil {
xml += " applyAlignment=\"1\""
}
xml += ">\n"
if xf.Alignment != nil {
xml += " <alignment"
if xf.Alignment.Horizontal != "" {
xml += fmt.Sprintf(" horizontal=\"%s\"", xf.Alignment.Horizontal)
}
if xf.Alignment.Vertical != "" {
xml += fmt.Sprintf(" vertical=\"%s\"", xf.Alignment.Vertical)
}
if xf.Alignment.WrapText {
xml += " wrapText=\"1\""
}
xml += " />\n"
}
xml += " </xf>\n"
}
xml += " </cellStyleXfs>\n"
// 单元格XF
xml += fmt.Sprintf(" <cellXfs count=\"%d\">\n", len(s.CellXfs))
for _, xf := range s.CellXfs {
xml += " <xf"
// 引用已经存在的样式ID
xml += fmt.Sprintf(" xfId=\"0\"")
// 设置字体
if xf.FontId > 0 {
xml += fmt.Sprintf(" fontId=\"%d\" applyFont=\"1\"", xf.FontId)
} else {
xml += " fontId=\"0\""
}
// 设置填充
if xf.FillId > 0 {
xml += fmt.Sprintf(" fillId=\"%d\" applyFill=\"1\"", xf.FillId)
} else {
xml += " fillId=\"0\""
}
// 设置边框
if xf.BorderId > 0 {
xml += fmt.Sprintf(" borderId=\"%d\" applyBorder=\"1\"", xf.BorderId)
} else {
xml += " borderId=\"0\""
}
// 设置数字格式
if xf.NumFmtId > 0 {
xml += fmt.Sprintf(" numFmtId=\"%d\" applyNumberFormat=\"1\"", xf.NumFmtId)
} else {
xml += " numFmtId=\"0\""
}
// 设置对齐
if xf.Alignment != nil {
xml += " applyAlignment=\"1\""
}
xml += ">\n"
// 添加对齐信息
if xf.Alignment != nil {
xml += " <alignment"
if xf.Alignment.Horizontal != "" {
xml += fmt.Sprintf(" horizontal=\"%s\"", xf.Alignment.Horizontal)
}
if xf.Alignment.Vertical != "" {
xml += fmt.Sprintf(" vertical=\"%s\"", xf.Alignment.Vertical)
}
if xf.Alignment.WrapText {
xml += " wrapText=\"1\""
}
xml += " />\n"
}
xml += " </xf>\n"
}
xml += " </cellXfs>\n"
// 单元格样式
xml += fmt.Sprintf(" <cellStyles count=\"%d\">\n", len(s.CellStyles))
for _, style := range s.CellStyles {
xml += fmt.Sprintf(" <cellStyle name=\"%s\" xfId=\"%d\" builtinId=\"%d\"", style.Name, style.XfId, style.BuiltinId)
if style.CustomBuiltin {
xml += " customBuiltin=\"1\""
}
xml += " />\n"
}
xml += " </cellStyles>\n"
xml += "</styleSheet>"
return xml
}
// CreateBorderWithStyle 创建一个边框样式并返回边框ID
func (s *Styles) CreateBorderWithStyle(style, color string) int {
border := s.AddBorder()
border.Left.Style = style
border.Left.Color = color
border.Right.Style = style
border.Right.Color = color
border.Top.Style = style
border.Top.Color = color
border.Bottom.Style = style
border.Bottom.Color = color
return len(s.Borders) - 1
}
// AddDirectStyleID 直接添加一个CellStyle并返回样式ID
func (s *Styles) AddDirectStyleID(style *CellStyle) int {
return s.AddCellXf(style.FontID, style.FillID, style.BorderID, style.NumberFormatID, style.Alignment)
}

105
workbook/theme.go Normal file
View File

@@ -0,0 +1,105 @@
package workbook
// Theme 表示Excel文档中的主题
type Theme struct {
}
// NewTheme 创建一个新的主题
func NewTheme() *Theme {
return &Theme{}
}
// ToXML 将主题转换为XML
func (t *Theme) ToXML() string {
// 使用Office默认主题
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
xml += "<a:theme xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" name=\"Office Theme\">\n"
// 颜色方案
xml += " <a:themeElements>\n"
xml += " <a:clrScheme name=\"Office\">\n"
xml += " <a:dk1><a:sysClr val=\"windowText\" lastClr=\"000000\"/></a:dk1>\n"
xml += " <a:lt1><a:sysClr val=\"window\" lastClr=\"FFFFFF\"/></a:lt1>\n"
xml += " <a:dk2><a:srgbClr val=\"1F497D\"/></a:dk2>\n"
xml += " <a:lt2><a:srgbClr val=\"EEECE1\"/></a:lt2>\n"
xml += " <a:accent1><a:srgbClr val=\"4F81BD\"/></a:accent1>\n"
xml += " <a:accent2><a:srgbClr val=\"C0504D\"/></a:accent2>\n"
xml += " <a:accent3><a:srgbClr val=\"9BBB59\"/></a:accent3>\n"
xml += " <a:accent4><a:srgbClr val=\"8064A2\"/></a:accent4>\n"
xml += " <a:accent5><a:srgbClr val=\"4BACC6\"/></a:accent5>\n"
xml += " <a:accent6><a:srgbClr val=\"F79646\"/></a:accent6>\n"
xml += " <a:hlink><a:srgbClr val=\"0000FF\"/></a:hlink>\n"
xml += " <a:folHlink><a:srgbClr val=\"800080\"/></a:folHlink>\n"
xml += " </a:clrScheme>\n"
// 字体方案
xml += " <a:fontScheme name=\"Office\">\n"
xml += " <a:majorFont>\n"
xml += " <a:latin typeface=\"Calibri\"/>\n"
xml += " <a:ea typeface=\"\"/>\n"
xml += " <a:cs typeface=\"\"/>\n"
xml += " </a:majorFont>\n"
xml += " <a:minorFont>\n"
xml += " <a:latin typeface=\"Calibri\"/>\n"
xml += " <a:ea typeface=\"\"/>\n"
xml += " <a:cs typeface=\"\"/>\n"
xml += " </a:minorFont>\n"
xml += " </a:fontScheme>\n"
// 格式方案
xml += " <a:fmtScheme name=\"Office\">\n"
xml += " <a:fillStyleLst>\n"
xml += " <a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>\n"
xml += " <a:gradFill rotWithShape=\"1\">\n"
xml += " <a:gsLst>\n"
xml += " <a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:tint val=\"50000\"/><a:satMod val=\"300000\"/></a:schemeClr></a:gs>\n"
xml += " <a:gs pos=\"35000\"><a:schemeClr val=\"phClr\"><a:tint val=\"37000\"/><a:satMod val=\"300000\"/></a:schemeClr></a:gs>\n"
xml += " <a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:tint val=\"15000\"/><a:satMod val=\"350000\"/></a:schemeClr></a:gs>\n"
xml += " </a:gsLst>\n"
xml += " <a:lin ang=\"16200000\" scaled=\"1\"/>\n"
xml += " </a:gradFill>\n"
xml += " <a:gradFill rotWithShape=\"1\">\n"
xml += " <a:gsLst>\n"
xml += " <a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:shade val=\"51000\"/><a:satMod val=\"130000\"/></a:schemeClr></a:gs>\n"
xml += " <a:gs pos=\"80000\"><a:schemeClr val=\"phClr\"><a:shade val=\"93000\"/><a:satMod val=\"130000\"/></a:schemeClr></a:gs>\n"
xml += " <a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:shade val=\"94000\"/><a:satMod val=\"135000\"/></a:schemeClr></a:gs>\n"
xml += " </a:gsLst>\n"
xml += " <a:lin ang=\"16200000\" scaled=\"0\"/>\n"
xml += " </a:gradFill>\n"
xml += " </a:fillStyleLst>\n"
xml += " <a:lnStyleLst>\n"
xml += " <a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"><a:shade val=\"95000\"/><a:satMod val=\"105000\"/></a:schemeClr></a:solidFill><a:prstDash val=\"solid\"/></a:ln>\n"
xml += " <a:ln w=\"25400\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/></a:ln>\n"
xml += " <a:ln w=\"38100\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/></a:ln>\n"
xml += " </a:lnStyleLst>\n"
xml += " <a:effectStyleLst>\n"
xml += " <a:effectStyle><a:effectLst/></a:effectStyle>\n"
xml += " <a:effectStyle><a:effectLst/></a:effectStyle>\n"
xml += " <a:effectStyle><a:effectLst/></a:effectStyle>\n"
xml += " </a:effectStyleLst>\n"
xml += " <a:bgFillStyleLst>\n"
xml += " <a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>\n"
xml += " <a:gradFill rotWithShape=\"1\">\n"
xml += " <a:gsLst>\n"
xml += " <a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:tint val=\"40000\"/><a:satMod val=\"350000\"/></a:schemeClr></a:gs>\n"
xml += " <a:gs pos=\"40000\"><a:schemeClr val=\"phClr\"><a:tint val=\"45000\"/><a:shade val=\"99000\"/><a:satMod val=\"350000\"/></a:schemeClr></a:gs>\n"
xml += " <a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:shade val=\"20000\"/><a:satMod val=\"255000\"/></a:schemeClr></a:gs>\n"
xml += " </a:gsLst>\n"
xml += " <a:path path=\"circle\"><a:fillToRect l=\"50000\" t=\"50000\" r=\"50000\" b=\"50000\"/></a:path>\n"
xml += " </a:gradFill>\n"
xml += " <a:gradFill rotWithShape=\"1\">\n"
xml += " <a:gsLst>\n"
xml += " <a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:tint val=\"80000\"/><a:satMod val=\"300000\"/></a:schemeClr></a:gs>\n"
xml += " <a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:shade val=\"30000\"/><a:satMod val=\"200000\"/></a:schemeClr></a:gs>\n"
xml += " </a:gsLst>\n"
xml += " <a:path path=\"circle\"><a:fillToRect l=\"50000\" t=\"50000\" r=\"50000\" b=\"50000\"/></a:path>\n"
xml += " </a:gradFill>\n"
xml += " </a:bgFillStyleLst>\n"
xml += " </a:fmtScheme>\n"
xml += " </a:themeElements>\n"
xml += " <a:objectDefaults/>\n"
xml += " <a:extraClrSchemeLst/>\n"
xml += "</a:theme>\n"
return xml
}

130
workbook/utils.go Normal file
View File

@@ -0,0 +1,130 @@
package workbook
import (
"fmt"
"strconv"
"strings"
"time"
)
// CellRef 根据行列索引生成单元格引用
// 例如: CellRef(0, 0) 返回 "A1"
func CellRef(row, col int) string {
return fmt.Sprintf("%s%d", ColIndexToName(col), row+1)
}
// ColIndexToName 将列索引转换为列名
// 例如: ColIndexToName(0) 返回 "A", ColIndexToName(25) 返回 "Z", ColIndexToName(26) 返回 "AA"
func ColIndexToName(colIndex int) string {
if colIndex < 0 {
return ""
}
result := ""
for colIndex >= 0 {
remainder := colIndex % 26
result = string(rune('A'+remainder)) + result
colIndex = colIndex/26 - 1
if colIndex < 0 {
break
}
}
return result
}
// ColNameToIndex 将列名转换为列索引
// 例如: ColNameToIndex("A") 返回 0, ColNameToIndex("Z") 返回 25, ColNameToIndex("AA") 返回 26
func ColNameToIndex(colName string) int {
colName = strings.ToUpper(colName)
result := 0
for i := 0; i < len(colName); i++ {
result = result*26 + int(colName[i]-'A'+1)
}
return result - 1
}
// ParseCellRef 解析单元格引用为行列索引
// 例如: ParseCellRef("A1") 返回 (0, 0)
func ParseCellRef(cellRef string) (row, col int, err error) {
// 找到字母和数字的分界点
index := 0
for index < len(cellRef) && (cellRef[index] < '0' || cellRef[index] > '9') {
index++
}
if index == 0 || index == len(cellRef) {
return 0, 0, fmt.Errorf("invalid cell reference: %s", cellRef)
}
colName := cellRef[:index]
rowStr := cellRef[index:]
// 解析行号
rowNum, err := strconv.Atoi(rowStr)
if err != nil {
return 0, 0, fmt.Errorf("invalid row number in cell reference: %s", cellRef)
}
// 解析列名
colIndex := ColNameToIndex(colName)
return rowNum - 1, colIndex, nil
}
// FormatDate 将时间格式化为Excel日期格式
func FormatDate(t time.Time, format string) string {
// Excel日期格式映射到Go时间格式
format = strings.Replace(format, "yyyy", "2006", -1)
format = strings.Replace(format, "yy", "06", -1)
format = strings.Replace(format, "mm", "01", -1)
format = strings.Replace(format, "dd", "02", -1)
format = strings.Replace(format, "hh", "15", -1)
format = strings.Replace(format, "ss", "05", -1)
// 确保分钟格式正确处理
if strings.Contains(format, "15:01") {
format = strings.Replace(format, "01", "04", -1)
}
return t.Format(format)
}
// GetExcelSerialDate 将时间转换为Excel序列日期值
// Excel日期系统: 1900年1月1日为1每天加1
func GetExcelSerialDate(t time.Time) float64 {
// 转换到当地时区,避免时区问题
t = t.Local()
// Excel基准日期: 1900年1月0日但Excel实际以1900年1月1日为1
baseDate := time.Date(1899, 12, 30, 0, 0, 0, 0, time.Local)
// 计算相差的天数(包括小数部分来表示时间)
days := t.Sub(baseDate).Hours() / 24.0
// Excel有一个关于1900年2月29日的错误实际上1900年不是闰年
// 如果日期在1900年3月1日之后需要加1来匹配Excel的错误
if t.After(time.Date(1900, 3, 1, 0, 0, 0, 0, time.Local)) {
days += 1
}
// Excel中1900年1月1日是1而不是0
return days + 1
}
// GetTimeFromExcelSerialDate 将Excel序列日期值转换为时间
func GetTimeFromExcelSerialDate(serialDate float64) time.Time {
// Excel基准日期: 1900年1月0日
baseDate := time.Date(1899, 12, 30, 0, 0, 0, 0, time.Local)
// 减1是因为Excel中1900年1月1日是1而不是0
daysPassed := serialDate - 1
// 处理Excel的1900年2月29日错误
if serialDate >= 60 {
daysPassed -= 1
}
// 计算时间
duration := time.Duration(daysPassed * 24 * float64(time.Hour))
return baseDate.Add(duration)
}

214
workbook/workbook.go Normal file
View File

@@ -0,0 +1,214 @@
package workbook
import (
"archive/zip"
"fmt"
"os"
"time"
)
// Workbook 表示一个Excel工作簿
type Workbook struct {
Worksheets []*Worksheet
Properties *WorkbookProperties
Relationships *Relationships
Styles *Styles
Theme *Theme
ContentTypes *ContentTypes
Rels *WorkbookRels
SharedStrings *SharedStrings
}
// WorkbookProperties 包含工作簿的元数据
type WorkbookProperties struct {
Title string
Subject string
Creator string
Keywords string
Description string
LastModifiedBy string
Revision int
Created time.Time
Modified time.Time
}
// NewWorkbook 创建一个新的Excel工作簿
func NewWorkbook() *Workbook {
return &Workbook{
Worksheets: make([]*Worksheet, 0),
Properties: &WorkbookProperties{
Created: time.Now(),
Modified: time.Now(),
Revision: 1,
},
Relationships: NewRelationships(),
Styles: NewStyles(),
Theme: NewTheme(),
ContentTypes: NewContentTypes(),
Rels: NewWorkbookRels(),
SharedStrings: NewSharedStrings(),
}
}
// AddWorksheet 添加一个新的工作表
func (wb *Workbook) AddWorksheet(name string) *Worksheet {
ws := NewWorksheet(name)
ws.SheetID = len(wb.Worksheets) + 1
wb.Worksheets = append(wb.Worksheets, ws)
return ws
}
// Save 保存Excel工作簿到文件
func (wb *Workbook) Save(filename string) error {
// 创建一个新的zip文件
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
// 创建一个新的zip writer
zipWriter := zip.NewWriter(file)
defer zipWriter.Close()
// 添加[Content_Types].xml
contentTypesWriter, err := zipWriter.Create("[Content_Types].xml")
if err != nil {
return err
}
_, err = contentTypesWriter.Write([]byte(wb.ContentTypes.ToXML()))
if err != nil {
return err
}
// 添加_rels/.rels
relsDir, err := zipWriter.Create("_rels/.rels")
if err != nil {
return err
}
// 创建根关系
rootRels := NewRelationships()
rootRels.AddRelationship("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml")
_, err = relsDir.Write([]byte(rootRels.ToXML()))
if err != nil {
return err
}
// 添加xl/workbook.xml
workbookWriter, err := zipWriter.Create("xl/workbook.xml")
if err != nil {
return err
}
_, err = workbookWriter.Write([]byte(wb.ToXML()))
if err != nil {
return err
}
// 添加xl/_rels/workbook.xml.rels
workbookRelsWriter, err := zipWriter.Create("xl/_rels/workbook.xml.rels")
if err != nil {
return err
}
// 创建工作簿关系
wbRels := NewRelationships()
// 添加样式关系
wbRels.AddRelationship("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", "styles.xml")
// 添加主题关系
wbRels.AddRelationship("rId2", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", "theme/theme1.xml")
// 添加共享字符串表关系
wbRels.AddRelationship("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings", "sharedStrings.xml")
// 添加工作表关系
for i := range wb.Worksheets {
relID := fmt.Sprintf("rId%d", i+4) // 从rId4开始
target := fmt.Sprintf("worksheets/sheet%d.xml", i+1)
wbRels.AddRelationship(relID, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet", target)
}
_, err = workbookRelsWriter.Write([]byte(wbRels.ToXML()))
if err != nil {
return err
}
// 添加xl/worksheets/sheet1.xml, sheet2.xml, ...
for i, ws := range wb.Worksheets {
sheetPath := fmt.Sprintf("xl/worksheets/sheet%d.xml", i+1)
sheetWriter, err := zipWriter.Create(sheetPath)
if err != nil {
return err
}
_, err = sheetWriter.Write([]byte(ws.ToXML(wb.SharedStrings)))
if err != nil {
return err
}
}
// 添加xl/styles.xml
stylesWriter, err := zipWriter.Create("xl/styles.xml")
if err != nil {
return err
}
_, err = stylesWriter.Write([]byte(wb.Styles.ToXML()))
if err != nil {
return err
}
// 添加xl/theme/theme1.xml
themeWriter, err := zipWriter.Create("xl/theme/theme1.xml")
if err != nil {
return err
}
_, err = themeWriter.Write([]byte(wb.Theme.ToXML()))
if err != nil {
return err
}
// 添加xl/sharedStrings.xml
sharedStringsWriter, err := zipWriter.Create("xl/sharedStrings.xml")
if err != nil {
return err
}
_, err = sharedStringsWriter.Write([]byte(wb.SharedStrings.ToXML()))
if err != nil {
return err
}
return nil
}
// WorkbookRels 表示工作簿的关系
type WorkbookRels struct {
Relationships *Relationships
}
// NewWorkbookRels 创建一个新的工作簿关系
func NewWorkbookRels() *WorkbookRels {
return &WorkbookRels{
Relationships: NewRelationships(),
}
}
// ToXML 将工作簿转换为XML
func (wb *Workbook) ToXML() string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
xml += "<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n"
// 工作簿属性
xml += " <workbookPr defaultThemeVersion=\"124226\"/>\n"
// 工作表
xml += " <sheets>\n"
for i, ws := range wb.Worksheets {
relID := fmt.Sprintf("rId%d", i+4) // 从rId4开始与上面的关系ID对应
xml += fmt.Sprintf(" <sheet name=\"%s\" sheetId=\"%d\" r:id=\"%s\"/>\n", ws.Name, ws.SheetID, relID)
}
xml += " </sheets>\n"
xml += "</workbook>"
return xml
}

419
workbook/worksheet.go Normal file
View File

@@ -0,0 +1,419 @@
package workbook
import (
"fmt"
"time"
)
// Worksheet 表示Excel工作簿中的工作表
type Worksheet struct {
Name string
SheetID int
Cells map[string]*Cell
Columns []*Column
Rows []*Row
MergedCells []*MergedCell
}
// NewWorksheet 创建一个新的工作表
func NewWorksheet(name string) *Worksheet {
return &Worksheet{
Name: name,
SheetID: 1,
Cells: make(map[string]*Cell),
Columns: make([]*Column, 0),
Rows: make([]*Row, 0),
MergedCells: make([]*MergedCell, 0),
}
}
// Cell 表示工作表中的单元格
type Cell struct {
Value interface{}
Formula string
Style *CellStyle
DataType string // s: 字符串, n: 数字, b: 布尔值, d: 日期, e: 错误
}
// NewCell 创建一个新的单元格
func NewCell() *Cell {
return &Cell{
Style: NewCellStyle(),
}
}
// CellStyle 表示单元格样式
type CellStyle struct {
FontID int
FillID int
BorderID int
NumberFormatID int
Alignment *Alignment
}
// NewCellStyle 创建一个新的单元格样式
func NewCellStyle() *CellStyle {
return &CellStyle{
Alignment: &Alignment{
Horizontal: "general",
Vertical: "bottom",
},
}
}
// Alignment 表示对齐方式
type Alignment struct {
Horizontal string // left, center, right, fill, justify, centerContinuous, distributed
Vertical string // top, center, bottom, justify, distributed
WrapText bool
}
// Column 表示工作表中的列
type Column struct {
Min int
Max int
Width float64
Style *CellStyle
Hidden bool
}
// Row 表示工作表中的行
type Row struct {
Index int
Height float64
Cells []*Cell
Style *CellStyle
Hidden bool
}
// MergedCell 表示合并的单元格
type MergedCell struct {
TopLeftRef string // 例如: "A1"
BottomRightRef string // 例如: "B2"
}
// AddRow 添加一个新的行
func (ws *Worksheet) AddRow() *Row {
row := &Row{
Index: len(ws.Rows) + 1,
Cells: make([]*Cell, 0),
Style: NewCellStyle(),
}
ws.Rows = append(ws.Rows, row)
return row
}
// AddColumn 添加一个新的列
func (ws *Worksheet) AddColumn(min, max int, width float64) *Column {
col := &Column{
Min: min,
Max: max,
Width: width,
Style: NewCellStyle(),
}
ws.Columns = append(ws.Columns, col)
return col
}
// AddCell 在指定位置添加一个单元格
func (ws *Worksheet) AddCell(cellRef string, value interface{}) *Cell {
cell := NewCell()
// 根据值类型设置数据类型
switch v := value.(type) {
case string:
cell.DataType = "s"
cell.Value = value
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
cell.DataType = "n"
cell.Value = value
case bool:
cell.DataType = "b"
cell.Value = value
case time.Time:
// 日期和时间在Excel中表示为序列号
// 数字部分代表天数,小数部分代表一天中的时间
cell.DataType = "n"
// 使用正确的Excel日期转换函数
// GetExcelSerialDate已经处理了日期转换的细节包括1900年2月29日的Excel错误
serialDate := GetExcelSerialDate(v)
// 需要保留小数部分以表示时间
hours := float64(v.Hour()) / 24.0
minutes := float64(v.Minute()) / (24.0 * 60.0)
seconds := float64(v.Second()) / (24.0 * 60.0 * 60.0)
// 组合日期和时间部分
cell.Value = serialDate + hours + minutes + seconds
default:
cell.DataType = "s"
cell.Value = fmt.Sprintf("%v", value)
}
ws.Cells[cellRef] = cell
return cell
}
// SetCellFormula 设置单元格公式
func (ws *Worksheet) SetCellFormula(cellRef string, formula string) *Cell {
cell, ok := ws.Cells[cellRef]
if !ok {
cell = ws.AddCell(cellRef, "")
}
cell.Formula = formula
// 公式单元格的初始值设为空让Excel自动计算
// 确保DataType不是字符串这会导致Excel无法正确计算公式
if cell.DataType == "s" {
cell.DataType = "" // 让Excel自动判断类型
}
return cell
}
// SetCellStyle 设置单元格样式
func (ws *Worksheet) SetCellStyle(cellRef string, style *CellStyle) *Cell {
cell, ok := ws.Cells[cellRef]
if !ok {
cell = ws.AddCell(cellRef, "")
}
// 直接设置样式,确保样式引用正确
cell.Style = style
return cell
}
// ToXML 将工作表转换为XML
func (ws *Worksheet) ToXML(sharedStrings *SharedStrings) string {
xml := "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
xml += "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n"
// 列定义
if len(ws.Columns) > 0 {
xml += " <cols>\n"
for _, col := range ws.Columns {
xml += fmt.Sprintf(" <col min=\"%d\" max=\"%d\" width=\"%f\" customWidth=\"1\"", col.Min, col.Max, col.Width)
if col.Hidden {
xml += " hidden=\"1\""
}
xml += "/>\n"
}
xml += " </cols>\n"
}
// 单元格数据
xml += " <sheetData>\n"
// 按行组织单元格
rowMap := make(map[int]map[string]*Cell)
for cellRef, cell := range ws.Cells {
rowIndex, colIndex, err := ParseCellRef(cellRef)
if err != nil {
continue
}
// ParseCellRef返回的是从0开始的索引而Excel行索引从1开始
// 将行索引加1使其与row.Index匹配
rowNum := rowIndex + 1
if _, ok := rowMap[rowNum]; !ok {
rowMap[rowNum] = make(map[string]*Cell)
}
// 确保cellRef格式正确重新生成标准格式的单元格引用
standardCellRef := CellRef(rowIndex, colIndex)
rowMap[rowNum][standardCellRef] = cell
}
// 收集所有行索引
rowIndices := make([]int, 0)
// 添加从单元格收集的行
for rowIdx := range rowMap {
rowIndices = append(rowIndices, rowIdx)
}
// 添加显式定义的行
for _, row := range ws.Rows {
found := false
for _, idx := range rowIndices {
if idx == row.Index {
found = true
break
}
}
if !found {
rowIndices = append(rowIndices, row.Index)
}
}
// 按行号排序
for i := 0; i < len(rowIndices)-1; i++ {
for j := i + 1; j < len(rowIndices); j++ {
if rowIndices[i] > rowIndices[j] {
rowIndices[i], rowIndices[j] = rowIndices[j], rowIndices[i]
}
}
}
// 输出行和单元格
for _, rowIdx := range rowIndices {
// 查找是否有显式添加的行
var rowHeight float64
var rowHidden bool
for _, r := range ws.Rows {
if r.Index == rowIdx {
rowHeight = r.Height
rowHidden = r.Hidden
break
}
}
xml += fmt.Sprintf(" <row r=\"%d\"", rowIdx)
if rowHeight > 0 {
xml += fmt.Sprintf(" ht=\"%f\" customHeight=\"1\"", rowHeight)
}
if rowHidden {
xml += " hidden=\"1\""
}
xml += ">\n"
// 输出该行的单元格
if cells, ok := rowMap[rowIdx]; ok {
// 对单元格按列排序
cellRefs := make([]string, 0, len(cells))
for cellRef := range cells {
cellRefs = append(cellRefs, cellRef)
}
// 简单排序,确保单元格按列顺序输出
for i := 0; i < len(cellRefs)-1; i++ {
for j := i + 1; j < len(cellRefs); j++ {
_, col1, _ := ParseCellRef(cellRefs[i])
_, col2, _ := ParseCellRef(cellRefs[j])
if col1 > col2 {
cellRefs[i], cellRefs[j] = cellRefs[j], cellRefs[i]
}
}
}
for _, cellRef := range cellRefs {
cell := cells[cellRef]
// 使用标准化的单元格引用
xml += fmt.Sprintf(" <c r=\"%s\"", cellRef)
// 正确处理样式ID引用
if cell.Style != nil {
// 从样式中获取样式ID
styleID := 0
// 首先尝试使用NumberFormatID这是格式化日期/数字等的关键
if cell.Style.NumberFormatID > 0 {
styleID = cell.Style.NumberFormatID
} else {
// 如果没有NumberFormatID则按优先级使用其他样式ID
if cell.Style.FontID > 0 {
styleID = cell.Style.FontID
} else if cell.Style.FillID > 0 {
styleID = cell.Style.FillID
} else if cell.Style.BorderID > 0 {
styleID = cell.Style.BorderID
}
}
if styleID > 0 {
xml += fmt.Sprintf(" s=\"%d\"", styleID)
}
}
// 设置数据类型
if cell.DataType != "" {
// 确保数据类型是有效的Excel类型
switch cell.DataType {
case "s":
xml += " t=\"s\"" // 字符串类型
case "b":
xml += " t=\"b\"" // 布尔类型
case "n":
// 数字类型不需要特殊的t属性
default:
xml += " t=\"s\""
}
}
xml += ">"
// 添加公式
if cell.Formula != "" {
xml += fmt.Sprintf("<f>%s</f>", cell.Formula)
// 公式单元格不应该标记为字符串类型,除非明确需要
// 让Excel自动根据公式计算结果决定单元格类型
if cell.Value != nil {
xml += fmt.Sprintf("<v>%v</v>", cell.Value)
}
} else if cell.Value != nil {
// 添加值(仅当没有公式时)
switch cell.DataType {
case "s": // 字符串
strValue, ok := cell.Value.(string)
if ok {
index := sharedStrings.AddString(strValue)
xml += fmt.Sprintf("<v>%d</v>", index)
}
case "n": // 数字 (包括日期,日期仅是有特殊格式的数字)
xml += fmt.Sprintf("<v>%v</v>", cell.Value)
case "b": // 布尔值
boolValue, ok := cell.Value.(bool)
if ok {
if boolValue {
xml += "<v>1</v>"
} else {
xml += "<v>0</v>"
}
}
default:
// 默认作为字符串处理
strValue := fmt.Sprintf("%v", cell.Value)
index := sharedStrings.AddString(strValue)
xml += fmt.Sprintf("<v>%d</v>", index)
}
}
xml += "</c>\n"
}
}
xml += " </row>\n"
}
xml += " </sheetData>\n"
// 合并单元格
if len(ws.MergedCells) > 0 {
xml += " <mergeCells count=\"" + fmt.Sprintf("%d", len(ws.MergedCells)) + "\">\n"
for _, mergedCell := range ws.MergedCells {
xml += " <mergeCell ref=\"" + mergedCell.TopLeftRef + ":" + mergedCell.BottomRightRef + "\" />\n"
}
xml += " </mergeCells>\n"
}
xml += "</worksheet>"
return xml
}
// MergeCells 合并单元格
func (ws *Worksheet) MergeCells(topLeftRef, bottomRightRef string) *MergedCell {
mergedCell := &MergedCell{
TopLeftRef: topLeftRef,
BottomRightRef: bottomRightRef,
}
ws.MergedCells = append(ws.MergedCells, mergedCell)
return mergedCell
}