🎉 Initial commit

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

419
workbook/worksheet.go Normal file
View File

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