🎉 Initial commit
This commit is contained in:
106
.gitignore
vendored
Normal file
106
.gitignore
vendored
Normal 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
73
LICENSE
Normal 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.
|
184
document/body.go
Normal file
184
document/body.go
Normal 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
112
document/content_types.go
Normal 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
685
document/document.go
Normal 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
336
document/drawing.go
Normal 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)
|
||||
}
|
150
document/examples/simple/main.go
Normal file
150
document/examples/simple/main.go
Normal 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
95
document/header_footer.go
Normal 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
375
document/numbering.go
Normal 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
299
document/paragraph.go
Normal 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
121
document/relationships.go
Normal 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
345
document/run.go
Normal 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
202
document/settings.go
Normal 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
399
document/styles.go
Normal 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
632
document/table.go
Normal 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
164
document/theme.go
Normal 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
63
document/utils.go
Normal 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
|
||||
}
|
92
workbook/content_types.go
Normal file
92
workbook/content_types.go
Normal 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
|
||||
}
|
269
workbook/examples/simple/main.go
Normal file
269
workbook/examples/simple/main.go
Normal 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
71
workbook/relationships.go
Normal 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
|
||||
}
|
68
workbook/shared_strings.go
Normal file
68
workbook/shared_strings.go
Normal 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, "&", "&", -1)
|
||||
s = strings.Replace(s, "<", "<", -1)
|
||||
s = strings.Replace(s, ">", ">", -1)
|
||||
s = strings.Replace(s, "\"", """, -1)
|
||||
s = strings.Replace(s, "'", "'", -1)
|
||||
|
||||
xml += " <si><t>" + s + "</t></si>\n"
|
||||
}
|
||||
|
||||
xml += "</sst>"
|
||||
return xml
|
||||
}
|
604
workbook/styles.go
Normal file
604
workbook/styles.go
Normal 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, "\"", """, -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
105
workbook/theme.go
Normal 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
130
workbook/utils.go
Normal 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
214
workbook/workbook.go
Normal 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
419
workbook/worksheet.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user