Skip to content

Commit ea542a0

Browse files
committed
qax-os#1402 Get CountRows from sheet
1 parent dde6b9c commit ea542a0

File tree

2 files changed

+135
-4
lines changed

2 files changed

+135
-4
lines changed

rows.go

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"io"
1919
"math"
2020
"os"
21+
"regexp"
2122
"strconv"
2223

2324
"github.com/mohae/deepcopy"
@@ -211,6 +212,15 @@ func (err ErrSheetNotExist) Error() string {
211212
return fmt.Sprintf("sheet %s does not exist", err.SheetName)
212213
}
213214

215+
// ErrCountRows defines an error of count rows
216+
type ErrCountRows struct {
217+
err error
218+
}
219+
220+
func (err ErrCountRows) Error() string {
221+
return fmt.Sprintf("wrong count rows: %s", err.err.Error())
222+
}
223+
214224
// rowXMLIterator defined runtime use field for the worksheet row SAX parser.
215225
type rowXMLIterator struct {
216226
err error
@@ -237,6 +247,56 @@ func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.Sta
237247
}
238248
}
239249

250+
// CountRows returns the number of rows in the worksheet.
251+
// if return -1, that row not found
252+
func (f *File) CountRows(sheet string) (int64, error) {
253+
name, ok := f.getSheetXMLPath(sheet)
254+
if !ok {
255+
return -1, ErrSheetNotExist{sheet}
256+
}
257+
258+
needClose, reader, tempFile, size, err := f.contentReader(name)
259+
if err != nil {
260+
return -1, ErrCountRows{fmt.Errorf("content reader: %v", err)}
261+
}
262+
if needClose && err == nil {
263+
defer tempFile.Close()
264+
}
265+
266+
var contentSize int64 = 1024
267+
var content = make([]byte, contentSize)
268+
var start int64
269+
if size-contentSize < 0 {
270+
start = 0
271+
} else {
272+
start = size - contentSize
273+
}
274+
275+
if _, err = reader.ReadAt(content, start); err != nil && err != io.EOF {
276+
return -1, ErrCountRows{fmt.Errorf("read at: %v", err)}
277+
}
278+
279+
indexStart := bytes.LastIndex(content, []byte(`<row`))
280+
if indexStart == -1 {
281+
return 0, ErrCountRows{fmt.Errorf("not found row tag")}
282+
}
283+
284+
indexStop := bytes.Index(content[indexStart:], []byte(`>`))
285+
if indexStop == -1 {
286+
return -1, ErrCountRows{fmt.Errorf("not found end row")}
287+
}
288+
indexStop = indexStart + indexStop
289+
290+
rFind := regexp.MustCompile(`r="(\d+)"`).Find(content[indexStart:indexStop])
291+
if len(rFind) == 0 {
292+
return -1, ErrCountRows{fmt.Errorf("not found row number")}
293+
}
294+
295+
countStr := string(rFind[3 : len(rFind)-1])
296+
297+
return strconv.ParseInt(countStr, 10, 64)
298+
}
299+
240300
// Rows returns a rows iterator, used for streaming reading data for a
241301
// worksheet with a large data. This function is concurrency safe. For
242302
// example:
@@ -326,19 +386,38 @@ func (f *File) getFromStringItem(index int) string {
326386
return f.getFromStringItem(index)
327387
}
328388

329-
// xmlDecoder creates XML decoder by given path in the zip from memory data
389+
type ReaderContent interface {
390+
io.Reader
391+
io.ReaderAt
392+
}
393+
394+
// contentReader returns reader by given path in the zip from memory data
330395
// or system temporary file.
331-
func (f *File) xmlDecoder(name string) (bool, *xml.Decoder, *os.File, error) {
396+
func (f *File) contentReader(name string) (bool, ReaderContent, *os.File, int64, error) {
332397
var (
333398
content []byte
334399
err error
335400
tempFile *os.File
336401
)
337402
if content = f.readXML(name); len(content) > 0 {
338-
return false, f.xmlNewDecoder(bytes.NewReader(content)), tempFile, err
403+
return false, bytes.NewReader(content), tempFile, int64(len(content)), err
339404
}
405+
340406
tempFile, err = f.readTemp(name)
341-
return true, f.xmlNewDecoder(tempFile), tempFile, err
407+
408+
fileStat, err := tempFile.Stat()
409+
if err != nil {
410+
return true, tempFile, tempFile, 0, fmt.Errorf("failed to get file stat: %w", err)
411+
}
412+
413+
return true, tempFile, tempFile, fileStat.Size(), err
414+
}
415+
416+
// xmlDecoder creates XML decoder by given path in the zip from memory data
417+
// or system temporary file.
418+
func (f *File) xmlDecoder(name string) (bool, *xml.Decoder, *os.File, error) {
419+
needClose, reader, tempFile, _, err := f.contentReader(name)
420+
return needClose, f.xmlNewDecoder(reader), tempFile, err
342421
}
343422

344423
// SetRowHeight provides a function to set the height of a single row. For

rows_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,3 +1113,55 @@ func trimSliceSpace(s []string) []string {
11131113
}
11141114
return s
11151115
}
1116+
1117+
func TestFile_CountRows(t *testing.T) {
1118+
type fields struct {
1119+
filename string
1120+
}
1121+
tests := []struct {
1122+
name string
1123+
fields fields
1124+
want int64
1125+
wantErr assert.ErrorAssertionFunc
1126+
}{{
1127+
name: "BadWorkbook.xlsx",
1128+
fields: fields{filename: filepath.Join("test", "BadWorkbook.xlsx")},
1129+
want: -1,
1130+
wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool {
1131+
return assert.Error(t, err)
1132+
},
1133+
}, {
1134+
name: "Book1.xlsx",
1135+
fields: fields{filename: filepath.Join("test", "Book1.xlsx")},
1136+
want: 22,
1137+
wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool {
1138+
return assert.NoError(t, err)
1139+
},
1140+
}, {
1141+
name: "CalcChain.xlsx",
1142+
fields: fields{filename: filepath.Join("test", "CalcChain.xlsx")},
1143+
want: 1,
1144+
wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool {
1145+
return assert.NoError(t, err)
1146+
},
1147+
}, {
1148+
name: "SharedStrings.xlsx",
1149+
fields: fields{filename: filepath.Join("test", "SharedStrings.xlsx")},
1150+
want: 1,
1151+
wantErr: func(t assert.TestingT, err error, _ ...interface{}) bool {
1152+
return assert.NoError(t, err)
1153+
},
1154+
}}
1155+
for _, tt := range tests {
1156+
t.Run(tt.name, func(t *testing.T) {
1157+
f, err := OpenFile(tt.fields.filename)
1158+
assert.NoError(t, err)
1159+
firstSheet := f.GetSheetName(0)
1160+
got, err := f.CountRows(firstSheet)
1161+
if !tt.wantErr(t, err, "CountRows") {
1162+
return
1163+
}
1164+
assert.Equal(t, tt.want, got, "CountRows")
1165+
})
1166+
}
1167+
}

0 commit comments

Comments
 (0)