Files
wasm-go/xlsx_reader.go
lychang 5e3dac3020 init
2026-05-19 19:42:55 +08:00

367 lines
7.9 KiB
Go

package main
import (
"archive/zip"
"encoding/xml"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
)
type XLSXReader struct {
files map[string]*zip.File
}
type SharedStrings struct {
Strings []string `xml:"si>t"`
}
type Sheet struct {
Name string `xml:"name,attr"`
SheetID int `xml:"sheetId,attr"`
Ref string `xml:"id,attr"`
}
type Sheets struct {
Sheets []Sheet `xml:"sheet"`
}
type Row struct {
Cells []Cell `xml:"c"`
RowNum int `xml:"r,attr"`
}
type Cell struct {
Ref string `xml:"r,attr"`
Type string `xml:"t,attr"`
Val string `xml:"v"`
}
type Worksheet struct {
Rows []Row `xml:"sheetData>row"`
}
func NewXLSXReader(filePath string) (*XLSXReader, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("打开文件失败: %v", err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, fmt.Errorf("获取文件信息失败: %v", err)
}
zr, err := zip.NewReader(f, fi.Size())
if err != nil {
return nil, fmt.Errorf("创建zip读取器失败: %v", err)
}
files := make(map[string]*zip.File)
for _, file := range zr.File {
files[file.Name] = file
}
return &XLSXReader{files: files}, nil
}
func (r *XLSXReader) GetSheetNames() ([]string, error) {
contentTypesFile, ok := r.files["[Content_Types].xml"]
if !ok {
return nil, fmt.Errorf("找不到[Content_Types].xml")
}
f, err := contentTypesFile.Open()
if err != nil {
return nil, fmt.Errorf("打开[Content_Types].xml失败: %v", err)
}
defer f.Close()
content, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("读取[Content_Types].xml失败: %v", err)
}
sheetsFile, ok := r.files["xl/workbook.xml"]
if !ok {
return nil, fmt.Errorf("找不到xl/workbook.xml")
}
f, err = sheetsFile.Open()
if err != nil {
return nil, fmt.Errorf("打开xl/workbook.xml失败: %v", err)
}
defer f.Close()
content, err = io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("读取xl/workbook.xml失败: %v", err)
}
var sheets Sheets
err = xml.Unmarshal(content, &sheets)
if err != nil {
return nil, fmt.Errorf("解析xl/workbook.xml失败: %v", err)
}
names := make([]string, 0, len(sheets.Sheets))
for _, sheet := range sheets.Sheets {
names = append(names, sheet.Name)
}
return names, nil
}
func (r *XLSXReader) GetSheetData(sheetName string) ([][]string, error) {
sheetsFile, ok := r.files["xl/workbook.xml"]
if !ok {
return nil, fmt.Errorf("找不到xl/workbook.xml")
}
f, err := sheetsFile.Open()
if err != nil {
return nil, fmt.Errorf("打开xl/workbook.xml失败: %v", err)
}
defer f.Close()
content, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("读取xl/workbook.xml失败: %v", err)
}
var sheets Sheets
err = xml.Unmarshal(content, &sheets)
if err != nil {
return nil, fmt.Errorf("解析xl/workbook.xml失败: %v", err)
}
var targetSheet Sheet
for _, sheet := range sheets.Sheets {
if sheet.Name == sheetName {
targetSheet = sheet
break
}
}
if targetSheet.Name == "" {
return nil, fmt.Errorf("找不到工作表: %s", sheetName)
}
sheetID := strings.TrimPrefix(targetSheet.Ref, "rId")
sheetFilePath := fmt.Sprintf("xl/worksheets/sheet%s.xml", sheetID)
sheetFile, ok := r.files[sheetFilePath]
if !ok {
return nil, fmt.Errorf("找不到工作表文件: %s", sheetFilePath)
}
f, err = sheetFile.Open()
if err != nil {
return nil, fmt.Errorf("打开工作表文件失败: %v", err)
}
defer f.Close()
content, err = io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("读取工作表文件失败: %v", err)
}
var worksheet Worksheet
err = xml.Unmarshal(content, &worksheet)
if err != nil {
return nil, fmt.Errorf("解析工作表失败: %v", err)
}
var sharedStrings []string
sharedStringsFile, ok := r.files["xl/sharedStrings.xml"]
if ok {
f, err := sharedStringsFile.Open()
if err == nil {
defer f.Close()
sharedContent, _ := io.ReadAll(f)
var ss SharedStrings
if xml.Unmarshal(sharedContent, &ss) == nil {
sharedStrings = ss.Strings
}
}
}
maxCol := 0
for _, row := range worksheet.Rows {
for _, cell := range row.Cells {
col := getColumnIndex(cell.Ref)
if col > maxCol {
maxCol = col
}
}
}
if len(worksheet.Rows) == 0 {
return [][]string{}, nil
}
result := make([][]string, len(worksheet.Rows))
for i, row := range worksheet.Rows {
result[i] = make([]string, maxCol+1)
for _, cell := range row.Cells {
col := getColumnIndex(cell.Ref)
val := cell.Val
if cell.Type == "s" && sharedStrings != nil {
idx, _ := strconv.Atoi(val)
if idx >= 0 && idx < len(sharedStrings) {
val = sharedStrings[idx]
}
}
if col < len(result[i]) {
result[i][col] = val
}
}
}
return result, nil
}
func (r *XLSXReader) GetCellValue(sheetName, cellRef string) (string, error) {
data, err := r.GetSheetData(sheetName)
if err != nil {
return "", err
}
rowIdx, colIdx := parseCellRef(cellRef)
if rowIdx >= len(data) {
return "", fmt.Errorf("行索引超出范围")
}
if colIdx >= len(data[rowIdx]) {
return "", fmt.Errorf("列索引超出范围")
}
return data[rowIdx][colIdx], nil
}
func (r *XLSXReader) GetCellByPosition(sheetName string, row, col int) (string, error) {
data, err := r.GetSheetData(sheetName)
if err != nil {
return "", err
}
if row < 0 || row >= len(data) {
return "", fmt.Errorf("行索引超出范围: %d", row)
}
if col < 0 || col >= len(data[row]) {
return "", fmt.Errorf("列索引超出范围: %d", col)
}
return data[row][col], nil
}
func parseCellRef(cellRef string) (row, col int) {
i := 0
for i < len(cellRef) && cellRef[i] >= 'A' && cellRef[i] <= 'Z' {
col = col*26 + int(cellRef[i]-'A') + 1
i++
}
row, _ = strconv.Atoi(cellRef[i:])
return row - 1, col - 1
}
func getColumnIndex(cellRef string) int {
i := 0
for i < len(cellRef) && cellRef[i] >= 'A' && cellRef[i] <= 'Z' {
i++
}
colStr := cellRef[:i]
col := 0
for _, c := range colStr {
col = col*26 + int(c-'A') + 1
}
return col - 1
}
func ExtractFinancialDataPureGo(excelFile, mappingFile, sheetName, itemName, columnName string) (float64, error) {
reader, err := NewXLSXReader(excelFile)
if err != nil {
return 0, fmt.Errorf("创建XLSX读取器失败: %v", err)
}
data, err := reader.GetSheetData(sheetName)
if err != nil {
return 0, fmt.Errorf("获取工作表数据失败: %v", err)
}
if len(data) < 2 {
return 0, fmt.Errorf("工作表 %s 数据不足", sheetName)
}
colIndex := -1
for _, cell := range data[0] {
if strings.Contains(strings.TrimSpace(cell), columnName) {
for j, c := range data[0] {
if strings.Contains(strings.TrimSpace(c), columnName) {
colIndex = j
break
}
}
break
}
}
if colIndex == -1 {
for _, row := range data {
for j, cell := range row {
if strings.Contains(strings.TrimSpace(cell), columnName) {
colIndex = j
break
}
}
if colIndex != -1 {
break
}
}
}
if colIndex == -1 {
return 0, fmt.Errorf("未找到列 %s 在工作表 %s", columnName, sheetName)
}
for _, row := range data {
if len(row) > 0 && strings.Contains(strings.TrimSpace(row[0]), itemName) {
if len(row) > colIndex {
value := strings.TrimSpace(row[colIndex])
return parseFinancialValue(value)
}
return 0, fmt.Errorf("列索引 %d 处未找到值", colIndex)
}
}
return 0, fmt.Errorf("未找到项目 %s 在工作表 %s", itemName, sheetName)
}
func parseFinancialValue(value string) (float64, error) {
re := regexp.MustCompile(`([\d.,]+)\s*万`)
if match := re.FindStringSubmatch(value); len(match) == 2 {
numStr := strings.ReplaceAll(match[1], ",", "")
num, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return 0, err
}
return num * 10000, nil
}
value = strings.ReplaceAll(value, ",", "")
value = strings.ReplaceAll(value, "元", "")
value = strings.ReplaceAll(value, "万元", "")
value = strings.TrimSpace(value)
if value == "" || value == "-" || value == "无" {
return 0, nil
}
return strconv.ParseFloat(value, 64)
}