🎉 Initial commit
This commit is contained in:
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