Skip to content

Commit

Permalink
Add 4 new functions: add_picture_from_bytes, add_vba_project, set_col…
Browse files Browse the repository at this point in the history
…_outline_level and set_defined_name

- Update unit tests and docs for the function
- Upgrade the dependencies package version
- Made unit test save workbook in the test folder
- The cValueToGo and py_value_to_c functions support convert byte array data type (Go: []byte and Python: bytes)
  • Loading branch information
xuri committed Dec 22, 2024
1 parent eeafd90 commit dc21761
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 33 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ __pycache__
*.dll
*.dylib
*.so
*.xlsx
*.xml
dist/
test/
excelize.egg-info/
libexcelize.*.h
Makefile
Makefile
121 changes: 119 additions & 2 deletions excelize.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ def py_value_to_c(py_instance, ctypes_instance):
py_value_to_c(getattr(py_instance, py_field_name), c_type()),
)
else:
if get_origin(py_field_args[0]) is not list:
arg_type = py_field_args[0]
if get_origin(arg_type) is not list and arg_type is not bytes:
# Pointer of the Go data type, for example: *excelize.Options or *string
value = getattr(py_instance, py_field_name)
c_type = get_c_field_type(ctypes_instance, c_field_name)._type_
Expand All @@ -335,7 +336,15 @@ def py_value_to_c(py_instance, ctypes_instance):
else:
# The Go data type array, for example:
# []*excelize.Options, []excelize.Options, []string, []*string
py_field_type = get_args(py_field_args[0])[0]
if arg_type is bytes: # []byte
c_type = get_c_field_type(ctypes_instance, c_field_name)._type_
value = getattr(py_instance, py_field_name)
ctypes_instance.__setattr__(
c_field_name, cast(value, POINTER(c_ubyte))
)
ctypes_instance.__setattr__(c_field_name + "Len", c_int(len(value)))
continue
py_field_type = get_args(arg_type)[0]
if type(None) not in get_args(py_field_type):
# The Go data type array, for example: []excelize.Options or []string
c_type = get_c_field_type(ctypes_instance, c_field_name)._type_
Expand Down Expand Up @@ -616,6 +625,36 @@ def add_picture(
).decode(ENCODE)
return None if err == "" else Exception(err)

def add_picture_from_bytes(
self, sheet: str, cell: str, picture: Picture
) -> Optional[Exception]:
"""
Add picture in a sheet by given picture format set (such as offset,
scale, aspect ratio setting and print settings), file base name,
extension name and file bytes, supported image types: EMF, EMZ, GIF,
JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. Note that this function
only supports adding pictures placed over the cells currently, and
doesn't support adding pictures placed in cells or creating the Kingsoft
WPS Office embedded image cells
Args:
sheet (str): The worksheet name
extension (str): The image extension
picture (Picture): The picture options
Returns:
Optional[Exception]: Returns None if no error occurred,
otherwise returns an Exception with the message.
"""
lib.AddPictureFromBytes.restype = c_char_p
err = lib.AddPictureFromBytes(
self.file_index,
sheet.encode(ENCODE),
cell.encode(ENCODE),
byref(py_value_to_c(picture, types_go._Picture())),
).decode(ENCODE)
return None if err == "" else Exception(err)

def add_pivot_table(self, opts: Optional[PivotTableOptions]) -> Optional[Exception]:
"""
Add pivot table by given pivot table options. Note that the same fields
Expand Down Expand Up @@ -880,6 +919,26 @@ def add_table(self, sheet: str, table: Table) -> Optional[Exception]:
).decode(ENCODE)
return None if err == "" else Exception(err)

def add_vba_project(self, file: bytes) -> Optional[Exception]:
"""
Add vbaProject.bin file which contains functions and/or macros. The file
extension should be XLSM or XLTM.
Args:
file (bytes): The contents buffer of the file
Returns:
Optional[Exception]: Returns None if no error occurred,
otherwise returns an Exception with the message.
"""
lib.AddVBAProject.restype = c_char_p
err = lib.AddVBAProject(
self.file_index,
cast(file, POINTER(c_ubyte)),
len(file),
).decode(ENCODE)
return None if err == "" else Exception(err)

def auto_filter(
self,
sheet: str,
Expand Down Expand Up @@ -1572,6 +1631,64 @@ def set_cell_value(
).decode(ENCODE)
return None if err == "" else Exception(err)

def set_col_outline_level(
self, sheet: str, col: str, level: int
) -> Optional[Exception]:
"""
Set outline level of a single column by given worksheet name and column
name.
Args:
sheet (str): The worksheet name
col (str): The column name
level (int): The out level, acceptable value from 1 to 7
Returns:
Optional[Exception]: Returns None if no error occurred,
otherwise returns an Exception with the message.
Example:
For example, set outline level of column D in Sheet1 to 2:
.. code-block:: python
err = f.set_col_outline_level("Sheet1", "D", 2)
"""
lib.SetColOutlineLevel.restype = c_char_p
err = lib.SetColOutlineLevel(
self.file_index, sheet.encode(ENCODE), col.encode(ENCODE), level
).decode(ENCODE)
return None if err == "" else Exception(err)

def set_defined_name(self, defined_name: DefinedName) -> Optional[Exception]:
"""
Set the defined names of the workbook or worksheet. If not specified
scope, the default scope is workbook.
Args:
defined_name (DefinedName): The defined name options
Returns:
Optional[Exception]: Returns None if no error occurred,
otherwise returns an Exception with the message.
Example:
For example, create a table of A1:D5 on Sheet1:
.. code-block:: python
err = f.set_defined_name(excelize.DefinedName(
name="Amount",
refers_to="Sheet1!$A$2:$D$5",
comment="defined name comment",
scope="Sheet2",
))
"""
lib.SetDefinedName.restype = c_char_p
options = py_value_to_c(defined_name, types_go._DefinedName())
err = lib.SetDefinedName(self.file_index, byref(options)).decode(ENCODE)
return None if err == "" else Exception(err)

def set_sheet_background(self, sheet: str, picture: str) -> Optional[Exception]:
"""
Set background picture by given worksheet name and file path. Supported
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/xuri/excelize-py
go 1.20

require (
github.com/xuri/excelize/v2 v2.9.1-0.20241208033954-3ca60f8d2382
github.com/xuri/excelize/v2 v2.9.1-0.20241221071117-9934bf5c8634
golang.org/x/image v0.23.0
)

Expand All @@ -14,6 +14,6 @@ require (
github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 // indirect
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ github.com/tiendc/go-deepcopy v1.2.0 h1:6vCCs+qdLQHzFqY1fcPirsAWOmrLbuccilfp8UzD
github.com/tiendc/go-deepcopy v1.2.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 h1:8m6DWBG+dlFNbx5ynvrE7NgI+Y7OlZVMVTpayoW+rCc=
github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.9.1-0.20241208033954-3ca60f8d2382 h1:51czxsTwHg9JHFIiKuookS9dUtLUeCL7xoXTqF6BI6A=
github.com/xuri/excelize/v2 v2.9.1-0.20241208033954-3ca60f8d2382/go.mod h1:wFKkKpd4PMjXhJVhlEnMxKAxFlT+wMhayUoEMp1LR1g=
github.com/xuri/excelize/v2 v2.9.1-0.20241221071117-9934bf5c8634 h1:GDmRYes16eUdzi5a4Iv2nqSn3jZUch8DNttR/qNe1NU=
github.com/xuri/excelize/v2 v2.9.1-0.20241221071117-9934bf5c8634/go.mod h1:NBRx6e5FHFx4mHLiYG1QBONNvNNSs/wrtzS+h56/A6k=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
86 changes: 83 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,13 @@ func cValueToGo(cVal reflect.Value, goType reflect.Type) (reflect.Value, error)
// The Go data type array, for example: []excelize.Options or []string
subEle := ele
cArrayLen := int(cVal.FieldByName(field.Name + "Len").Int())
if subEle.Kind() == reflect.Uint8 { // []byte
buf := C.GoBytes(unsafe.Pointer(cArray.Interface().(*C.uchar)), C.int(cArrayLen))
s.Field(resultFieldIdx).Set(reflect.ValueOf(buf))
continue
}
cArray = cToGoArray(cArray, cArrayLen)
for i := 0; i < cArray.Len(); i++ {
if subEle.Kind() == reflect.Uint8 { // []byte
break
}
if goBaseTypes[subEle.Kind()] {
// The Go basic data type array, for example: []string
v, err := cToGoBaseType(cArray.Index(i), subEle.Kind())
Expand Down Expand Up @@ -558,6 +560,31 @@ func AddPicture(idx int, sheet, cell, name *C.char, opts *C.struct_GraphicOption
return C.CString(errNil)
}

// AddPictureFromBytes provides the method to add picture in a sheet by given
// picture format set (such as offset, scale, aspect ratio setting and print
// settings), file base name, extension name and file bytes, supported image
// types: EMF, EMZ, GIF, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. Note that
// this function only supports adding pictures placed over the cells currently,
// and doesn't support adding pictures placed in cells or creating the Kingsoft
// WPS Office embedded image cells
//
//export AddPictureFromBytes
func AddPictureFromBytes(idx int, sheet, cell *C.char, pic *C.struct_Picture) *C.char {
f, ok := files.Load(idx)
if !ok {
return C.CString(errFilePtr)
}
goVal, err := cValueToGo(reflect.ValueOf(*pic), reflect.TypeOf(excelize.Picture{}))
if err != nil {
return C.CString(err.Error())
}
options := goVal.Elem().Interface().(excelize.Picture)
if err := f.(*excelize.File).AddPictureFromBytes(C.GoString(sheet), C.GoString(cell), &options); err != nil {
C.CString(err.Error())
}
return C.CString(errNil)
}

// AddPivotTable provides the method to add pivot table by given pivot table
// options. Note that the same fields can not in Columns, Rows and Filter
// fields at the same time.
Expand Down Expand Up @@ -667,6 +694,22 @@ func AddTable(idx int, sheet *C.char, table *C.struct_Table) *C.char {
return C.CString(errNil)
}

// AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be XLSM or XLTM.
//
//export AddVBAProject
func AddVBAProject(idx int, file *C.uchar, fileLen C.int) *C.char {
f, ok := files.Load(idx)
if !ok {
return C.CString(errFilePtr)
}
buf := C.GoBytes(unsafe.Pointer(file), fileLen)
if err := f.(*excelize.File).AddVBAProject(buf); err != nil {
C.CString(err.Error())
}
return C.CString(errNil)
}

// AutoFilter provides the method to add auto filter in a worksheet by given
// worksheet name, range reference and settings. An auto filter in Excel is a
// way of filtering a 2D range of data based on some simple criteria.
Expand Down Expand Up @@ -1385,6 +1428,43 @@ func SetCellValue(idx int, sheet, cell *C.char, value *C.struct_Interface) *C.ch
return C.CString(errNil)
}

// SetColOutlineLevel provides a function to set outline level of a single
// column by given worksheet name and column name. The value of parameter
// 'level' is 1-7.
//
//export SetColOutlineLevel
func SetColOutlineLevel(idx int, sheet, col *C.char, level int) *C.char {
f, ok := files.Load(idx)
if !ok {
return C.CString(errFilePtr)
}
if err := f.(*excelize.File).SetColOutlineLevel(C.GoString(sheet), C.GoString(col), uint8(level)); err != nil {
return C.CString(err.Error())
}
return C.CString(errNil)
}

// SetDefinedName provides a function to set the defined names of the workbook
// or worksheet. If not specified scope, the default scope is workbook.
//
//export SetDefinedName
func SetDefinedName(idx int, definedName *C.struct_DefinedName) *C.char {
var df excelize.DefinedName
goVal, err := cValueToGo(reflect.ValueOf(*definedName), reflect.TypeOf(excelize.DefinedName{}))
if err != nil {
return C.CString(err.Error())
}
df = goVal.Elem().Interface().(excelize.DefinedName)
f, ok := files.Load(idx)
if !ok {
return C.CString(errFilePtr)
}
if err := f.(*excelize.File).SetDefinedName(&df); err != nil {
return C.CString(err.Error())
}
return C.CString(errNil)
}

// SetSheetBackground provides a function to set background picture by given
// worksheet name and file path. Supported image types: BMP, EMF, EMZ, GIF,
// JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ.
Expand Down
Binary file added test/vbaProject.bin
Binary file not shown.
Loading

0 comments on commit dc21761

Please sign in to comment.