package excel import ( "archive/zip" "bytes" "fmt" "io" "os" "strconv" "strings" ) type SheetInfo struct { Name string SheetID int RID string Path string } type XLSXReader struct { Files map[string][]byte SheetMap map[string]SheetInfo } 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 sanitizeFileName(name string) string { name = strings.ReplaceAll(name, "/", "_") name = strings.ReplaceAll(name, "\\", "_") name = strings.ReplaceAll(name, ":", "_") name = strings.ReplaceAll(name, "*", "_") name = strings.ReplaceAll(name, "?", "_") name = strings.ReplaceAll(name, "\"", "_") name = strings.ReplaceAll(name, "<", "_") name = strings.ReplaceAll(name, ">", "_") name = strings.ReplaceAll(name, "|", "_") return name } func NewXLSXReader(filePath string) (*XLSXReader, error) { f, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("打开文件失败: %v", err) } defer f.Close() _, err = f.Stat() if err != nil { return nil, fmt.Errorf("获取文件信息失败: %v", err) } fileBytes, err := io.ReadAll(f) if err != nil { return nil, fmt.Errorf("读取文件失败: %v", err) } return NewXLSXReaderFromBytes(fileBytes) } func NewXLSXReaderFromBytes(data []byte) (*XLSXReader, error) { zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { return nil, fmt.Errorf("创建zip读取器失败: %v", err) } files := make(map[string][]byte) for _, file := range zr.File { rc, err := file.Open() if err != nil { return nil, fmt.Errorf("打开内部文件失败: %v", err) } content, err := io.ReadAll(rc) rc.Close() if err != nil { return nil, fmt.Errorf("读取内部文件失败: %v", err) } files[file.Name] = content } reader := &XLSXReader{Files: files} reader.parseWorkbook() return reader, nil } func (r *XLSXReader) parseWorkbook() { r.SheetMap = make(map[string]SheetInfo) // 收集所有可用的worksheet文件 var availableSheets []string for file := range r.Files { if strings.HasPrefix(file, "xl/worksheets/sheet") && strings.HasSuffix(file, ".xml") { availableSheets = append(availableSheets, file) } } fmt.Printf("✓ 发现 %d 个可用的worksheet文件: %v\n", len(availableSheets), availableSheets) // 读取rels文件获取正确的映射关系 ridToPath := r.readWorkbookRels() fmt.Printf("✓ 从rels文件读取到 %d 个映射关系\n", len(ridToPath)) content, ok := r.Files["xl/workbook.xml"] if !ok { return } contentStr := string(content) var sheetNames []string idx := 0 for { sheetStart := strings.Index(contentStr[idx:], " 0 { path = fmt.Sprintf("xl/worksheets/sheet%d.xml", sheetID) if r.Files[path] == nil { path = "" } } if path == "" && len(availableSheets) > 0 { usedPaths := make(map[string]bool) for _, info := range r.SheetMap { usedPaths[info.Path] = true } for _, availPath := range availableSheets { if !usedPaths[availPath] { path = availPath break } } } sheetNames = append(sheetNames, name) r.SheetMap[name] = SheetInfo{ Name: name, SheetID: sheetID, RID: rid, Path: path, } idx = nameAttrEnd + 1 } fmt.Printf("✓ 工作簿中定义了 %d 个工作表\n", len(sheetNames)) } func (r *XLSXReader) readWorkbookRels() map[string]string { result := make(map[string]string) content, ok := r.Files["xl/_rels/workbook.xml.rels"] if !ok { fmt.Println("⚠️ 找不到 xl/_rels/workbook.xml.rels 文件") return result } contentStr := string(content) idx := 0 for { relStart := strings.Index(contentStr[idx:], `") if siStart == -1 { break } siStart += siIdx siEnd := strings.Index(contentStr[siStart:], "") if siEnd == -1 { break } siEnd += siStart siContent := contentStr[siStart+4 : siEnd] var text strings.Builder tIdx := 0 for { tStart := strings.Index(siContent[tIdx:], "") if tCloseTag == -1 { break } tCloseTag += tStart tEnd := strings.Index(siContent[tCloseTag:], "") if tEnd == -1 { break } tEnd += tCloseTag text.WriteString(siContent[tCloseTag+1 : tEnd]) tIdx = tEnd + 4 } sharedStrings = append(sharedStrings, text.String()) siIdx = siEnd + 5 } return sharedStrings } func (r *XLSXReader) parseSheetData(contentStr string, sharedStrings []string) [][]string { sheetDataStart := strings.Index(contentStr, "") if sheetDataStart == -1 { return nil } sheetDataEnd := strings.Index(contentStr[sheetDataStart:], "") if sheetDataEnd == -1 { return nil } sheetDataEnd += sheetDataStart sheetData := contentStr[sheetDataStart+11 : sheetDataEnd] var result [][]string maxCol := 0 rowIdx := 0 for { rowStart := strings.Index(sheetData[rowIdx:], "") if rowEnd == -1 { break } rowEnd += rowStart rowContent := sheetData[rowStart : rowEnd+6] rowNumber := len(result) + 1 rowNumAttr := strings.Index(rowContent, ` r="`) if rowNumAttr != -1 { rowNumAttr += 4 rowNumEnd := strings.Index(rowContent[rowNumAttr:], `"`) if rowNumEnd != -1 { if rn, err := strconv.Atoi(rowContent[rowNumAttr : rowNumAttr+rowNumEnd]); err == nil { for len(result) < rn-1 { result = append(result, []string{}) } rowNumber = rn } } } rowData := []string{} cellIdx := 0 for { cellStart := strings.Index(rowContent[cellIdx:], "") if cellEnd == -1 { break } cellEnd += cellStart cellContent := rowContent[cellStart : cellEnd+4] cellRef := "" refAttr := strings.Index(cellContent, ` r="`) if refAttr != -1 { refAttr += 4 refEnd := strings.Index(cellContent[refAttr:], `"`) if refEnd != -1 { cellRef = cellContent[refAttr : refAttr+refEnd] } } if cellRef == "" { cellIdx = cellEnd + 4 continue } colIndex := getColumnIndex(cellRef) cellType := "" typeAttr := strings.Index(cellContent, ` t="`) if typeAttr != -1 { typeAttr += 4 typeEnd := strings.Index(cellContent[typeAttr:], `"`) if typeEnd != -1 { cellType = cellContent[typeAttr : typeAttr+typeEnd] } } val := "" vStart := strings.Index(cellContent, "") if vStart != -1 { vStart += 3 vEnd := strings.Index(cellContent[vStart:], "") if vEnd != -1 { val = cellContent[vStart : vStart+vEnd] if cellType == "s" && sharedStrings != nil { if idx, err := strconv.Atoi(val); err == nil && idx >= 0 && idx < len(sharedStrings) { val = sharedStrings[idx] } } } } for len(rowData) <= colIndex { rowData = append(rowData, "") } rowData[colIndex] = val if colIndex > maxCol { maxCol = colIndex } cellIdx = cellEnd + 4 } for len(result) < rowNumber { result = append(result, []string{}) } result[rowNumber-1] = rowData rowIdx = rowEnd + 6 } for i := range result { for len(result[i]) <= maxCol { result[i] = append(result[i], "") } } return result } func getColumnIndex(cellRef string) int { i := 0 for i < len(cellRef) { c := cellRef[i] if c < 'A' || c > 'Z' { break } i++ } colStr := cellRef[:i] col := 0 for _, c := range colStr { col = col*26 + int(c-'A') + 1 } return col - 1 } func ConvertToMarkdown(data [][]string) string { if len(data) == 0 { return "表格为空" } var result strings.Builder numCols := 0 for _, row := range data { if len(row) > numCols { numCols = len(row) } } for rowIdx, row := range data { result.WriteString("|") for colIdx := 0; colIdx < numCols; colIdx++ { val := "" if colIdx < len(row) { val = escapeMarkdown(row[colIdx]) } result.WriteString(fmt.Sprintf(" %s |", val)) } result.WriteString("\n") if rowIdx == 0 { result.WriteString("|") for colIdx := 0; colIdx < numCols; colIdx++ { result.WriteString(" --- |") } result.WriteString("\n") } } return result.String() } func escapeMarkdown(text string) string { text = strings.ReplaceAll(text, "\\", "\\\\") text = strings.ReplaceAll(text, "|", "\\|") text = strings.ReplaceAll(text, "[", "\\[") text = strings.ReplaceAll(text, "]", "\\]") text = strings.ReplaceAll(text, "(", "\\(") text = strings.ReplaceAll(text, ")", "\\)") text = strings.ReplaceAll(text, "#", "\\#") text = strings.ReplaceAll(text, "*", "\\*") text = strings.ReplaceAll(text, "_", "\\_") return text }