You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
5.0 KiB
Go
218 lines
5.0 KiB
Go
/*
|
|
* Copyright 2025 CloudWeGo Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package generic
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/xuri/excelize/v2"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
type PreviewFile struct {
|
|
FilePath string `json:"file_path,omitempty" xml:"file_path"`
|
|
SingleFilePreviews []*SingleFilePreview `json:"single_file_previews,omitempty" xml:"single_file_previews>single_file_preview"`
|
|
}
|
|
|
|
type SingleFilePreview struct {
|
|
SheetName string `json:"sheet_name,omitempty" xml:"sheet_name"`
|
|
Header []*ExcelCell `json:"header" xml:"header"`
|
|
Content [][]*ExcelCell `json:"content,omitempty" xml:"content"`
|
|
MergedCells []*ExcelCell `json:"merged_cells,omitempty" xml:"merged_cells>merged_cell"`
|
|
}
|
|
|
|
type ExcelCell struct {
|
|
Address string `json:"address,omitempty" xml:"address"` // B1:D1 表示 B1 到 D1 范围的单元格, B1 表示单个单元格
|
|
Value string `json:"value,omitempty" xml:"value"` // 单元格的值
|
|
}
|
|
|
|
func PreviewPath(path string) ([]*PreviewFile, error) {
|
|
filePaths, err := getAllFiles(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(filePaths) == 0 {
|
|
return nil, nil
|
|
}
|
|
resp := make([]*PreviewFile, len(filePaths))
|
|
eg := errgroup.Group{}
|
|
eg.SetLimit(10)
|
|
for i := range filePaths {
|
|
idx := i
|
|
fp := filePaths[idx]
|
|
eg.Go(func() error {
|
|
var (
|
|
pf *PreviewFile
|
|
e error
|
|
)
|
|
if ext := filepath.Ext(fp); ext != ".xlsx" { // .xls not support
|
|
pf = &PreviewFile{FilePath: fp}
|
|
} else {
|
|
pf, e = previewExcelDocument(path)
|
|
}
|
|
if e != nil {
|
|
return e
|
|
}
|
|
resp[idx] = pf
|
|
return nil
|
|
})
|
|
}
|
|
if err := eg.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func getAllFiles(path string) ([]string, error) {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !info.IsDir() {
|
|
if isHiddenFile(info.Name()) {
|
|
return nil, nil
|
|
}
|
|
return []string{path}, nil
|
|
}
|
|
|
|
var files []string
|
|
if err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isHiddenFile(info.Name()) {
|
|
if info.IsDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if !info.IsDir() {
|
|
files = append(files, path)
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return files, nil
|
|
}
|
|
|
|
func previewExcelDocument(filePath string) (*PreviewFile, error) {
|
|
f, err := excelize.OpenFile(filePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
pf := &PreviewFile{
|
|
FilePath: filePath,
|
|
SingleFilePreviews: nil,
|
|
}
|
|
for _, sheetName := range f.GetSheetList() {
|
|
sfp, err := parseSheet(f, sheetName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pf.SingleFilePreviews = append(pf.SingleFilePreviews, sfp)
|
|
}
|
|
|
|
return pf, nil
|
|
}
|
|
|
|
func parseSheet(f *excelize.File, sheetName string) (*SingleFilePreview, error) {
|
|
preview := &SingleFilePreview{
|
|
SheetName: sheetName,
|
|
Header: make([]*ExcelCell, 0),
|
|
Content: make([][]*ExcelCell, 0),
|
|
MergedCells: make([]*ExcelCell, 0),
|
|
}
|
|
mcs := make([][][]int, 20)
|
|
mergedCells, err := f.GetMergeCells(sheetName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, cell := range mergedCells {
|
|
lcol, lrow, err := excelize.CellNameToCoordinates(cell.GetStartAxis())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if lrow >= 20 {
|
|
continue
|
|
}
|
|
|
|
rcol, rrow, err := excelize.CellNameToCoordinates(cell.GetEndAxis())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
preview.MergedCells = append(preview.MergedCells, &ExcelCell{
|
|
Address: cell[0],
|
|
Value: cell.GetCellValue(),
|
|
})
|
|
mcs[lrow] = append(mcs[lrow], []int{lcol, lrow, rcol, rrow})
|
|
}
|
|
|
|
rowIter, err := f.Rows(sheetName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
i := 0
|
|
for rowIter.Next() && i < 20 {
|
|
values, err := rowIter.Columns()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rowValues []*ExcelCell
|
|
for j, val := range values {
|
|
col, row := j+1, i+1
|
|
skip := false
|
|
if r := mcs[i]; len(r) > 0 {
|
|
for _, coverage := range r {
|
|
if col >= coverage[0] && row >= coverage[1] && col <= coverage[2] && row <= coverage[3] {
|
|
skip = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if skip {
|
|
continue
|
|
}
|
|
addr, err := excelize.CoordinatesToCellName(col, row)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rowValues = append(rowValues, &ExcelCell{Address: addr, Value: val})
|
|
}
|
|
if i == 0 {
|
|
preview.Header = rowValues
|
|
} else {
|
|
preview.Content = append(preview.Content, rowValues)
|
|
}
|
|
i++
|
|
}
|
|
|
|
return preview, nil
|
|
}
|
|
|
|
func isHiddenFile(name string) bool {
|
|
return len(name) > 0 && name[0] == '.'
|
|
}
|