367 lines
7.9 KiB
Go
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)
|
|
}
|