已验证 提交 5221729b 编写于 作者: xurime's avatar xurime

make columns iterator read cell streamingly and add max column limit on ColumnNumberToName

上级 dcb772d6
......@@ -41,9 +41,6 @@ var rwMutex sync.RWMutex
func (f *File) GetCellValue(sheet, axis string) (string, error) {
return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
val, err := c.getValueFrom(f, f.sharedStringsReader())
if err != nil {
return val, false, err
}
return val, true, err
})
}
......
......@@ -13,8 +13,8 @@ import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"math"
"strconv"
"strings"
"github.com/mohae/deepcopy"
......@@ -34,10 +34,11 @@ type Cols struct {
sheet string
cols []xlsxCols
f *File
decoder *xml.Decoder
sheetXML []byte
}
// GetCols return all the columns in a sheet by given worksheet name (case sensitive). For example:
// GetCols return all the columns in a sheet by given worksheet name (case
// sensitive). For example:
//
// cols, err := f.Cols("Sheet1")
// if err != nil {
......@@ -60,29 +61,17 @@ func (f *File) GetCols(sheet string) ([][]string, error) {
if err != nil {
return nil, err
}
results := make([][]string, 0, 64)
for cols.Next() {
if cols.Error() != nil {
break
}
col, err := cols.Rows()
if err != nil {
break
}
col, _ := cols.Rows()
results = append(results, col)
}
return results, nil
}
// Next will return true if the next col element is found.
func (cols *Cols) Next() bool {
cols.curCol++
return cols.curCol <= cols.totalCol
}
......@@ -91,27 +80,53 @@ func (cols *Cols) Error() error {
return cols.err
}
// Rows return the current column's row values
// Rows return the current column's row values.
func (cols *Cols) Rows() ([]string, error) {
var (
err error
rows []string
err error
inElement string
cellCol, cellRow int
rows []string
)
if cols.stashCol >= cols.curCol {
return rows, err
}
for i := 1; i <= cols.totalRow; i++ {
colName, _ := ColumnNumberToName(cols.curCol)
val, _ := cols.f.GetCellValue(cols.sheet, fmt.Sprintf("%s%d", colName, i))
rows = append(rows, val)
d := cols.f.sharedStringsReader()
decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "c" {
for _, attr := range startElement.Attr {
if attr.Name.Local == "r" {
if cellCol, cellRow, err = CellNameToCoordinates(attr.Value); err != nil {
return rows, err
}
blank := cellRow - len(rows)
for i := 1; i < blank; i++ {
rows = append(rows, "")
}
if cellCol == cols.curCol {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, &startElement)
val, _ := colCell.getValueFrom(cols.f, d)
rows = append(rows, val)
}
}
}
}
}
}
return rows, nil
}
// Cols returns a columns iterator, used for streaming/reading data for a worksheet with a large data. For example:
// Cols returns a columns iterator, used for streaming/reading data for a
// worksheet with a large data. For example:
//
// cols, err := f.Cols("Sheet1")
// if err != nil {
......@@ -134,60 +149,51 @@ func (f *File) Cols(sheet string) (*Cols, error) {
if !ok {
return nil, ErrSheetNotExist{sheet}
}
if f.Sheet[name] != nil {
output, _ := xml.Marshal(f.Sheet[name])
f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
}
var (
inElement string
cols Cols
colsNum, rowsNum []int
inElement string
cols Cols
cellCol int
err error
)
decoder := f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
cols.sheetXML = f.readXML(name)
decoder := f.xmlNewDecoder(bytes.NewReader(cols.sheetXML))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "dimension" {
colsNum = make([]int, 0)
rowsNum = make([]int, 0)
if inElement == "row" {
for _, attr := range startElement.Attr {
if attr.Name.Local == "ref" {
sheetCoordinates := attr.Value
if i := strings.Index(sheetCoordinates, ":"); i <= -1 {
return &cols, errors.New("Sheet coordinates are wrong")
if attr.Name.Local == "r" {
if cols.totalRow, err = strconv.Atoi(attr.Value); err != nil {
return &cols, err
}
coordinates := strings.Split(sheetCoordinates, ":")
for _, coordinate := range coordinates {
c, r, _ := SplitCellName(coordinate)
columnNum, _ := ColumnNameToNumber(c)
colsNum = append(colsNum, columnNum)
rowsNum = append(rowsNum, r)
}
}
}
if inElement == "c" {
for _, attr := range startElement.Attr {
if attr.Name.Local == "r" {
if cellCol, _, err = CellNameToCoordinates(attr.Value); err != nil {
return &cols, err
}
if cellCol > cols.totalCol {
cols.totalCol = cellCol
}
}
}
cols.totalCol = colsNum[1] - (colsNum[0] - 1)
cols.totalRow = rowsNum[1] - (rowsNum[0] - 1)
}
default:
}
}
cols.f = f
cols.sheet = trimSheetName(sheet)
cols.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
return &cols, nil
}
......
package excelize
import (
"bytes"
"path/filepath"
"testing"
......@@ -61,7 +60,7 @@ func TestCols(t *testing.T) {
func TestColumnsIterator(t *testing.T) {
const (
sheet2 = "Sheet2"
expectedNumCol = 4
expectedNumCol = 9
)
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
......@@ -82,29 +81,57 @@ func TestColumnsIterator(t *testing.T) {
for _, cell := range cells {
assert.NoError(t, f.SetCellValue("Sheet1", cell, 1))
}
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
Dimension: &xlsxDimension{
Ref: "C2:D4",
},
}
cols, err = f.Cols("Sheet1")
require.NoError(t, err)
colCount = 0
for cols.Next() {
colCount++
require.True(t, colCount <= 2, "colCount is greater than expected")
require.True(t, colCount <= 4, "colCount is greater than expected")
}
assert.Equal(t, 2, colCount)
assert.Equal(t, 4, colCount)
}
func TestColsError(t *testing.T) {
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = f.Cols("SheetN")
assert.EqualError(t, err, "sheet SheetN is not exist")
}
func TestGetColsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
_, err = xlsx.Cols("SheetN")
_, err = f.GetCols("SheetN")
assert.EqualError(t, err, "sheet SheetN is not exist")
f = NewFile()
delete(f.Sheet, "xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)
f.checked = nil
_, err = f.GetCols("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
f = NewFile()
delete(f.Sheet, "xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="2"><c r="A" t="str"><v>B</v></c></row></sheetData></worksheet>`)
f.checked = nil
_, err = f.GetCols("Sheet1")
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
f = NewFile()
cols, err := f.Cols("Sheet1")
assert.NoError(t, err)
cols.totalRow = 2
cols.totalCol = 2
cols.curCol = 1
cols.decoder = []byte(`<worksheet><sheetData><row r="1"><c r="A" t="str"><v>A</v></c></row></sheetData></worksheet>`)
_, err = cols.Rows()
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}
func TestColsRows(t *testing.T) {
......@@ -112,7 +139,7 @@ func TestColsRows(t *testing.T) {
f.NewSheet("Sheet1")
cols, err := f.Cols("Sheet1")
assert.EqualError(t, err, `Sheet coordinates are wrong`)
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
......@@ -126,7 +153,7 @@ func TestColsRows(t *testing.T) {
assert.NoError(t, err)
// Test if token is nil
cols.decoder = f.xmlNewDecoder(bytes.NewReader(nil))
cols.decoder = nil
_, err = cols.Rows()
assert.NoError(t, err)
}
......
......@@ -152,6 +152,9 @@ func ColumnNumberToName(num int) (string, error) {
if num < 1 {
return "", fmt.Errorf("incorrect column number %d", num)
}
if num > TotalColumns {
return "", fmt.Errorf("column number exceeds maximum limit")
}
var col string
for num > 0 {
col = string((num-1)%26+65) + col
......
......@@ -46,9 +46,6 @@ func (f *File) GetRows(sheet string) ([][]string, error) {
}
results := make([][]string, 0, 64)
for rows.Next() {
if rows.Error() != nil {
break
}
row, err := rows.Columns()
if err != nil {
break
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册