Skip to content

Commit 5a2155b

Browse files
committed
Add 2 new functions: get_custom_props and set_custom_props
- Update unit tests - Made functions set_row, set_sheet_col and set_sheet_row function accept float data type value - Rename CodeQL config
1 parent 51766c0 commit 5a2155b

File tree

7 files changed

+252
-27
lines changed

7 files changed

+252
-27
lines changed

excelize.py

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
c_char,
2222
c_double,
2323
c_int,
24+
c_int32,
2425
c_longlong,
2526
c_ubyte,
2627
cast,
@@ -452,19 +453,48 @@ def py_value_to_c_interface(py_value):
452453
"""
453454
type_mappings = {
454455
int: lambda: Interface(type=1, integer=py_value),
455-
str: lambda: Interface(type=2, string=py_value),
456-
float: lambda: Interface(type=3, float64=py_value),
457-
bool: lambda: Interface(type=4, boolean=py_value),
458-
datetime: lambda: Interface(type=5, integer=int(py_value.timestamp())),
456+
str: lambda: Interface(type=3, string=py_value),
457+
float: lambda: Interface(type=4, float64=py_value),
458+
bool: lambda: Interface(type=5, boolean=py_value),
459+
datetime: lambda: Interface(type=6, integer=int(py_value.timestamp())),
459460
date: lambda: Interface(
460-
type=5,
461+
type=6,
461462
integer=int(datetime.combine(py_value, time.min).timestamp()),
462463
),
463464
}
464465
interface = type_mappings.get(type(py_value), Interface)()
465466
return py_value_to_c(interface, types_go._Interface())
466467

467468

469+
def c_value_to_py_interface(c_value):
470+
"""
471+
Converts a C value to a Python interface representation.
472+
473+
Args:
474+
c_value: The C value to be converted.
475+
476+
Returns:
477+
An Interface object representing the C value in a Python-compatible format.
478+
479+
Raises:
480+
TypeError: If the type of c_value is not supported.
481+
"""
482+
if c_value is None:
483+
return None
484+
type_mappings = {
485+
1: lambda: c_value.Integer,
486+
2: lambda: c_value.Integer32,
487+
3: lambda: c_value.String.decode(ENCODE) if c_value.String else "",
488+
4: lambda: c_value.Float64,
489+
5: lambda: c_value.Boolean,
490+
6: lambda: datetime.fromtimestamp(c_value.Integer),
491+
}
492+
converter = type_mappings.get(c_value.Type)
493+
if converter:
494+
return converter()
495+
raise TypeError(f"unsupported interface type code: {c_value.Type}")
496+
497+
468498
def prepare_args(args: List, types: List[argsRule]):
469499
"""
470500
Validate arguments against expected types.
@@ -785,7 +815,7 @@ def set_panes(self, opts: Panes) -> None:
785815
def set_row(
786816
self,
787817
cell: str,
788-
values: List[Union[None, int, str, bool, datetime, date]],
818+
values: List[Union[bool, float, int, str, date, datetime, None]],
789819
) -> None:
790820
"""
791821
Writes an array to stream rows by giving starting cell reference and a
@@ -794,8 +824,8 @@ def set_row(
794824
795825
Args:
796826
cell (str): The cell reference
797-
values (List[Union[None, int, str, bool, datetime, date]]): The cell
798-
values
827+
values (List[Union[bool, float, int, str, date, datetime, None]]):
828+
The cell values
799829
800830
Returns:
801831
None: Return None if no error occurred, otherwise raise a
@@ -3054,6 +3084,30 @@ def get_comments(self, sheet: str) -> List[Comment]:
30543084
raise RuntimeError(err)
30553085
return result.comments if result and result.comments else []
30563086

3087+
def get_custom_props(self) -> List[CustomProperty]:
3088+
"""
3089+
Get custom file properties
3090+
3091+
Returns:
3092+
List[CustomProperty]: Return the custom file properties if no error
3093+
occurred, otherwise raise a RuntimeError with the message.
3094+
"""
3095+
lib.GetCustomProps.restype = types_go._GetCustomPropsResult
3096+
res = lib.GetCustomProps(self.file_index)
3097+
err = res.Err.decode(ENCODE)
3098+
if err == "":
3099+
arr = []
3100+
if res.CustomProps:
3101+
for i in range(res.CustomPropsLen):
3102+
arr.append(
3103+
CustomProperty(
3104+
name=res.CustomProps[i].Name.decode(ENCODE),
3105+
value=c_value_to_py_interface(res.CustomProps[i].Value),
3106+
)
3107+
)
3108+
return arr
3109+
raise RuntimeError(err)
3110+
30573111
def get_default_font(self) -> str:
30583112
"""
30593113
Get the default font name currently set in the workbook. The spreadsheet
@@ -5148,6 +5202,37 @@ def set_conditional_format(
51485202
if err != "":
51495203
raise RuntimeError(err)
51505204

5205+
def set_custom_props(self, prop: CustomProperty) -> None:
5206+
"""
5207+
Set custom file properties by given property name and value. If the
5208+
property name already exists, it will be updated, otherwise a new
5209+
property will be added. The value can be of type `int`, `float`, `bool`,
5210+
`str`, `datetime`, `date` and `None`. The property will be delete if the
5211+
value is `None`. The function returns an error if the property value is
5212+
not of the correct type.
5213+
5214+
Args:
5215+
prop (CustomProperty): The custom file property
5216+
5217+
Returns:
5218+
None: Return None if no error occurred, otherwise raise a
5219+
RuntimeError with the message.
5220+
"""
5221+
prepare_args([prop], [argsRule("prop", [CustomProperty])])
5222+
lib.SetCustomProps.restype = c_char_p
5223+
options = types_go._CustomProperty()
5224+
setattr(options, "Name", (prop.name or "").encode(ENCODE))
5225+
val = types_go._Interface()
5226+
if type(prop.value) is int:
5227+
setattr(val, "Type", c_int(2))
5228+
setattr(val, "Integer32", c_int32(prop.value))
5229+
else:
5230+
val = py_value_to_c_interface(prop.value)
5231+
setattr(options, "Value", val)
5232+
err = lib.SetCustomProps(self.file_index, options).decode(ENCODE)
5233+
if err != "":
5234+
raise RuntimeError(err)
5235+
51515236
def set_default_font(self, font_name: str) -> None:
51525237
"""
51535238
Set the default font name in the workbook. The spreadsheet generated by
@@ -5216,10 +5301,7 @@ def set_defined_name(self, defined_name: DefinedName) -> None:
52165301
))
52175302
```
52185303
"""
5219-
prepare_args(
5220-
[defined_name],
5221-
[argsRule("defined_name", [DefinedName])],
5222-
)
5304+
prepare_args([defined_name], [argsRule("defined_name", [DefinedName])])
52235305
lib.SetDefinedName.restype = c_char_p
52245306
options = py_value_to_c(defined_name, types_go._DefinedName())
52255307
err = lib.SetDefinedName(self.file_index, byref(options)).decode(ENCODE)
@@ -5645,7 +5727,7 @@ def set_sheet_col(
56455727
self,
56465728
sheet: str,
56475729
cell: str,
5648-
values: List[Union[None, int, str, bool, datetime, date]],
5730+
values: List[Union[bool, float, int, str, date, datetime, None]],
56495731
) -> None:
56505732
"""
56515733
Writes cells to column by given worksheet name, starting cell reference
@@ -5654,8 +5736,8 @@ def set_sheet_col(
56545736
Args:
56555737
sheet (str): The worksheet name
56565738
cell (str): The cell reference
5657-
values (List[Union[None, int, str, bool, datetime, date]]): The cell
5658-
values
5739+
values (List[Union[bool, float, int, str, date, datetime, None]):
5740+
The cell values
56595741
56605742
Returns:
56615743
None: Return None if no error occurred, otherwise raise a
@@ -5780,7 +5862,7 @@ def set_sheet_row(
57805862
self,
57815863
sheet: str,
57825864
cell: str,
5783-
values: List[Union[None, int, str, bool, datetime, date]],
5865+
values: List[Union[bool, float, int, str, date, datetime, None]],
57845866
) -> None:
57855867
"""
57865868
Writes cells to row by given worksheet name, starting cell reference and
@@ -5789,8 +5871,8 @@ def set_sheet_row(
57895871
Args:
57905872
sheet (str): The worksheet name
57915873
cell (str): The cell reference
5792-
values (List[Union[None, int, str, bool, datetime, date]]): The cell
5793-
values
5874+
values (List[Union[bool, float, int, str, date, datetime, None]]):
5875+
The cell values
57945876
57955877
Returns:
57965878
None: Return None if no error occurred, otherwise raise a

main.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ type GetRowsResult struct {
4545
const (
4646
Nil C.int = 0
4747
Int C.int = 1
48-
String C.int = 2
49-
Float C.int = 3
50-
Boolean C.int = 4
51-
Time C.int = 5
48+
Int32 C.int = 2
49+
String C.int = 3
50+
Float C.int = 4
51+
Boolean C.int = 5
52+
Time C.int = 6
5253
)
5354

5455
var (
@@ -433,6 +434,8 @@ func cInterfaceToGo(val C.struct_Interface) interface{} {
433434
switch val.Type {
434435
case Int:
435436
return int(val.Integer)
437+
case Int32:
438+
return int32(val.Integer32)
436439
case String:
437440
return C.GoString(val.String)
438441
case Float:
@@ -446,6 +449,26 @@ func cInterfaceToGo(val C.struct_Interface) interface{} {
446449
}
447450
}
448451

452+
// goInterfaceToC convert Go interface to C interface data type value.
453+
func goInterfaceToC(val interface{}) C.struct_Interface {
454+
switch v := val.(type) {
455+
case int:
456+
return C.struct_Interface{Type: Int, Integer: C.int(v)}
457+
case int32:
458+
return C.struct_Interface{Type: Int32, Integer32: C.int(v)}
459+
case string:
460+
return C.struct_Interface{Type: String, String: C.CString(v)}
461+
case float64:
462+
return C.struct_Interface{Type: Float, Float64: C.double(v)}
463+
case bool:
464+
return C.struct_Interface{Type: Boolean, Boolean: C._Bool(v)}
465+
case time.Time:
466+
return C.struct_Interface{Type: Time, Integer: C.int(v.Unix())}
467+
default:
468+
return C.struct_Interface{Type: Nil}
469+
}
470+
}
471+
449472
// AddChart provides the method to add chart in a sheet by given chart format
450473
// set (such as offset, scale, aspect ratio setting and print settings) and
451474
// properties set.
@@ -1240,6 +1263,26 @@ func GetComments(idx int, sheet *C.char) C.struct_GetCommentsResult {
12401263
return C.struct_GetCommentsResult{CommentsLen: C.int(len(comments)), Comments: (*C.struct_Comment)(cArray), Err: C.CString(emptyString)}
12411264
}
12421265

1266+
// GetCustomProps provides a function to get custom file properties.
1267+
//
1268+
//export GetCustomProps
1269+
func GetCustomProps(idx int) C.struct_GetCustomPropsResult {
1270+
f, ok := files.Load(idx)
1271+
if !ok {
1272+
return C.struct_GetCustomPropsResult{Err: C.CString(errFilePtr)}
1273+
}
1274+
props, err := f.(*excelize.File).GetCustomProps()
1275+
if err != nil {
1276+
return C.struct_GetCustomPropsResult{Err: C.CString(err.Error())}
1277+
}
1278+
cArray := C.malloc(C.size_t(len(props)) * C.size_t(unsafe.Sizeof(C.struct_CustomProperty{})))
1279+
for i, prop := range props {
1280+
cVal := C.struct_CustomProperty{Name: C.CString(prop.Name), Value: goInterfaceToC(prop.Value)}
1281+
*(*C.struct_CustomProperty)(unsafe.Pointer(uintptr(unsafe.Pointer(cArray)) + uintptr(i)*unsafe.Sizeof(C.struct_CustomProperty{}))) = cVal
1282+
}
1283+
return C.struct_GetCustomPropsResult{CustomPropsLen: C.int(len(props)), CustomProps: (*C.struct_CustomProperty)(cArray), Err: C.CString(emptyString)}
1284+
}
1285+
12431286
// GetDefaultFont provides the default font name currently set in the
12441287
// workbook. The spreadsheet generated by excelize default font is Calibri.
12451288
//
@@ -2585,6 +2628,27 @@ func SetConditionalFormat(idx int, sheet, rangeRef *C.char, opts *C.struct_Condi
25852628
return C.CString(emptyString)
25862629
}
25872630

2631+
// SetCustomProps provides a function to set custom file properties by given
2632+
// property name and value. If the property name already exists, it will be
2633+
// updated, otherwise a new property will be added. The value can be of type
2634+
// int32, float64, bool, string, time.Time or nil. The property will be delete
2635+
// if the value is nil. The function returns an error if the property value is
2636+
// not of the correct type.
2637+
//
2638+
//export SetCustomProps
2639+
func SetCustomProps(idx int, prop C.struct_CustomProperty) *C.char {
2640+
f, ok := files.Load(idx)
2641+
if !ok {
2642+
return C.CString(errFilePtr)
2643+
}
2644+
if err := f.(*excelize.File).SetCustomProps(excelize.CustomProperty{
2645+
Name: C.GoString(prop.Name), Value: cInterfaceToGo(prop.Value),
2646+
}); err != nil {
2647+
return C.CString(err.Error())
2648+
}
2649+
return C.CString(emptyString)
2650+
}
2651+
25882652
// SetDefaultFont provides the default font name currently set in the
25892653
// workbook. The spreadsheet generated by excelize default font is Calibri.
25902654
//

test_excelize.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
)
2424
import os
2525
import excelize
26+
import types_go
2627
from types_py import argsRule
2728

2829

@@ -920,6 +921,9 @@ def test_none_file_pointer(self):
920921
with self.assertRaises(RuntimeError) as context:
921922
f.get_app_props()
922923
self.assertEqual(str(context.exception), expected)
924+
with self.assertRaises(RuntimeError) as context:
925+
f.get_custom_props()
926+
self.assertEqual(str(context.exception), expected)
923927
with self.assertRaises(RuntimeError) as context:
924928
f.get_default_font()
925929
self.assertEqual(str(context.exception), expected)
@@ -929,6 +933,9 @@ def test_none_file_pointer(self):
929933
with self.assertRaises(RuntimeError) as context:
930934
f.get_calc_props()
931935
self.assertEqual(str(context.exception), expected)
936+
with self.assertRaises(RuntimeError) as context:
937+
f.set_custom_props(excelize.CustomProperty(name="Prop", value=""))
938+
self.assertEqual(str(context.exception), expected)
932939
with self.assertRaises(RuntimeError) as context:
933940
f.get_sheet_name(0)
934941
self.assertEqual(str(context.exception), expected)
@@ -2143,6 +2150,34 @@ def test_column_number_to_name(self):
21432150
"expected type int for argument 'num', but got str",
21442151
)
21452152

2153+
def test_custom_props(self):
2154+
f = excelize.new_file()
2155+
props = [
2156+
excelize.CustomProperty(name="Text Prop", value="text"),
2157+
excelize.CustomProperty(name="Boolean Prop 1", value=True),
2158+
excelize.CustomProperty(name="Boolean Prop 2", value=False),
2159+
excelize.CustomProperty(name="Number Prop 1", value=-123.456),
2160+
excelize.CustomProperty(name="Number Prop 2", value=1),
2161+
excelize.CustomProperty(
2162+
name="Date Prop", value=datetime.datetime(2016, 8, 30, 11, 51, 0)
2163+
),
2164+
]
2165+
for prop in props:
2166+
f.set_custom_props(prop)
2167+
self.assertEqual(props, f.get_custom_props())
2168+
2169+
with self.assertRaises(RuntimeError) as context:
2170+
f.set_custom_props(excelize.CustomProperty(name=None, value=1))
2171+
self.assertEqual(str(context.exception), "parameter is invalid")
2172+
with self.assertRaises(TypeError) as context:
2173+
f.set_custom_props(1)
2174+
self.assertEqual(
2175+
str(context.exception),
2176+
"expected type CustomProperty for argument 'prop', but got int",
2177+
)
2178+
self.assertIsNone(f.save_as(os.path.join("test", "TestSetCustomProps.xlsx")))
2179+
self.assertIsNone(f.close())
2180+
21462181
def test_add_picture(self):
21472182
f = excelize.new_file()
21482183
self.assertIsNone(f.add_picture("Sheet1", "A1", "chart.png", None))
@@ -2491,3 +2526,12 @@ class T1:
24912526
self.assertEqual(
24922527
excelize.c_value_to_py(excelize.py_value_to_c(t1, _T1()), T1()), t1
24932528
)
2529+
self.assertIsNone(excelize.c_value_to_py_interface(None))
2530+
with self.assertRaises(TypeError) as context:
2531+
val = types_go._Interface()
2532+
setattr(val, "Type", c_int(-1))
2533+
excelize.c_value_to_py_interface(val)
2534+
self.assertEqual(
2535+
str(context.exception),
2536+
"unsupported interface type code: -1",
2537+
)

0 commit comments

Comments
 (0)