Skip to content

Commit d802e92

Browse files
dengyidengyi
authored andcommitted
feat: add IterateColumns method for improved performance
1 parent 5d2ee53 commit d802e92

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

rows.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,95 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) {
197197
return rowIterator.cells, rowIterator.err
198198
}
199199

200+
// ColumnIteratorFunc defines a function type for iterating over columns in a row.
201+
// The function receives the column index (0-based) and the cell value.
202+
// Return a non-nil error to stop the iteration.
203+
type ColumnIteratorFunc func(index int, cell string) error
204+
205+
// The iterator function receives the column index (0-based) and the cell value.
206+
// If the iterator function returns a non-nil error, the iteration stops and
207+
// that error is returned.
208+
//
209+
// Example usage:
210+
//
211+
// err := rows.IterateColumns(func(index int, cell string) error {
212+
// fmt.Printf("Column %d: %s\n", index, cell)
213+
// return nil
214+
// })
215+
func (rows *Rows) IterateColumns(f ColumnIteratorFunc, opts ...Options) error {
216+
if rows.curRow > rows.seekRow {
217+
return nil
218+
}
219+
var rowIterator rowXMLIterator
220+
var token xml.Token
221+
rows.rawCellValue = rows.f.getOptions(opts...).RawCellValue
222+
if rows.sst, rowIterator.err = rows.f.sharedStringsReader(); rowIterator.err != nil {
223+
return rowIterator.err
224+
}
225+
columnIndex := 0
226+
for {
227+
if rows.token != nil {
228+
token = rows.token
229+
} else if token, _ = rows.decoder.Token(); token == nil {
230+
break
231+
}
232+
switch xmlElement := token.(type) {
233+
case xml.StartElement:
234+
rowIterator.inElement = xmlElement.Name.Local
235+
if rowIterator.inElement == "row" {
236+
rowNum := 0
237+
if rowNum, rowIterator.err = attrValToInt("r", xmlElement.Attr); rowNum != 0 {
238+
rows.curRow = rowNum
239+
} else if rows.token == nil {
240+
rows.curRow++
241+
}
242+
rows.token = token
243+
rows.seekRowOpts = extractRowOpts(xmlElement.Attr)
244+
if rows.curRow > rows.seekRow {
245+
rows.token = nil
246+
return nil
247+
}
248+
}
249+
if rowIterator.inElement == "c" {
250+
rowIterator.cellCol++
251+
colCell := xlsxC{}
252+
colCell.cellXMLHandler(rows.decoder, &xmlElement)
253+
if colCell.R != "" {
254+
if rowIterator.cellCol, _, rowIterator.err = CellNameToCoordinates(colCell.R); rowIterator.err != nil {
255+
rows.token = nil
256+
return rowIterator.err
257+
}
258+
}
259+
for columnIndex < rowIterator.cellCol-1 {
260+
if err := f(columnIndex, ""); err != nil {
261+
rows.token = nil
262+
return err
263+
}
264+
columnIndex++
265+
}
266+
if val, _ := colCell.getValueFrom(rows.f, rows.sst, rows.rawCellValue); val != "" || colCell.F != nil {
267+
if err := f(columnIndex, val); err != nil {
268+
rows.token = nil
269+
return err
270+
}
271+
} else {
272+
if err := f(columnIndex, ""); err != nil {
273+
rows.token = nil
274+
return err
275+
}
276+
}
277+
columnIndex++
278+
}
279+
rows.token = nil
280+
case xml.EndElement:
281+
if xmlElement.Name.Local == "sheetData" {
282+
return nil
283+
}
284+
}
285+
}
286+
return nil
287+
}
288+
200289
// extractRowOpts extract row element attributes.
201290
func extractRowOpts(attrs []xml.Attr) RowOpts {
202291
rowOpts := RowOpts{Height: defaultRowHeight}

rows_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,55 @@ func TestRowsError(t *testing.T) {
149149
assert.NoError(t, f.Close())
150150
}
151151

152+
func TestIterateColumns(t *testing.T) {
153+
f := NewFile()
154+
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
155+
assert.NoError(t, f.SetCellValue("Sheet1", "C1", "C1"))
156+
assert.NoError(t, f.SetCellValue("Sheet1", "D1", "D1"))
157+
rows, err := f.Rows("Sheet1")
158+
assert.NoError(t, err)
159+
defer rows.Close()
160+
161+
assert.True(t, rows.Next())
162+
var iteratedCells []string
163+
err = rows.IterateColumns(func(index int, cell string) error {
164+
iteratedCells = append(iteratedCells, cell)
165+
return nil
166+
})
167+
assert.NoError(t, err)
168+
rows2, err := f.Rows("Sheet1")
169+
assert.NoError(t, err)
170+
defer rows2.Close()
171+
assert.True(t, rows2.Next())
172+
columns, err := rows2.Columns()
173+
assert.NoError(t, err)
174+
assert.Equal(t, columns, iteratedCells)
175+
assert.Equal(t, []string{"A1", "", "C1", "D1"}, iteratedCells)
176+
}
177+
178+
func TestIterateColumnsEarlyTermination(t *testing.T) {
179+
f := NewFile()
180+
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
181+
assert.NoError(t, f.SetCellValue("Sheet1", "B1", "B1"))
182+
assert.NoError(t, f.SetCellValue("Sheet1", "C1", "C1"))
183+
184+
rows, err := f.Rows("Sheet1")
185+
assert.NoError(t, err)
186+
defer rows.Close()
187+
188+
assert.True(t, rows.Next())
189+
var collectedCells []string
190+
err = rows.IterateColumns(func(index int, cell string) error {
191+
collectedCells = append(collectedCells, cell)
192+
if index == 1 {
193+
return fmt.Errorf("stop") // stop at second column
194+
}
195+
return nil
196+
})
197+
assert.EqualError(t, err, "stop")
198+
assert.Equal(t, []string{"A1", "B1"}, collectedCells)
199+
}
200+
152201
func TestRowHeight(t *testing.T) {
153202
f := NewFile()
154203
sheet1 := f.GetSheetName(0)

0 commit comments

Comments
 (0)