mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-10-12 02:16:39 +00:00
feat(parser): support genuine BIFF8 XLS workbooks (#20573)
This commit is contained in:
@@ -48,6 +48,7 @@ require (
|
||||
github.com/infiniflow/infinity-go-sdk v0.0.0-00010101000000-000000000000
|
||||
github.com/infiniflow/onnxruntime_go v1.29.0
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/jloor/go-excel-reader v0.3.0
|
||||
github.com/kaptinlin/jsonrepair v0.4.8
|
||||
github.com/larksuite/oapi-sdk-go/v3 v3.9.9
|
||||
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728
|
||||
|
||||
@@ -375,6 +375,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jloor/go-excel-reader v0.3.0 h1:CczAwTDGIZgMu7wP718Im3yONlOTN+asdb+cp7yk7As=
|
||||
github.com/jloor/go-excel-reader v0.3.0/go.mod h1:A3hRAFOOAy82/j+pVmX/f/dVs4XEfDCT3aMVw+HNnGw=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
|
||||
@@ -17,10 +17,17 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
excel "github.com/jloor/go-excel-reader"
|
||||
)
|
||||
|
||||
var compoundDocumentSignature = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}
|
||||
|
||||
type XLSParser struct {
|
||||
ParseMethod string
|
||||
OutputFormat string
|
||||
@@ -85,15 +92,25 @@ func (p *XLSParser) ParseWithResult(ctx context.Context, filename string, data [
|
||||
}
|
||||
}
|
||||
|
||||
mediaBudget := newEmbeddedMediaBudget()
|
||||
items, warnings, sheetsCount, err := parseXLSXBytes(data, mediaBudget)
|
||||
var (
|
||||
items []map[string]any
|
||||
warnings []string
|
||||
sheetsCount int
|
||||
err error
|
||||
)
|
||||
if bytes.HasPrefix(data, compoundDocumentSignature) {
|
||||
items, sheetsCount, err = parseBIFF8Bytes(ctx, data)
|
||||
} else {
|
||||
mediaBudget := newEmbeddedMediaBudget()
|
||||
items, warnings, sheetsCount, err = parseXLSXBytes(data, mediaBudget)
|
||||
warnings = append(warnings, mediaBudget.warnings()...)
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return ParseResult{Err: err}
|
||||
}
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("xls parse: %w", err)}
|
||||
}
|
||||
warnings = append(warnings, mediaBudget.warnings()...)
|
||||
|
||||
return ParseResult{
|
||||
OutputFormat: spreadsheetOutputFormat,
|
||||
@@ -102,3 +119,82 @@ func (p *XLSParser) ParseWithResult(ctx context.Context, filename string, data [
|
||||
Warnings: warnings,
|
||||
}
|
||||
}
|
||||
|
||||
func parseBIFF8Bytes(ctx context.Context, data []byte) ([]map[string]any, int, error) {
|
||||
reader, err := excel.OpenXLS(bytes.NewReader(data), excel.WithErrorValues(true))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("open BIFF workbook: %w", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
sheetCount := reader.SheetCount()
|
||||
items := make([]map[string]any, 0, sheetCount)
|
||||
for sheetIndex := 0; reader.NextSheet(); sheetIndex++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, sheetCount, err
|
||||
}
|
||||
records, dataRows, headerRow := readBIFF8Records(ctx, reader)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, sheetCount, err
|
||||
}
|
||||
items = append(items, buildSheetItems(records, reader.SheetName(), sheetIndex+1, headerRow, dataRows, nil)...)
|
||||
}
|
||||
if err := reader.Err(); err != nil {
|
||||
return nil, sheetCount, fmt.Errorf("read BIFF workbook: %w", err)
|
||||
}
|
||||
return items, sheetCount, nil
|
||||
}
|
||||
|
||||
func readBIFF8Records(ctx context.Context, reader excel.Reader) ([][]string, []int, int) {
|
||||
type numberedRow struct {
|
||||
number int
|
||||
cells []string
|
||||
}
|
||||
|
||||
rows := make([]numberedRow, 0)
|
||||
for reader.Read() {
|
||||
if ctx.Err() != nil {
|
||||
return nil, nil, 0
|
||||
}
|
||||
cells := make([]string, reader.FieldCount())
|
||||
for col := range cells {
|
||||
cells[col] = spreadsheetCellString(reader.GetValue(col))
|
||||
}
|
||||
if rowIsEmpty(cells) {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, numberedRow{number: reader.RowIndex() + 1, cells: cells})
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return nil, nil, 0
|
||||
}
|
||||
|
||||
records := make([][]string, 0, len(rows))
|
||||
dataRows := make([]int, 0, len(rows)-1)
|
||||
records = append(records, rows[0].cells)
|
||||
for _, row := range rows[1:] {
|
||||
records = append(records, row.cells)
|
||||
dataRows = append(dataRows, row.number)
|
||||
}
|
||||
return cleanIllegalControlChars(records), dataRows, rows[0].number
|
||||
}
|
||||
|
||||
func spreadsheetCellString(value any) string {
|
||||
switch value := value.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case time.Time:
|
||||
return value.Format(time.RFC3339)
|
||||
default:
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
}
|
||||
|
||||
func rowIsEmpty(row []string) bool {
|
||||
for _, cell := range row {
|
||||
if strings.TrimSpace(cell) != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -33,6 +36,175 @@ func createTestExcelBytes(t *testing.T) []byte {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// genuineBIFF8FixtureBase64 was generated with xlwt 1.3.0. It contains two
|
||||
// worksheets and is an OLE compound document with a BIFF8 Workbook stream,
|
||||
// rather than OOXML bytes renamed with an .xls extension.
|
||||
const genuineBIFF8FixtureBase64 = `
|
||||
0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAACQAAAAAAAAAAEAAA/v///wAAAAD+////AAAAAAgAAAD/////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////8JCBAAAAYFALsNzAcAAAAABgAAAOEAAgCwBMEA
|
||||
AgAAAOIAAABcAHAATm9uZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEIAAgCwBGEBAgAAAD0BBAABAAIAnAACAA4AGQACAAAAEgACAAAAYwACAAAAEwACAAAArwECAAAA
|
||||
vAECAAAAQAACAAAAjQACAAAAPQASAOABWgDPP04qOAAAAAAAAQBYAiIAAgAAAA4AAgABALcBAgAAANoAAgAAADEAFQDIAAAA/3+QAQAAAAABAAUAQXJpYWwx
|
||||
ABUAyAAAAP9/kAEAAAAAAQAFAEFyaWFsMQAVAMgAAAD/f5ABAAAAAAEABQBBcmlhbDEAFQDIAAAA/3+QAQAAAAABAAUAQXJpYWwxABUAyAAAAP9/kAEAAAAA
|
||||
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
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAP7////9/////v//////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////9SAG8AbwB0ACAA
|
||||
RQBuAHQAcgB5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAFAf//////////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAP7///8AAAAAAAAAAFcAbwByAGsAYgBvAG8AawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAASAAIB////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAD+////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//////////
|
||||
/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7///8AAAAAAAAAAA==
|
||||
`
|
||||
|
||||
// formulaErrorBIFF8FixtureGzipBase64 was generated with xlwt 1.3.0 and
|
||||
// patched to carry the cached BIFF8 error value for the formula 1/0. It is
|
||||
// compressed to keep the mostly empty OLE container compact in source.
|
||||
const formulaErrorBIFF8FixtureGzipBase64 = `
|
||||
H4sIAAAAAAAC/+1YPWgUQRT+ZnP/JJe9eBES8TgCRhPTiI1NslGIqQzRRhFBL+YKSbiT1UYbo/FKQbBSbAJpbKIW/qGFdhZCRAtBEO60tBIULJLbvPl2Vy6JRQ40GJlvmTfv3ps3827mzZudfbuYqc497K5hDYbQgrqXRKxBpqQkwx82RO95mg3rhBTPYEshmZCFjEXxvO1NXK+hXu8aLDyIvBIKfJZyCucxVi4V85uIQ/ShoLQPg0IV7ookjS561UF6lnQb6X22fEE6TMkN0kFpW1UnseiM9R8IoviE1UNdGrrfJ7T5SMk+dOK1juIrN5XfNoqD7rnC9L+pyEVaMQ9Zt9FiqegWpqvIygLO44eXB76HO/Vl3sg3V64g8p+r5fHfyG9ZEWAG3hkGeEUC8lGLvwlHXLfsXliSLKuCxBuFL0zpJMxNa6/atG0M5lahk2gnn2FI25KWl+59e3dkYtw5TckME7Xf6y7tATxc1RZinKbG+qXvJ7+X9Bp73UG+mzQrXkndO94ZMIdn2eY6tb0yzn7ivbO7gd8jfOXr0ae5yhenT/iF0drl7MIHZw49crxMir1+ZjGgBtSd2xrPnLBWwdb/RNq1Lg0kLDvw3QvOrHYsI0U2Q6pbqDUtYthJmZ6xOA8xXxVHTiEn8ztkdeAxZ2W44TxMwcDAwMDAwMDAwGBrQQWv+/reEfGvGbxOxIPvOstS6uYzyX+LYyjLc1EupiMoSe3iUlPxsx1RFfalNmgTfi/UOC6ju5jCBP2Yajp+5TqoGv/Phg3tP7eFmh2/3oyff3n8FQEt9k4AFgAA
|
||||
`
|
||||
|
||||
func TestXLSParser_PreservesFormulaErrors(t *testing.T) {
|
||||
compressed, err := base64.StdEncoding.DecodeString(strings.TrimSpace(formulaErrorBIFF8FixtureGzipBase64))
|
||||
if err != nil {
|
||||
t.Fatalf("decode formula-error fixture: %v", err)
|
||||
}
|
||||
zr, err := gzip.NewReader(bytes.NewReader(compressed))
|
||||
if err != nil {
|
||||
t.Fatalf("open formula-error fixture: %v", err)
|
||||
}
|
||||
data, err := io.ReadAll(zr)
|
||||
if err != nil {
|
||||
t.Fatalf("decompress formula-error fixture: %v", err)
|
||||
}
|
||||
if err := zr.Close(); err != nil {
|
||||
t.Fatalf("close formula-error fixture: %v", err)
|
||||
}
|
||||
|
||||
p, err := NewXLSParser("excelize")
|
||||
if err != nil {
|
||||
t.Fatalf("NewXLSParser failed: %v", err)
|
||||
}
|
||||
res := p.ParseWithResult(t.Context(), "formula-error.xls", data)
|
||||
if res.Err != nil {
|
||||
t.Fatalf("ParseWithResult failed: %v", res.Err)
|
||||
}
|
||||
if len(res.JSON) != 1 {
|
||||
t.Fatalf("res.JSON len = %d, want 1", len(res.JSON))
|
||||
}
|
||||
text, _ := res.JSON[0]["text"].(string)
|
||||
if !strings.Contains(text, "<tr><td>#DIV/0!</td></tr>") {
|
||||
t.Fatalf("table markup = %q, want formula error row", text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXLSParser_GenuineBIFF8SpreadsheetJSONOutput(t *testing.T) {
|
||||
data, err := base64.StdEncoding.DecodeString(genuineBIFF8FixtureBase64)
|
||||
if err != nil {
|
||||
t.Fatalf("decode BIFF8 fixture: %v", err)
|
||||
}
|
||||
p, err := NewXLSParser("excelize")
|
||||
if err != nil {
|
||||
t.Fatalf("NewXLSParser failed: %v", err)
|
||||
}
|
||||
|
||||
res := p.ParseWithResult(t.Context(), "inventory.xls", data)
|
||||
if res.Err != nil {
|
||||
t.Fatalf("ParseWithResult failed: %v", res.Err)
|
||||
}
|
||||
if res.OutputFormat != spreadsheetOutputFormat {
|
||||
t.Fatalf("res.OutputFormat = %q, want %q", res.OutputFormat, spreadsheetOutputFormat)
|
||||
}
|
||||
if got := res.File["format"]; got != "xls" {
|
||||
t.Fatalf("res.File[format] = %v, want xls", got)
|
||||
}
|
||||
if got := res.File["sheets"]; got != 2 {
|
||||
t.Fatalf("res.File[sheets] = %v, want 2", got)
|
||||
}
|
||||
if len(res.JSON) != 2 {
|
||||
t.Fatalf("res.JSON len = %d, want one table for each sheet", len(res.JSON))
|
||||
}
|
||||
|
||||
first := res.JSON[0]
|
||||
text, _ := first["text"].(string)
|
||||
if !strings.Contains(text, "<caption>Inventory</caption>") ||
|
||||
!strings.Contains(text, "<tr><th>Name</th><th>Count</th></tr>") ||
|
||||
!strings.Contains(text, "<tr><td>Apples</td><td>3</td></tr>") {
|
||||
t.Fatalf("first table markup = %q, want BIFF8 sheet data", text)
|
||||
}
|
||||
if first["sheet_index"] != 1 {
|
||||
t.Fatalf("first sheet_index = %v, want 1", first["sheet_index"])
|
||||
}
|
||||
positions, ok := first["positions"].([][]float64)
|
||||
if !ok || len(positions) != 3 {
|
||||
t.Fatalf("first positions = %#v, want header plus two data rows", first["positions"])
|
||||
}
|
||||
if got, want := positions[1], []float64{1, 2, 2, 1, 2}; !slices.Equal(got, want) {
|
||||
t.Fatalf("first data row position = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := positions[2], []float64{1, 3, 3, 1, 2}; !slices.Equal(got, want) {
|
||||
t.Fatalf("last row position = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
second := res.JSON[1]
|
||||
secondText, _ := second["text"].(string)
|
||||
if !strings.Contains(secondText, "<caption>Notes</caption>") ||
|
||||
!strings.Contains(secondText, "<tr><td>Legacy</td><td>BIFF8</td></tr>") {
|
||||
t.Fatalf("second table markup = %q, want second BIFF8 sheet data", secondText)
|
||||
}
|
||||
if second["sheet_index"] != 2 {
|
||||
t.Fatalf("second sheet_index = %v, want 2", second["sheet_index"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestXLSParser_SpreadsheetJSONOutput(t *testing.T) {
|
||||
data := createTestExcelBytes(t)
|
||||
p, err := NewXLSParser("excelize")
|
||||
|
||||
Reference in New Issue
Block a user