Skip to content

Commit 0bda115

Browse files
committed
This closes #33 and #38, upgrade version to v0.0.8
- Add new 6 rows iterator functions to support stream reader - rows - rows.close - rows.columns - rows.error - rows.get_row_opts - rows.next - Fix get_rows function skip the empty rows - Upgrade the dependencies package version - Using escape tab control character in the code doc blocks
1 parent 46e001e commit 0bda115

File tree

9 files changed

+314
-14
lines changed

9 files changed

+314
-14
lines changed

excelize.py

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def load_lib() -> Optional[str]:
9999

100100
lib = CDLL(os.path.join(os.path.dirname(__file__), load_lib()))
101101
ENCODE = "utf-8"
102-
__version__ = "0.0.7"
102+
__version__ = "0.0.8"
103103
uppercase_words = ["id", "rgb", "sq", "xml"]
104104

105105

@@ -537,6 +537,93 @@ def get_end_axis(self) -> str:
537537
return self.arr[0].split(":")[1]
538538

539539

540+
class Rows:
541+
"""
542+
Rows defines an iterator to a sheet.
543+
"""
544+
545+
index: int
546+
547+
def __init__(self, index: int):
548+
self.index = index
549+
550+
def close(self) -> None:
551+
"""
552+
Closes the open worksheet XML file in the system temporary directory.
553+
"""
554+
lib.RowsClose.restype = c_char_p
555+
err = lib.RowsClose(self.index).decode(ENCODE)
556+
if err != "":
557+
raise RuntimeError(err)
558+
559+
def columns(self, *opts: Options) -> List[str]:
560+
"""
561+
Return the current row's column values. This fetches the worksheet data
562+
as a stream, returns each cell in a row as is, and will not skip empty
563+
rows in the tail of the worksheet.
564+
565+
Args:
566+
*opts (Options): Optional parameters for get column cells value.
567+
568+
Returns:
569+
List[str]: Return the current row's column values if no error
570+
occurred, otherwise return an empty list.
571+
"""
572+
prepare_args(
573+
[opts[0]] if opts else [],
574+
[argsRule("opts", [Options], True)],
575+
)
576+
lib.RowsColumns.restype, options = types_go._StringArrayErrorResult, None
577+
if len(opts) > 0:
578+
options = byref(py_value_to_c(opts[0], types_go._Options()))
579+
res = lib.RowsColumns(self.index, options)
580+
arr = c_value_to_py(res, StringArrayErrorResult()).arr
581+
return arr if arr else []
582+
583+
def error(self) -> None:
584+
"""
585+
Return the error when the error occurs.
586+
587+
Returns:
588+
None: Return None if no error occurred, otherwise raise a
589+
RuntimeError with the message.
590+
"""
591+
lib.RowsError.restype = c_char_p
592+
err = lib.RowsError(self.index).decode(ENCODE)
593+
if err != "":
594+
raise RuntimeError(err)
595+
596+
def get_row_opts(self) -> Optional[RowOpts]:
597+
"""
598+
Return the row options of the current row.
599+
600+
Returns:
601+
Optional[RowOpts]: Return the row options of the current row if no
602+
error occurred, otherwise raise a RuntimeError with the message.
603+
"""
604+
lib.RowsGetRowOpts.restype = types_go._GetRowOptsResult
605+
res = lib.RowsGetRowOpts(self.index)
606+
err = res.err.decode(ENCODE)
607+
if not err:
608+
return c_value_to_py(res.opts, RowOpts())
609+
raise RuntimeError(err)
610+
611+
def next(self) -> bool:
612+
"""
613+
Return `True` if it finds the next row element.
614+
615+
Returns:
616+
bool: Return if it finds the next row element if no error occurred,
617+
otherwise raise a RuntimeError with the message.
618+
"""
619+
lib.RowsNext.restype = types_go._BoolErrorResult
620+
res = lib.RowsNext(self.index)
621+
err = res.err.decode(ENCODE)
622+
if not err:
623+
return res.val
624+
raise RuntimeError(err)
625+
626+
540627
class StreamWriter:
541628
"""
542629
StreamWriter is a streaming writer for writing large amounts of data to a
@@ -3177,7 +3264,7 @@ def get_rows(self, sheet: str, *opts: Options) -> List[List[str]]:
31773264
rows = f.get_rows("Sheet1")
31783265
for row in rows:
31793266
for cell in row:
3180-
print(f"{cell}\t", end="")
3267+
print(f"{cell}\\t", end="")
31813268
print()
31823269
```
31833270
"""
@@ -3197,8 +3284,7 @@ def get_rows(self, sheet: str, *opts: Options) -> List[List[str]]:
31973284
result = c_value_to_py(res, GetRowsResult()).row
31983285
if result:
31993286
for row in result:
3200-
if row.cell:
3201-
rows.append([cell for cell in row.cell])
3287+
rows.append([cell for cell in row.cell] if row.cell else [])
32023288
if not err:
32033289
return rows
32043290
raise RuntimeError(err)
@@ -4062,6 +4148,41 @@ def remove_row(self, sheet: str, row: int) -> None:
40624148
if err != "":
40634149
raise RuntimeError(err)
40644150

4151+
def rows(self, sheet: str) -> Rows:
4152+
"""
4153+
Returns a rows iterator, used for streaming reading data for a worksheet
4154+
with a large data.
4155+
4156+
Args:
4157+
sheet (str): The worksheet name
4158+
4159+
Returns:
4160+
Rows: Return the rows iterator object if no error occurred,
4161+
otherwise raise a RuntimeError with the message.
4162+
4163+
Example:
4164+
For example:
4165+
4166+
```python
4167+
try:
4168+
rows = f.rows("Sheet1")
4169+
while rows.next():
4170+
for cell in rows.columns():
4171+
print(f"{cell}\\t", end="")
4172+
print()
4173+
rows.close()
4174+
except (RuntimeError, TypeError) as err:
4175+
print(err)
4176+
```
4177+
"""
4178+
prepare_args([sheet], [argsRule("sheet", [str])])
4179+
lib.Rows.restype = types_go._IntErrorResult
4180+
res = lib.Rows(self.file_index, sheet.encode(ENCODE))
4181+
err = res.err.decode(ENCODE)
4182+
if not err:
4183+
return Rows(res.val)
4184+
raise RuntimeError(err)
4185+
40654186
def search_sheet(self, sheet: str, value: str, *reg: bool) -> List[str]:
40664187
"""
40674188
Get cell reference by given worksheet name, cell value, and regular

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/xuri/excelize-py
33
go 1.24.0
44

55
require (
6-
github.com/xuri/excelize/v2 v2.10.1-0.20251128004124-f1ac51e63bc8
6+
github.com/xuri/excelize/v2 v2.10.1-0.20251205010452-4ff4208b3c6c
77
golang.org/x/image v0.33.0
88
)
99

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrI
1313
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
1414
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
1515
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
16-
github.com/xuri/excelize/v2 v2.10.1-0.20251128004124-f1ac51e63bc8 h1:BMTZwsTDkAGubaojDitcn7jw+VbPCRT5vpV2C4eD/gY=
17-
github.com/xuri/excelize/v2 v2.10.1-0.20251128004124-f1ac51e63bc8/go.mod h1:B1Yl9Szv1D018lVoZttJ5lJkw8AP40Ysb0uKP4VO2hA=
16+
github.com/xuri/excelize/v2 v2.10.1-0.20251205010452-4ff4208b3c6c h1:ooy51XVFN19WdNWLKed+dzBmKySvFDiCoV7qe+ar9ic=
17+
github.com/xuri/excelize/v2 v2.10.1-0.20251205010452-4ff4208b3c6c/go.mod h1:B1Yl9Szv1D018lVoZttJ5lJkw8AP40Ysb0uKP4VO2hA=
1818
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
1919
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
2020
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=

main.go

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@ const (
5252
)
5353

5454
var (
55-
files, sw = sync.Map{}, sync.Map{}
56-
emptyString string
57-
errFilePtr = "can not find file pointer"
58-
errStreamWriterPtr = "can not find stream writer pointer"
59-
errArgType = errors.New("invalid argument data type")
55+
files, rowsIterator, sw = sync.Map{}, sync.Map{}, sync.Map{}
56+
emptyString string
57+
errFilePtr = "can not find file pointer"
58+
errRowsIteratorPtr = "can not find rows iterator pointer"
59+
errStreamWriterPtr = "can not find stream writer pointer"
60+
errArgType = errors.New("invalid argument data type")
6061

6162
// goBaseTypes defines Go's basic data types.
6263
goBaseTypes = map[reflect.Kind]bool{
@@ -1754,6 +1755,116 @@ func NewStreamWriter(idx int, sheet *C.char) C.struct_IntErrorResult {
17541755
return C.struct_IntErrorResult{val: C.int(swIdx), err: C.CString(emptyString)}
17551756
}
17561757

1758+
// Rows returns a rows iterator, used for streaming reading data for a worksheet
1759+
// with a large data. This function is concurrency safe.
1760+
//
1761+
//export Rows
1762+
func Rows(idx int, sheet *C.char) C.struct_IntErrorResult {
1763+
f, ok := files.Load(idx)
1764+
if !ok {
1765+
return C.struct_IntErrorResult{val: C.int(0), err: C.CString(errFilePtr)}
1766+
}
1767+
rows, err := f.(*excelize.File).Rows(C.GoString(sheet))
1768+
if err != nil {
1769+
return C.struct_IntErrorResult{val: C.int(0), err: C.CString(err.Error())}
1770+
}
1771+
var rIdx int
1772+
rowsIterator.Range(func(_, _ interface{}) bool {
1773+
rIdx++
1774+
return true
1775+
})
1776+
rIdx++
1777+
rowsIterator.Store(rIdx, rows)
1778+
return C.struct_IntErrorResult{val: C.int(rIdx), err: C.CString(emptyString)}
1779+
}
1780+
1781+
// RowsClose closes the open worksheet XML file in the system temporary
1782+
// directory.
1783+
//
1784+
//export RowsClose
1785+
func RowsClose(rIdx int) *C.char {
1786+
row, ok := rowsIterator.Load(rIdx)
1787+
if !ok {
1788+
return C.CString(errRowsIteratorPtr)
1789+
}
1790+
defer rowsIterator.Delete(rIdx)
1791+
if err := row.(*excelize.Rows).Close(); err != nil {
1792+
return C.CString(err.Error())
1793+
}
1794+
return C.CString(emptyString)
1795+
}
1796+
1797+
// RowsColumns return the current row's column values. This fetches the
1798+
// worksheet data as a stream, returns each cell in a row as is, and will not
1799+
// skip empty rows in the tail of the worksheet.
1800+
//
1801+
//export RowsColumns
1802+
func RowsColumns(rIdx int, opts *C.struct_Options) C.struct_StringArrayErrorResult {
1803+
var options excelize.Options
1804+
if opts != nil {
1805+
goVal, err := cValueToGo(reflect.ValueOf(*opts), reflect.TypeOf(excelize.Options{}))
1806+
if err != nil {
1807+
return C.struct_StringArrayErrorResult{Err: C.CString(err.Error())}
1808+
}
1809+
options = goVal.Elem().Interface().(excelize.Options)
1810+
}
1811+
row, ok := rowsIterator.Load(rIdx)
1812+
if !ok {
1813+
return C.struct_StringArrayErrorResult{Err: C.CString(errRowsIteratorPtr)}
1814+
}
1815+
result, err := row.(*excelize.Rows).Columns(options)
1816+
if err != nil {
1817+
return C.struct_StringArrayErrorResult{Err: C.CString(err.Error())}
1818+
}
1819+
cArray := C.malloc(C.size_t(len(result)) * C.size_t(unsafe.Sizeof(uintptr(0))))
1820+
for i, v := range result {
1821+
*(*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(cArray)) + uintptr(i)*unsafe.Sizeof(uintptr(0)))) = unsafe.Pointer(C.CString(v))
1822+
}
1823+
return C.struct_StringArrayErrorResult{ArrLen: C.int(len(result)), Arr: (**C.char)(cArray), Err: C.CString(emptyString)}
1824+
}
1825+
1826+
// RowsError will return the error when the error occurs.
1827+
//
1828+
//export RowsError
1829+
func RowsError(rIdx int) *C.char {
1830+
row, ok := rowsIterator.Load(rIdx)
1831+
if !ok {
1832+
return C.CString(errRowsIteratorPtr)
1833+
}
1834+
err := row.(*excelize.Rows).Error()
1835+
if err != nil {
1836+
return C.CString(err.Error())
1837+
}
1838+
return C.CString(emptyString)
1839+
}
1840+
1841+
// RowsGetRowOpts will return the RowOpts of the current row.
1842+
//
1843+
//export RowsGetRowOpts
1844+
func RowsGetRowOpts(rIdx int) C.struct_GetRowOptsResult {
1845+
row, ok := rowsIterator.Load(rIdx)
1846+
if !ok {
1847+
return C.struct_GetRowOptsResult{err: C.CString(errRowsIteratorPtr)}
1848+
}
1849+
opts := row.(*excelize.Rows).GetRowOpts()
1850+
cVal, err := goValueToC(reflect.ValueOf(opts), reflect.ValueOf(&C.struct_RowOpts{}))
1851+
if err != nil {
1852+
return C.struct_GetRowOptsResult{err: C.CString(err.Error())}
1853+
}
1854+
return C.struct_GetRowOptsResult{opts: cVal.Elem().Interface().(C.struct_RowOpts), err: C.CString(emptyString)}
1855+
}
1856+
1857+
// RowsNext will return true if it finds the next row element.
1858+
//
1859+
//export RowsNext
1860+
func RowsNext(rIdx int) C.struct_BoolErrorResult {
1861+
row, ok := rowsIterator.Load(rIdx)
1862+
if !ok {
1863+
return C.struct_BoolErrorResult{val: C._Bool(false), err: C.CString(errRowsIteratorPtr)}
1864+
}
1865+
return C.struct_BoolErrorResult{val: C._Bool(row.(*excelize.Rows).Next()), err: C.CString(emptyString)}
1866+
}
1867+
17571868
// StreamAddTable creates an Excel table for the StreamWriter using the given
17581869
// cell range and format set.
17591870
//
@@ -1874,6 +1985,7 @@ func StreamFlush(swIdx int) *C.char {
18741985
if !ok {
18751986
return C.CString(errStreamWriterPtr)
18761987
}
1988+
defer sw.Delete(swIdx)
18771989
if err := streamWriter.(*excelize.StreamWriter).Flush(); err != nil {
18781990
return C.CString(err.Error())
18791991
}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def run(self):
4242

4343
setup(
4444
name="excelize",
45-
version="0.0.7",
45+
version="0.0.8",
4646
license="BSD 3-Clause",
4747
license_files=("LICENSE"),
4848
description="A Python build of the Go Excelize library for reading and writing Microsoft Excel™ (XLAM / XLSM / XLSX / XLTM / XLTX) spreadsheets",

0 commit comments

Comments
 (0)