From 23424611f9fde1825d403d4254267a38f149c19b Mon Sep 17 00:00:00 2001 From: xuri Date: Fri, 27 Dec 2024 09:48:39 +0800 Subject: [PATCH] Add 4 new functions: set_header_footer, set_page_layout, set_page_margins and set_panes - Update unit tests and docs for the function --- excelize.py | 104 ++++++++++++++++++++++++++++++++++++++++++++++- main.go | 86 +++++++++++++++++++++++++++++++++++++++ test_excelize.py | 84 ++++++++++++++++++++++++++++++++++++++ types_c.h | 96 +++++++++++++++++++++++++++++++++++-------- types_go.py | 62 ++++++++++++++++++++++++++++ types_py.py | 56 +++++++++++++++++++++++++ 6 files changed, 470 insertions(+), 18 deletions(-) diff --git a/excelize.py b/excelize.py index 39d8e7c..dc472da 100644 --- a/excelize.py +++ b/excelize.py @@ -83,7 +83,7 @@ def load_lib(): lib = CDLL(os.path.join(os.path.dirname(__file__), load_lib())) ENCODE = "utf-8" __version__ = "0.0.2" -uppercase_words = ["id", "xml"] +uppercase_words = ["id", "sq", "xml"] def py_to_base_ctype(py_value, c_type): @@ -2182,6 +2182,108 @@ def set_defined_name(self, defined_name: DefinedName) -> Optional[Exception]: err = lib.SetDefinedName(self.file_index, byref(options)).decode(ENCODE) return None if err == "" else Exception(err) + def set_header_footer( + self, sheet: str, opts: HeaderFooterOptions + ) -> Optional[Exception]: + """ + Set headers and footers by given worksheet name and the control + characters. + + Args: + sheet (str): The worksheet name + opts (HeaderFooterOptions): The header footer options + + Returns: + Optional[Exception]: Returns None if no error occurred, + otherwise returns an Exception with the message. + + Example: + For example: + + .. code-block:: python + + err = f.set_header_footer( + "Sheet1", + excelize.HeaderFooterOptions( + different_first=True, + different_odd_even=True, + odd_header="&R&P", + odd_footer="&C&F", + even_header="&L&P", + even_footer="&L&D&R&T", + first_header="&CCenter &\"-,Bold\"Bold&\"-,Regular\"HeaderU+000A&D", + ), + ) + """ + lib.SetHeaderFooter.restype = c_char_p + options = py_value_to_c(opts, types_go._HeaderFooterOptions()) + err = lib.SetHeaderFooter( + self.file_index, sheet.encode(ENCODE), byref(options) + ).decode(ENCODE) + return None if err == "" else Exception(err) + + def set_page_layout( + self, sheet: str, opts: PageLayoutOptions + ) -> Optional[Exception]: + """ + Sets worksheet page layout. + + Args: + sheet (str): The worksheet name + opts (PageLayoutOptions): The page layout options + + Returns: + Optional[Exception]: Returns None if no error occurred, + otherwise returns an Exception with the message. + """ + lib.SetPageLayout.restype = c_char_p + options = py_value_to_c(opts, types_go._PageLayoutOptions()) + err = lib.SetPageLayout( + self.file_index, sheet.encode(ENCODE), byref(options) + ).decode(ENCODE) + return None if err == "" else Exception(err) + + def set_page_margins( + self, sheet: str, opts: PageLayoutMarginsOptions + ) -> Optional[Exception]: + """ + Set worksheet page margins. + + Args: + sheet (str): The worksheet name + opts (PageLayoutMarginsOptions): The page margins options + + Returns: + Optional[Exception]: Returns None if no error occurred, + otherwise returns an Exception with the message. + """ + lib.SetPageMargins.restype = c_char_p + options = py_value_to_c(opts, types_go._PageLayoutMarginsOptions()) + err = lib.SetPageMargins( + self.file_index, sheet.encode(ENCODE), byref(options) + ).decode(ENCODE) + return None if err == "" else Exception(err) + + def set_panes(self, sheet: str, opts: Panes) -> Optional[Exception]: + """ + Create and remove freeze panes and split panes by given worksheet name + and panes options. + + Args: + sheet (str): The worksheet name + opts (Panes): The panes options + + Returns: + Optional[Exception]: Returns None if no error occurred, + otherwise returns an Exception with the message. + """ + lib.SetPanes.restype = c_char_p + options = py_value_to_c(opts, types_go._Panes()) + err = lib.SetPanes( + self.file_index, sheet.encode(ENCODE), byref(options) + ).decode(ENCODE) + return None if err == "" else Exception(err) + def set_row_height( self, sheet: str, row: int, height: float ) -> Optional[Exception]: diff --git a/main.go b/main.go index 3e3a924..1f69173 100644 --- a/main.go +++ b/main.go @@ -174,6 +174,10 @@ func cToGoArray(cArray reflect.Value, cArrayLen int) reflect.Value { val := cArray.Interface().(*C.struct_RichTextRun) arr := unsafe.Slice(val, cArrayLen) return reflect.ValueOf(arr) + case "main._Ctype_struct_Selection": + val := cArray.Interface().(*C.struct_Selection) + arr := unsafe.Slice(val, cArrayLen) + return reflect.ValueOf(arr) } return cArray } @@ -1715,6 +1719,88 @@ func SetDefinedName(idx int, definedName *C.struct_DefinedName) *C.char { return C.CString(errNil) } +// SetHeaderFooter provides a function to set headers and footers by given +// worksheet name and the control characters. +// +//export SetHeaderFooter +func SetHeaderFooter(idx int, sheet *C.char, opts *C.struct_HeaderFooterOptions) *C.char { + var options excelize.HeaderFooterOptions + goVal, err := cValueToGo(reflect.ValueOf(*opts), reflect.TypeOf(excelize.HeaderFooterOptions{})) + if err != nil { + return C.CString(err.Error()) + } + options = goVal.Elem().Interface().(excelize.HeaderFooterOptions) + f, ok := files.Load(idx) + if !ok { + return C.CString(errFilePtr) + } + if err := f.(*excelize.File).SetHeaderFooter(C.GoString(sheet), &options); err != nil { + return C.CString(err.Error()) + } + return C.CString(errNil) +} + +// SetPageLayout provides a function to sets worksheet page layout. +// +//export SetPageLayout +func SetPageLayout(idx int, sheet *C.char, opts *C.struct_PageLayoutOptions) *C.char { + var options excelize.PageLayoutOptions + goVal, err := cValueToGo(reflect.ValueOf(*opts), reflect.TypeOf(excelize.PageLayoutOptions{})) + if err != nil { + return C.CString(err.Error()) + } + options = goVal.Elem().Interface().(excelize.PageLayoutOptions) + f, ok := files.Load(idx) + if !ok { + return C.CString(errFilePtr) + } + if err := f.(*excelize.File).SetPageLayout(C.GoString(sheet), &options); err != nil { + return C.CString(err.Error()) + } + return C.CString(errNil) +} + +// SetPageMargins provides a function to set worksheet page margins. +// +//export SetPageMargins +func SetPageMargins(idx int, sheet *C.char, opts *C.struct_PageLayoutMarginsOptions) *C.char { + var options excelize.PageLayoutMarginsOptions + goVal, err := cValueToGo(reflect.ValueOf(*opts), reflect.TypeOf(excelize.PageLayoutMarginsOptions{})) + if err != nil { + return C.CString(err.Error()) + } + options = goVal.Elem().Interface().(excelize.PageLayoutMarginsOptions) + f, ok := files.Load(idx) + if !ok { + return C.CString(errFilePtr) + } + if err := f.(*excelize.File).SetPageMargins(C.GoString(sheet), &options); err != nil { + return C.CString(err.Error()) + } + return C.CString(errNil) +} + +// SetPanes provides a function to create and remove freeze panes and split panes +// by given worksheet name and panes options. +// +//export SetPanes +func SetPanes(idx int, sheet *C.char, opts *C.struct_Panes) *C.char { + var options excelize.Panes + goVal, err := cValueToGo(reflect.ValueOf(*opts), reflect.TypeOf(excelize.Panes{})) + if err != nil { + return C.CString(err.Error()) + } + options = goVal.Elem().Interface().(excelize.Panes) + f, ok := files.Load(idx) + if !ok { + return C.CString(errFilePtr) + } + if err := f.(*excelize.File).SetPanes(C.GoString(sheet), &options); err != nil { + return C.CString(err.Error()) + } + return C.CString(errNil) +} + // SetRowHeight provides a function to set the height of a single row. If the // value of height is 0, will hide the specified row, if the value of height is // -1, will unset the custom row height. diff --git a/test_excelize.py b/test_excelize.py index 908f834..f9e581e 100644 --- a/test_excelize.py +++ b/test_excelize.py @@ -500,6 +500,90 @@ def test_add_form_control(self): self.assertIsNone(f.save_as(os.path.join("test", "TestAddFormControl.xlsm"))) self.assertIsNone(f.close()) + def test_header_footer(self): + f = excelize.new_file() + self.assertIsNone( + f.set_header_footer( + "Sheet1", + excelize.HeaderFooterOptions( + different_first=True, + different_odd_even=True, + odd_header="&R&P", + odd_footer="&C&F", + even_header="&L&P", + even_footer="&L&D&R&T", + first_header='&CCenter &"-,Bold"Bold&"-,Regular"HeaderU+000A&D', + ), + ) + ) + self.assertIsNone(f.save_as(os.path.join("test", "TestHeaderFooter.xlsx"))) + self.assertIsNone(f.close()) + + def test_page_layout(self): + f = excelize.new_file() + self.assertIsNone( + f.set_page_layout( + "Sheet1", + excelize.PageLayoutOptions( + size=1, + orientation="landscape", + first_page_number=1, + adjust_to=120, + fit_to_height=2, + fit_to_width=2, + black_and_white=True, + page_order="overThenDown", + ), + ) + ) + self.assertIsNone(f.save_as(os.path.join("test", "TestPageLayout.xlsx"))) + self.assertIsNone(f.close()) + + def test_page_margins(self): + f = excelize.new_file() + self.assertIsNone( + f.set_page_margins( + "Sheet1", + excelize.PageLayoutMarginsOptions( + bottom=1.0, + footer=1.0, + header=1.0, + left=1.0, + right=1.0, + top=1.0, + horizontally=True, + vertically=True, + ), + ) + ) + self.assertIsNone(f.save_as(os.path.join("test", "TestPageMargins.xlsx"))) + self.assertIsNone(f.close()) + + def test_panes(self): + f = excelize.new_file() + self.assertIsNone( + f.set_panes( + "Sheet1", + excelize.Panes( + freeze=True, + split=False, + x_split=1, + y_split=0, + top_left_cell="B1", + active_pane="topRight", + selection=[ + excelize.Selection( + sq_ref="K16", + active_cell="K16", + pane="topRight", + ) + ], + ), + ) + ) + self.assertIsNone(f.save_as(os.path.join("test", "TestPanes.xlsx"))) + self.assertIsNone(f.close()) + def test_pivot_table(self): f = excelize.new_file() month = [ diff --git a/types_c.h b/types_c.h index 4834251..43b1916 100644 --- a/types_c.h +++ b/types_c.h @@ -143,6 +143,21 @@ struct FormulaOpts char **Ref; }; +// HeaderFooterOptions directly maps the settings of header and footer. +struct HeaderFooterOptions +{ + bool *AlignWithMargins; + bool DifferentFirst; + bool DifferentOddEven; + bool *ScaleWithDoc; + char *OddHeader; + char *OddFooter; + char *EvenHeader; + char *EvenFooter; + char *FirstHeader; + char *FirstFooter; +}; + // HyperlinkOpts can be passed to SetCellHyperlink to set optional hyperlink // attributes (e.g. display value) struct HyperlinkOpts @@ -191,6 +206,32 @@ struct GraphicOptions char *Positioning; }; +// PageLayoutMarginsOptions directly maps the settings of page layout margins. +struct PageLayoutMarginsOptions +{ + double *Bottom; + double *Footer; + double *Header; + double *Left; + double *Right; + double *Top; + bool *Horizontally; + bool *Vertically; +}; + +// PageLayoutOptions directly maps the settings of page layout. +struct PageLayoutOptions +{ + int *Size; + char **Orientation; + unsigned int *FirstPageNumber; + unsigned int *AdjustTo; + int *FitToHeight; + int *FitToWidth; + bool *BlackAndWhite; + char **PageOrder; +}; + // Picture maps the format settings of the picture. struct Picture { @@ -201,6 +242,27 @@ struct Picture unsigned char InsertType; }; +// Selection directly maps the settings of the worksheet selection. +struct Selection +{ + char *SQRef; + char *ActiveCell; + char *Pane; +}; + +// Panes directly maps the settings of the panes. +struct Panes +{ + bool Freeze; + bool Split; + int XSplit; + int YSplit; + char *TopLeftCell; + char *ActivePane; + int SelectionLen; + struct Selection *Selection; +}; + // RichTextRun directly maps the settings of the rich text run. struct RichTextRun { @@ -453,23 +515,23 @@ struct Shape // SheetProtectionOptions directly maps the settings of worksheet protection. struct SheetProtectionOptions { - char *AlgorithmName; - bool AutoFilter; - bool DeleteColumns; - bool DeleteRows; - bool EditObjects; - bool EditScenarios; - bool FormatCells; - bool FormatColumns; - bool FormatRows; - bool InsertColumns; - bool InsertHyperlinks; - bool InsertRows; - char *Password; - bool PivotTables; - bool SelectLockedCells; - bool SelectUnlockedCells; - bool Sort; + char *AlgorithmName; + bool AutoFilter; + bool DeleteColumns; + bool DeleteRows; + bool EditObjects; + bool EditScenarios; + bool FormatCells; + bool FormatColumns; + bool FormatRows; + bool InsertColumns; + bool InsertHyperlinks; + bool InsertRows; + char *Password; + bool PivotTables; + bool SelectLockedCells; + bool SelectUnlockedCells; + bool Sort; }; // SlicerOptions represents the settings of the slicer. diff --git a/types_go.py b/types_go.py index 7fbd234..eca40ea 100644 --- a/types_go.py +++ b/types_go.py @@ -128,6 +128,21 @@ class _FormulaOpts(Structure): ] +class _HeaderFooterOptions(Structure): + _fields_ = [ + ("AlignWithMargins", POINTER(c_bool)), + ("DifferentFirst", c_bool), + ("DifferentOddEven", c_bool), + ("ScaleWithDoc", POINTER(c_bool)), + ("OddHeader", c_char_p), + ("OddFooter", c_char_p), + ("EvenHeader", c_char_p), + ("EvenFooter", c_char_p), + ("FirstHeader", c_char_p), + ("FirstFooter", c_char_p), + ] + + class _HyperlinkOpts(Structure): _fields_ = [ ("Display", POINTER(c_char_p)), @@ -168,6 +183,32 @@ class _GraphicOptions(Structure): ] +class _PageLayoutMarginsOptions(Structure): + _fields_ = [ + ("Bottom", POINTER(c_double)), + ("Footer", POINTER(c_double)), + ("Header", POINTER(c_double)), + ("Left", POINTER(c_double)), + ("Right", POINTER(c_double)), + ("Top", POINTER(c_double)), + ("Horizontally", POINTER(c_bool)), + ("Vertically", POINTER(c_bool)), + ] + + +class _PageLayoutOptions(Structure): + _fields_ = [ + ("Size", POINTER(c_int)), + ("Orientation", POINTER(c_char_p)), + ("FirstPageNumber", POINTER(c_uint)), + ("AdjustTo", POINTER(c_uint)), + ("FitToHeight", POINTER(c_int)), + ("FitToWidth", POINTER(c_int)), + ("BlackAndWhite", POINTER(c_bool)), + ("PageOrder", POINTER(c_char_p)), + ] + + class _Picture(Structure): _fields_ = [ ("Extension", c_char_p), @@ -178,6 +219,27 @@ class _Picture(Structure): ] +class _Selection(Structure): + _fields_ = [ + ("SQRef", c_char_p), + ("ActiveCell", c_char_p), + ("Pane", c_char_p), + ] + + +class _Panes(Structure): + _fields_ = [ + ("Freeze", c_bool), + ("Split", c_bool), + ("XSplit", c_int), + ("YSplit", c_int), + ("TopLeftCell", c_char_p), + ("ActivePane", c_char_p), + ("SelectionLen", c_int), + ("Selection", POINTER(_Selection)), + ] + + class _RichTextRun(Structure): _fields_ = [ ("Font", POINTER(_Font)), diff --git a/types_py.py b/types_py.py index a2ba52e..f39cace 100644 --- a/types_py.py +++ b/types_py.py @@ -265,6 +265,20 @@ class FormulaOpts: ref: Optional[str] = None +@dataclass +class HeaderFooterOptions: + align_with_margins: Optional[bool] = None + different_first: bool = False + different_odd_even: bool = False + scale_with_doc: Optional[bool] = None + odd_header: str = "" + odd_footer: str = "" + even_header: str = "" + even_footer: str = "" + first_header: str = "" + first_footer: str = "" + + @dataclass class HyperlinkOpts: display: Optional[str] = None @@ -311,6 +325,30 @@ class GraphicOptions: positioning: str = "" +@dataclass +class PageLayoutMarginsOptions: + bottom: Optional[float] = (None,) + footer: Optional[float] = (None,) + header: Optional[float] = (None,) + left: Optional[float] = (None,) + right: Optional[float] = (None,) + top: Optional[float] = (None,) + horizontally: Optional[bool] = (None,) + vertically: Optional[bool] = (None,) + + +@dataclass +class PageLayoutOptions: + size: Optional[int] = (None,) + orientation: Optional[str] = (None,) + first_page_number: Optional[int] = (None,) + adjust_to: Optional[int] = (None,) + fit_to_height: Optional[int] = (None,) + fit_to_width: Optional[int] = (None,) + black_and_white: Optional[bool] = (None,) + page_order: Optional[str] = (None,) + + @dataclass class Picture: extension: str = "" @@ -319,6 +357,24 @@ class Picture: insert_type: PictureInsertType = PictureInsertType.PictureInsertTypePlaceOverCells +@dataclass +class Selection: + sq_ref: str = "" + active_cell: str = "" + pane: str = "" + + +@dataclass +class Panes: + freeze: bool = False + split: bool = False + x_split: int = 0 + y_split: int = 0 + top_left_cell: str = "" + active_pane: str = "" + selection: Optional[List[Selection]] = None + + @dataclass class RichTextRun: font: Optional[Font] = None