Bläddra i källkod

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

xuri 5 år sedan
förälder
incheckning
5221729bc3
5 ändrade filer med 106 tillägg och 76 borttagningar
  1. 0 3
      cell.go
  2. 63 57
      col.go
  3. 40 13
      col_test.go
  4. 3 0
      lib.go
  5. 0 3
      rows.go

+ 0 - 3
cell.go

@@ -41,9 +41,6 @@ var rwMutex sync.RWMutex
 func (f *File) GetCellValue(sheet, axis string) (string, error) {
 func (f *File) GetCellValue(sheet, axis string) (string, error) {
 	return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
 	return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
 		val, err := c.getValueFrom(f, f.sharedStringsReader())
 		val, err := c.getValueFrom(f, f.sharedStringsReader())
-		if err != nil {
-			return val, false, err
-		}
 		return val, true, err
 		return val, true, err
 	})
 	})
 }
 }

+ 63 - 57
col.go

@@ -13,8 +13,8 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/xml"
 	"encoding/xml"
 	"errors"
 	"errors"
-	"fmt"
 	"math"
 	"math"
+	"strconv"
 	"strings"
 	"strings"
 
 
 	"github.com/mohae/deepcopy"
 	"github.com/mohae/deepcopy"
@@ -34,10 +34,11 @@ type Cols struct {
 	sheet                                string
 	sheet                                string
 	cols                                 []xlsxCols
 	cols                                 []xlsxCols
 	f                                    *File
 	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")
 //    cols, err := f.Cols("Sheet1")
 //    if err != nil {
 //    if err != nil {
@@ -60,29 +61,17 @@ func (f *File) GetCols(sheet string) ([][]string, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-
 	results := make([][]string, 0, 64)
 	results := make([][]string, 0, 64)
-
 	for cols.Next() {
 	for cols.Next() {
-		if cols.Error() != nil {
-			break
-		}
-
-		col, err := cols.Rows()
-		if err != nil {
-			break
-		}
-
+		col, _ := cols.Rows()
 		results = append(results, col)
 		results = append(results, col)
 	}
 	}
-
 	return results, nil
 	return results, nil
 }
 }
 
 
 // Next will return true if the next col element is found.
 // Next will return true if the next col element is found.
 func (cols *Cols) Next() bool {
 func (cols *Cols) Next() bool {
 	cols.curCol++
 	cols.curCol++
-
 	return cols.curCol <= cols.totalCol
 	return cols.curCol <= cols.totalCol
 }
 }
 
 
@@ -91,27 +80,53 @@ func (cols *Cols) Error() error {
 	return cols.err
 	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) {
 func (cols *Cols) Rows() ([]string, error) {
 	var (
 	var (
-		err  error
-		rows []string
+		err              error
+		inElement        string
+		cellCol, cellRow int
+		rows             []string
 	)
 	)
-
 	if cols.stashCol >= cols.curCol {
 	if cols.stashCol >= cols.curCol {
 		return rows, err
 		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
 	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")
 //    cols, err := f.Cols("Sheet1")
 //    if err != nil {
 //    if err != nil {
@@ -134,60 +149,51 @@ func (f *File) Cols(sheet string) (*Cols, error) {
 	if !ok {
 	if !ok {
 		return nil, ErrSheetNotExist{sheet}
 		return nil, ErrSheetNotExist{sheet}
 	}
 	}
-
 	if f.Sheet[name] != nil {
 	if f.Sheet[name] != nil {
 		output, _ := xml.Marshal(f.Sheet[name])
 		output, _ := xml.Marshal(f.Sheet[name])
 		f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
 		f.saveFileList(name, replaceRelationshipsNameSpaceBytes(output))
 	}
 	}
-
 	var (
 	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 {
 	for {
 		token, _ := decoder.Token()
 		token, _ := decoder.Token()
 		if token == nil {
 		if token == nil {
 			break
 			break
 		}
 		}
-
 		switch startElement := token.(type) {
 		switch startElement := token.(type) {
 		case xml.StartElement:
 		case xml.StartElement:
 			inElement = startElement.Name.Local
 			inElement = startElement.Name.Local
-			if inElement == "dimension" {
-				colsNum = make([]int, 0)
-				rowsNum = make([]int, 0)
-
+			if inElement == "row" {
 				for _, attr := range startElement.Attr {
 				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.f = f
 	cols.sheet = trimSheetName(sheet)
 	cols.sheet = trimSheetName(sheet)
-	cols.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
-
 	return &cols, nil
 	return &cols, nil
 }
 }
 
 

+ 40 - 13
col_test.go

@@ -1,7 +1,6 @@
 package excelize
 package excelize
 
 
 import (
 import (
-	"bytes"
 	"path/filepath"
 	"path/filepath"
 	"testing"
 	"testing"
 
 
@@ -61,7 +60,7 @@ func TestCols(t *testing.T) {
 func TestColumnsIterator(t *testing.T) {
 func TestColumnsIterator(t *testing.T) {
 	const (
 	const (
 		sheet2         = "Sheet2"
 		sheet2         = "Sheet2"
-		expectedNumCol = 4
+		expectedNumCol = 9
 	)
 	)
 
 
 	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
 	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
@@ -82,29 +81,57 @@ func TestColumnsIterator(t *testing.T) {
 	for _, cell := range cells {
 	for _, cell := range cells {
 		assert.NoError(t, f.SetCellValue("Sheet1", cell, 1))
 		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")
 	cols, err = f.Cols("Sheet1")
 	require.NoError(t, err)
 	require.NoError(t, err)
 
 
 	colCount = 0
 	colCount = 0
 	for cols.Next() {
 	for cols.Next() {
 		colCount++
 		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) {
 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) {
 	if !assert.NoError(t, err) {
 		t.FailNow()
 		t.FailNow()
 	}
 	}
-	_, err = xlsx.Cols("SheetN")
+	_, err = f.GetCols("SheetN")
 	assert.EqualError(t, err, "sheet SheetN is not exist")
 	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) {
 func TestColsRows(t *testing.T) {
@@ -112,7 +139,7 @@ func TestColsRows(t *testing.T) {
 	f.NewSheet("Sheet1")
 	f.NewSheet("Sheet1")
 
 
 	cols, err := f.Cols("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))
 	assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
 	f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
 	f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
@@ -126,7 +153,7 @@ func TestColsRows(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	// Test if token is nil
 	// Test if token is nil
-	cols.decoder = f.xmlNewDecoder(bytes.NewReader(nil))
+	cols.decoder = nil
 	_, err = cols.Rows()
 	_, err = cols.Rows()
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
 }

+ 3 - 0
lib.go

@@ -152,6 +152,9 @@ func ColumnNumberToName(num int) (string, error) {
 	if num < 1 {
 	if num < 1 {
 		return "", fmt.Errorf("incorrect column number %d", num)
 		return "", fmt.Errorf("incorrect column number %d", num)
 	}
 	}
+	if num > TotalColumns {
+		return "", fmt.Errorf("column number exceeds maximum limit")
+	}
 	var col string
 	var col string
 	for num > 0 {
 	for num > 0 {
 		col = string((num-1)%26+65) + col
 		col = string((num-1)%26+65) + col

+ 0 - 3
rows.go

@@ -46,9 +46,6 @@ func (f *File) GetRows(sheet string) ([][]string, error) {
 	}
 	}
 	results := make([][]string, 0, 64)
 	results := make([][]string, 0, 64)
 	for rows.Next() {
 	for rows.Next() {
-		if rows.Error() != nil {
-			break
-		}
 		row, err := rows.Columns()
 		row, err := rows.Columns()
 		if err != nil {
 		if err != nil {
 			break
 			break