From 6816ba7571f7c20c62b0980839eef88badeef353 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Fri, 20 Dec 2024 12:15:34 +0545 Subject: [PATCH 01/42] add new docx table celll and border style features and update templaate and test cases --- BeginerGuide/UsingElements/data/template.docx | Bin 44421 -> 71832 bytes BeginerGuide/UsingElements/output/output.docx | Bin 42739 -> 61208 bytes BeginerGuide/UsingElements/using_elements.py | 39 +++++- cloudofficeprint/elements/elements.py | 124 +++++++++++++++++- tests/test_elements.py | 58 +++++++- 5 files changed, 210 insertions(+), 11 deletions(-) diff --git a/BeginerGuide/UsingElements/data/template.docx b/BeginerGuide/UsingElements/data/template.docx index 12db90c7b007207bb583fc410a7336e1fe6124ff..ee0aa61b196b4fce97edbcfc0b5b300d35adcea3 100644 GIT binary patch delta 41242 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 delta 13773 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 pJ{kRLK=9{({iU{|9tKdf)&6 diff --git a/BeginerGuide/UsingElements/output/output.docx b/BeginerGuide/UsingElements/output/output.docx index dba7ec096826db33435291ccdeb9693f26db1e24..e051adce0ee2d3d5be2e7791c938653b414a20fd 100644 GIT binary patch delta 33242 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 gHvcK;eV#Ok?_qsZtQ-%V~|ux$o*oU_G(Uq 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≀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 Zg%Hv~rzd!bSYTLSDbFhvMY4Zh`aesp-va;u diff --git a/BeginerGuide/UsingElements/using_elements.py b/BeginerGuide/UsingElements/using_elements.py index 91dfb6c..1f11d08 100644 --- a/BeginerGuide/UsingElements/using_elements.py +++ b/BeginerGuide/UsingElements/using_elements.py @@ -1,6 +1,9 @@ # Install cloudofficeprint using pip install cloudofficeprint #Import the cloudofficeprint libary. -from ... import cloudofficeprint as cop +import sys +sys.path.insert(0, "PATH_TO_COP_DIR") +import cloudofficeprint as cop + # Main object that holds the data collection = cop.elements.ElementCollection() # Create the title element and add it to the element collection @@ -42,6 +45,36 @@ ) collection.add(styled_prop) +docx_column1_cell_style = cop.elements.CellStyleDocx( + cell_background_color='red', + border_color='0d72c7', + border_top='double', + border_top_size=20, + border_left='dotDash', + border_bottom_color='yellow', + border_left_size='38' + ) +docx_column1_table_style_property = cop.elements.CellStyleProperty( + name='column1', + value='DemoCustomerName', + cell_style=docx_column1_cell_style + ) +collection.add(docx_column1_table_style_property) + +docx_column2_cell_style = cop.elements.CellStyleDocx( + border_right_space=15, + border_diagonal_down='single', + border_diagonal_down_size=10, + border_diagonal_up='single', + border_diagonal_up_color='#0d72c7' + ) +docx_column2_table_style_property = cop.elements.CellStyleProperty( + name='column2', + value='DemoCustomerName', + cell_style=docx_column2_cell_style + ) +collection.add(docx_column2_table_style_property) + # ------------------watermark---------- watermark = cop.elements.Watermark( name='watermark_name', @@ -66,8 +99,8 @@ printjob = cop.PrintJob( data=collection, server=server, - template=cop.Resource.from_local_file("./data/template.docx"), + template=cop.Resource.from_local_file("D:/UC/cloudofficeprint-python/BeginerGuide/UsingElements/data/template.docx"), ) # Execute print job and save response to file response = printjob.execute() -response.to_file("output/output") \ No newline at end of file +response.to_file("D:/UC/cloudofficeprint-python/BeginerGuide/UsingElements/output/output.docx") \ No newline at end of file diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index 8a06339..f811b2a 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -40,7 +40,38 @@ class CellStyleDocx(CellStyle): """Cell styling settings for docx templates""" def __init__( - self, cell_background_color: str = None, width: Union[int, str] = None + self, + cell_background_color: str = None, + width: Union[int, str] = None, + preserve_total_width_of_table: str = None, + border: str = None, + border_top: str = None, + border_bottom: str = None, + border_left: str = None, + border_right: str = None, + border_diagonal_down: str = None, + border_diagonal_up: str = None, + border_color: str = None, + border_top_color: str = None, + border_bottom_color: str = None, + border_left_color: str = None, + border_right_color: str = None, + border_diagonal_up_color: str = None, + border_diagonal_down_color: str = None, + border_size: Union[int, str] = None, + border_top_size: Union[int, str] = None, + border_bottom_size: Union[int, str] = None, + border_left_size: Union[int, str] = None, + border_right_size: Union[int, str] = None, + border_diagonal_up_size: Union[int, str] = None, + border_diagonal_down_size: Union[int, str] = None, + border_space: Union[int, str] = None, + border_top_space: Union[int, str] = None, + border_bottom_space: Union[int, str] = None, + border_left_space: Union[int, str] = None, + border_right_space: Union[int, str] = None, + border_diagonal_up_space: Union[int, str] = None, + border_diagonal_down_space: Union[int, str] = None, ): """ Args: @@ -50,16 +81,101 @@ def __init__( super().__init__() self.cell_background_color: str = cell_background_color self.width: Union[int, str] = width - + self.preserve_total_width_of_table: str = preserve_total_width_of_table + self.border: str = border + self.border_top: str = border_top + self.border_bottom: str = border_bottom + self.border_left: str = border_left + self.border_right: str = border_right + self.border_diagonal_down: str = border_diagonal_down + self.border_diagonal_up: str = border_diagonal_up + self.border_color: str = border_color + self.border_top_color: str = border_top_color + self.border_bottom_color: str = border_bottom_color + self.border_left_color: str = border_left_color + self.border_right_color: str = border_right_color + self.border_diagonal_up_color: str = border_diagonal_up_color + self.border_diagonal_down_color: str = border_diagonal_down_color + self.border_size: Union[int, str] = border_size + self.border_top_size: Union[int, str] = border_top_size + self.border_bottom_size: Union[int, str] = border_bottom_size + self.border_left_size: Union[int, str] = border_left_size + self.border_right_size: Union[int, str] = border_right_size + self.border_diagonal_up_size: Union[int, str] = border_diagonal_up_size + self.border_diagonal_down_size: Union[int, str] = border_diagonal_down_size + self.border_space: Union[int, str] = border_space + self.border_top_space: Union[int, str] = border_top_space + self.border_bottom_space: Union[int, str] = border_bottom_space + self.border_left_space: Union[int, str] = border_left_space + self.border_right_space: Union[int, str] = border_right_space + self.border_diagonal_up_space: Union[int, str] = border_diagonal_up_space + self.border_diagonal_down_space: Union[int, str] = border_diagonal_down_space + @property def _dict_suffixes(self): result = super()._dict_suffixes - if self.cell_background_color is not None: result["_cell_background_color"] = self.cell_background_color if self.width is not None: result["_width"] = self.width - + if self.preserve_total_width_of_table is not None: + result["_preserve_total_width_of_table"] = self.preserve_total_width_of_table + if self.border is not None: + result["_border"] = self.border + if self.border_top is not None: + result["_border_top"] = self.border_top + if self.border_bottom is not None: + result["_border_bottom"] = self.border_bottom + if self.border_left is not None: + result["_border_left"] = self.border_left + if self.border_right is not None: + result["_border_right"] = self.border_right + if self.border_diagonal_down is not None: + result["_border_diagonal_down"] = self.border_diagonal_down + if self.border_diagonal_up is not None: + result["_border_diagonal_up"] = self.border_diagonal_up + if self.border_color is not None: + result["_border_color"] = self.border_color + if self.border_top_color is not None: + result["_border_top_color"] = self.border_top_color + if self.border_bottom_color is not None: + result["_border_bottom_color"] = self.border_bottom_color + if self.border_left_color is not None: + result["_border_left_color"] = self.border_left_color + if self.border_right_color is not None: + result["_border_right_color"] = self.border_right_color + if self.border_diagonal_up_color is not None: + result["_border_diagonal_up_color"] = self.border_diagonal_up_color + if self.border_diagonal_down_color is not None: + result["_border_diagonal_down_color"] = self.border_diagonal_down_color + if self.border_size is not None: + result["_border_size"] = self.border_size + if self.border_top_size is not None: + result["_border_top_size"] = self.border_top_size + if self.border_bottom_size is not None: + result["_border_bottom_size"] = self.border_bottom_size + if self.border_left_size is not None: + result["_border_left_size"] = self.border_left_size + if self.border_right_size is not None: + result["_border_right_size"] = self.border_right_size + if self.border_diagonal_up_size is not None: + result["_border_diagonal_up_size"] = self.border_diagonal_up_size + if self.border_diagonal_down_size is not None: + result["_border_diagonal_down_size"] = self.border_diagonal_down_size + if self.border_space is not None: + result["_border_space"] = self.border_space + if self.border_top_space is not None: + result["_border_top_space"] = self.border_top_space + if self.border_bottom_space is not None: + result["_border_bottom_space"] = self.border_bottom_space + if self.border_left_space is not None: + result["_border_left_space"] = self.border_left_space + if self.border_right_space is not None: + result["_border_right_space"] = self.border_right_space + if self.border_diagonal_up_space is not None: + result["_border_diagonal_up_space"] = self.border_diagonal_up_space + if self.border_diagonal_down_space is not None: + result["_border_diagonal_down_space"] = self.border_diagonal_down_space return result diff --git a/tests/test_elements.py b/tests/test_elements.py index a35cc90..f5c06e7 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -1,3 +1,5 @@ +import sys +sys.path.insert(0, "PATH_TO_COP_DIR") import cloudofficeprint as cop @@ -30,6 +32,52 @@ def test_cell_style_property_docx(): } assert style_property.as_dict == style_property_expected +def test_cell_style_property_docx_preserve_width(): + style = cop.elements.CellStyleDocx( + width=10, + preserve_total_width_of_table='true' + ) + style_property = cop.elements.CellStyleProperty( + name='name', + value='value', + cell_style=style + ) + style_property_expected = { + 'name': 'value', + 'name_width': 10, + 'name_preserve_total_width_of_table': 'true' + } + assert style_property.as_dict == style_property_expected + +def test_cell_style_border_property_docx(): + style = cop.elements.CellStyleDocx( + border='double', + border_top_color='red', + border_diagonal_down_size=38, + border_bottom='thick', + border_bottom_color='#ff0000', + border_bottom_size='10', + border_right='wave', + border_right_space=5 + ) + style_property = cop.elements.CellStyleProperty( + name='name', + value='value', + cell_style=style + ) + style_property_expected = { + 'name': 'value', + 'name_border': 'double', + 'name_border_top_color': 'red', + 'name_border_diagonal_down_size': 38, + 'name_border_bottom': 'thick', + 'name_border_bottom_color': '#ff0000', + 'name_border_bottom_size': '10', + 'name_border_right': 'wave', + 'name_border_right_space': 5 + } + assert style_property.as_dict == style_property_expected + def test_cell_style_property_xlsx(): style = cop.elements.CellStyleXlsx( @@ -370,6 +418,8 @@ def test_cell_validation(): def run(): test_property() test_cell_style_property_docx() + test_cell_style_property_docx_preserve_width() + test_cell_style_border_property_docx() test_cell_style_property_xlsx() test_autoLink() test_hyperlink() @@ -381,11 +431,11 @@ def run(): test_text_box() test_element_collection() test_freeze_element() - test_protect_element() + # test_protect_element() test_insert_element() - test_embed_element() - test_excel_insert_element() - test_cell_validation() + # test_embed_element() + # test_excel_insert_element() + # test_cell_validation() # COP charts get tested in test_charts.py From 2558bdbce69dea41c69abba2c7376c4ce8bbf045 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Tue, 24 Dec 2024 17:31:53 +0545 Subject: [PATCH 02/42] add {remove?} feature to remove the added shape completely when the tag has falsy values for pptx --- cloudofficeprint/elements/elements.py | 18 +++++++++++++++++- tests/test_elements.py | 9 ++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index f811b2a..f51923d 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -1039,7 +1039,7 @@ def __init__(self, name: str, value: str): Args: name (str): The name for the insert tag. value (str): Base64 encoded document that needs to be inserted in output docx or pptx. - The documnet can be docx, pptx, xlsx, or pdf documents. + The document can be docx, pptx, xlsx, or pdf documents. """ super().__init__(name, value) @@ -1047,6 +1047,22 @@ def __init__(self, name: str, value: str): def available_tags(self) -> FrozenSet[str]: return frozenset({"{?insert " + self.name + "}"}) +class Remove(Property): + """Allows the removal of an entire shape / text-box if the associated tag evaluates to false. For example, if a template slide includes a text box with the tag {toShow?} and + the value of toShow is false or undefined, the entire shape will be removed from the slide. + """ + def __init__(self, name: str, value: Union[bool, str]): + """ + Args: + name (str): The name for the remove tag. + value (bool or string): False (to remove the shape / text-box) or string/True + The document should be, pptx. + """ + super().__init__(name, value) + + @property + def available_tags(self) -> FrozenSet[str]: + return frozenset({"{ " + self.name + "?" + "}"}) class ExcelInsert(Element): """Inside Excel it is posiible to insert word, powerpoint, excel and pdf file using AOP tag {?insert fileToInsert}. diff --git a/tests/test_elements.py b/tests/test_elements.py index f5c06e7..5c26931 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -1,5 +1,5 @@ import sys -sys.path.insert(0, "PATH_TO_COP_DIR") +sys.path.insert(0, "D:/UC/cloudofficeprint-python") import cloudofficeprint as cop @@ -374,6 +374,12 @@ def test_insert_element(): } assert insertElement.as_dict == insertElement_expected +def test_remove_txt_box(): + remove = cop.elements.Remove('greetings', False) + remove_expected = { + "greetings":False + } + assert remove.as_dict == remove_expected def test_embed_element(): embedElement = cop.elements.Embed("fileToEmbed","base64EncodedValue") embedElement_expected = { @@ -436,6 +442,7 @@ def run(): # test_embed_element() # test_excel_insert_element() # test_cell_validation() + test_remove_txt_box # COP charts get tested in test_charts.py From 3ec7eed67fb5f588c738b7addb3bad7d0b8b2850 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Wed, 25 Dec 2024 11:30:41 +0545 Subject: [PATCH 03/42] add new pdf option convert_to_pdfa for generating PDF/A format and update test_config.py --- cloudofficeprint/config/pdf.py | 6 ++++++ tests/test_config.py | 19 ++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cloudofficeprint/config/pdf.py b/cloudofficeprint/config/pdf.py index 8d3aead..4580c0e 100644 --- a/cloudofficeprint/config/pdf.py +++ b/cloudofficeprint/config/pdf.py @@ -36,6 +36,8 @@ def __init__( identify_form_fields: bool = None, sign_certificate: str = None, sign_certificate_password: str = None, + convert_to_pdfa: str = None, + ): """ Args: @@ -62,6 +64,7 @@ def __init__( identify_form_fields (bool, optional): Identify the form fields in a PDF-form by filling the name of each field into the respective field. Defaults to None. sign_certificate (str, optional): Signing certificate for the output PDF (pkcs #12 .p12/.pfx) as a base64 string, URL, FTP location or a server path. The function read_file_as_base64() from file_utils.py can be used to read local .p12 or .pfx file as base64. Defaults to None. sign_certificate_password (str, optional): If you are signing with a password protected certificate, you can specify the password as a plain string. Defaults to None. + convert_to_pdfa (str, optional): For generating PDF/A format. While converting using openoffice converter, specifying it will create PDF/A format, values can be either 1b or 2b which are the variants of PDF/A specification. """ self.even_page: bool = even_page self.merge_making_even: bool = merge_making_even @@ -86,6 +89,7 @@ def __init__( self.identify_form_fields: bool = identify_form_fields self.sign_certificate: str = sign_certificate self.sign_certificate_password: str = sign_certificate_password + self.convert_to_pdfa: str = convert_to_pdfa def __str__(self) -> str: """Get the string representation of these PDF options. @@ -164,6 +168,8 @@ def as_dict(self) -> Dict: result["output_sign_certificate"] = self.sign_certificate if self.sign_certificate_password is not None: result["output_sign_certificate_password"] = self.sign_certificate_password + if self.convert_to_pdfa is not None: + result["output_convert_to_pdfa"] = self.convert_to_pdfa return result diff --git a/tests/test_config.py b/tests/test_config.py index 3dc74d6..af759f9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,8 +1,11 @@ +import sys +sys.path.insert(0, "D:/UC/cloudofficeprint-python") import cloudofficeprint as cop def test_pdf_options(): """Test class PDFOptions in combination with OutputConfig""" + pdf_opts = cop.config.PDFOptions( even_page=True, merge_making_even=False, @@ -27,11 +30,12 @@ def test_pdf_options(): identify_form_fields=True, sign_certificate="test_sign_certificate", sign_certificate_password="test_certificate_password", + convert_to_pdfa = "1b", ) pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32) pdf_opts.set_page_margin_at(6, "top") pdf_opts.page_orientation = "portrait" - conf = cop.config.OutputConfig(filetype="pdf", pdf_options=pdf_opts) + conf = cop.config.OutputConfig(filetype="pdf", converter="openoffice", pdf_options=pdf_opts) conf_expected = { "output_even_page": True, "output_merge_making_even": False, @@ -46,7 +50,7 @@ def test_pdf_options(): "output_watermark_size": 32, "output_type": "pdf", "output_encoding": "raw", - "output_converter": "libreoffice", + "output_converter": "openoffice", "output_page_width": 500, "output_page_height": 500, "lock_form": True, @@ -62,6 +66,7 @@ def test_pdf_options(): "output_merge": False, "output_sign_certificate": "test_sign_certificate", "output_sign_certificate_password": "test_certificate_password", + "output_convert_to_pdfa": "1b", "identify_form_fields": True, "output_split": True, } @@ -264,11 +269,11 @@ def test_request_option(): def run(): test_pdf_options() - test_csv_options() - test_printer() - test_cloud_access_tokens() - test_commands() - test_route_paths() + # test_csv_options() + # test_printer() + # test_cloud_access_tokens() + # test_commands() + # test_route_paths() if __name__ == "__main__": From c24f7b1c20a9eb96455cb731655cfcee46a9c765 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Wed, 25 Dec 2024 16:05:02 +0545 Subject: [PATCH 04/42] add option to addd custom text to signature field --- cloudofficeprint/config/pdf.py | 5 +++++ tests/test_config.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/cloudofficeprint/config/pdf.py b/cloudofficeprint/config/pdf.py index 4580c0e..d17e943 100644 --- a/cloudofficeprint/config/pdf.py +++ b/cloudofficeprint/config/pdf.py @@ -36,6 +36,7 @@ def __init__( identify_form_fields: bool = None, sign_certificate: str = None, sign_certificate_password: str = None, + sign_certificate_txt: str = None, convert_to_pdfa: str = None, ): @@ -64,6 +65,7 @@ def __init__( identify_form_fields (bool, optional): Identify the form fields in a PDF-form by filling the name of each field into the respective field. Defaults to None. sign_certificate (str, optional): Signing certificate for the output PDF (pkcs #12 .p12/.pfx) as a base64 string, URL, FTP location or a server path. The function read_file_as_base64() from file_utils.py can be used to read local .p12 or .pfx file as base64. Defaults to None. sign_certificate_password (str, optional): If you are signing with a password protected certificate, you can specify the password as a plain string. Defaults to None. + sign_certificate_txt (str, optional) Add custom text in any language to the signature field convert_to_pdfa (str, optional): For generating PDF/A format. While converting using openoffice converter, specifying it will create PDF/A format, values can be either 1b or 2b which are the variants of PDF/A specification. """ self.even_page: bool = even_page @@ -89,6 +91,7 @@ def __init__( self.identify_form_fields: bool = identify_form_fields self.sign_certificate: str = sign_certificate self.sign_certificate_password: str = sign_certificate_password + self.sign_certificate_txt: str = sign_certificate_txt self.convert_to_pdfa: str = convert_to_pdfa def __str__(self) -> str: @@ -168,6 +171,8 @@ def as_dict(self) -> Dict: result["output_sign_certificate"] = self.sign_certificate if self.sign_certificate_password is not None: result["output_sign_certificate_password"] = self.sign_certificate_password + if self.sign_certificate_txt is not None: + result["output_sign_certificate_txt"] = self.sign_certificate_txt if self.convert_to_pdfa is not None: result["output_convert_to_pdfa"] = self.convert_to_pdfa diff --git a/tests/test_config.py b/tests/test_config.py index af759f9..4907a7f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -30,6 +30,7 @@ def test_pdf_options(): identify_form_fields=True, sign_certificate="test_sign_certificate", sign_certificate_password="test_certificate_password", + sign_certificate_txt="text in english", convert_to_pdfa = "1b", ) pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32) @@ -66,6 +67,7 @@ def test_pdf_options(): "output_merge": False, "output_sign_certificate": "test_sign_certificate", "output_sign_certificate_password": "test_certificate_password", + "output_sign_certificate_txt": "text in english", "output_convert_to_pdfa": "1b", "identify_form_fields": True, "output_split": True, From a8edf104e6f28c276759562f67ac396a4eb5391f Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Thu, 26 Dec 2024 11:00:46 +0545 Subject: [PATCH 05/42] add new option watermark_rotation for pdf config --- cloudofficeprint/config/pdf.py | 8 ++++++++ tests/test_config.py | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cloudofficeprint/config/pdf.py b/cloudofficeprint/config/pdf.py index d17e943..3518f75 100644 --- a/cloudofficeprint/config/pdf.py +++ b/cloudofficeprint/config/pdf.py @@ -24,6 +24,7 @@ def __init__( watermark_font: str = None, watermark_opacity: int = None, watermark_size: int = None, + watermark_rotation: int = None, lock_form: bool = None, copies: int = None, page_margin: Union[int, dict] = None, @@ -53,6 +54,7 @@ def __init__( watermark_font (str, optional): Requires PDF output, specifies the font of the watermark text specified, with a default of "Arial". Defaults to None. watermark_opacity (int, optional): Requires PDF output, specifies the opacity of the watermark text specified, should be as a percentage, i.e. 45. Defaults to None. watermark_size (int, optional): Requires PDF output, specifies the size of watermark text specified, should be a number in px, i.e. 45. Defaults to None. + watermark_rotation (int, optional): Requires PDF output, specifies the angle of watermark text specified, should be a number, i.e. 45. Defaults to None. lock_form (bool, optional): Locks / flattens the forms in the PDF. Defaults to None. copies (int, optional): Repeats the output pdf for the given number of times. Defaults to None. page_margin (Union[int, dict], optional): Only for HTML to PDF. Margin in px. Returns either a dict containing: { "top": int, "bottom": int, "left": int, "right": int } or just an int to be used on all sides. Defaults to None. @@ -79,6 +81,7 @@ def __init__( self.watermark_font: str = watermark_font self.watermark_opacity: int = watermark_opacity self.watermark_size: int = watermark_size + self.watermark_rotation: int = watermark_rotation self.lock_form: bool = lock_form self.copies: int = copies self.page_margin: Union[int, dict] = page_margin @@ -145,6 +148,8 @@ def as_dict(self) -> Dict: result["output_watermark_opacity"] = self.watermark_opacity if self.watermark_size is not None: result["output_watermark_size"] = self.watermark_size + if self.watermark_rotation is not None: + result["output_watermark_rotation"] = self.watermark_rotation if self.lock_form is not None: result["lock_form"] = self.lock_form if self.copies is not None: @@ -185,6 +190,7 @@ def set_watermark( font: str = None, opacity: int = None, size: int = None, + rotation: int = None, ): """Set watermark @@ -197,12 +203,14 @@ def set_watermark( font (str, optional): Requires PDF output, specifies the font of the watermark text specified, with a default of "Arial". Defaults to None. opacity (int, optional): Requires PDF output, specifies the opacity of the watermark text specified, should be as a percentage, i.e. 45. Defaults to None. size (int, optional): Requires PDF output, specifies the size of watermark text specified, should be a number in px, i.e. 45. Defaults to None. + rotation (int, optional): Requires PDF output, specifies the angle of watermark text specified, should be a number in px, i.e. 45. Defaults to None. """ self.watermark = text self.watermark_color = color self.watermark_font = font self.watermark_opacity = opacity self.watermark_size = size + self.watermark_rotation = rotation def set_page_margin_at(self, value: int, position: str = None): """Set page_margin diff --git a/tests/test_config.py b/tests/test_config.py index 4907a7f..e1eda88 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -33,7 +33,7 @@ def test_pdf_options(): sign_certificate_txt="text in english", convert_to_pdfa = "1b", ) - pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32) + pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32, 45) pdf_opts.set_page_margin_at(6, "top") pdf_opts.page_orientation = "portrait" conf = cop.config.OutputConfig(filetype="pdf", converter="openoffice", pdf_options=pdf_opts) @@ -49,6 +49,7 @@ def test_pdf_options(): "output_watermark_font": "Arial", "output_watermark_opacity": 51, "output_watermark_size": 32, + "output_watermark_rotation": 45, "output_type": "pdf", "output_encoding": "raw", "output_converter": "openoffice", From a5c3b4ac10b52f3e1899ae6c5f5d8c4ceae5b7d2 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Thu, 26 Dec 2024 11:45:50 +0545 Subject: [PATCH 06/42] add new option to provide start of the page number in output config --- cloudofficeprint/config/output.py | 5 +++++ tests/test_config.py | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cloudofficeprint/config/output.py b/cloudofficeprint/config/output.py index 9c80c69..7ce68a2 100644 --- a/cloudofficeprint/config/output.py +++ b/cloudofficeprint/config/output.py @@ -22,6 +22,7 @@ def __init__(self, prepend_per_page: bool = None, output_polling: bool = None, secret_key: str = None, + page_number_start_at: str = None, request_option: requestOptions = None): """If the parameters are not provided default value will be used. @@ -37,6 +38,7 @@ def __init__(self, output_polling (bool, optional): A unique link for each request is sent back, which can be used later to download the output file. Defaults to None. secret_key (str, optional): A secret key can be specified to encrypt the file stored on the server (ussed with output polling). Defaults to None. request_option (requestOptions, optional): AOP makes a call to the given option with response/output of the current request. Defaults to None. + page_number_start_at (str, optional): Provide start of the page number. Defaults to None. """ self.filetype: str = filetype self.converter: str = converter @@ -48,6 +50,7 @@ def __init__(self, self.prepend_per_page = prepend_per_page self.output_polling = output_polling self.secret_key = secret_key + self.page_number_start_at = page_number_start_at self.request_option = request_option @property @@ -89,6 +92,8 @@ def as_dict(self) -> Dict: result['output_polling'] = self.output_polling if self.secret_key is not None: result['secret_key'] = self.secret_key + if self.page_number_start_at is not None: + result['output_page_number_start_at'] = self.page_number_start_at if self.request_option is not None: result['request_option'] = self.request_option.as_dict return result diff --git a/tests/test_config.py b/tests/test_config.py index e1eda88..1452fb9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -36,7 +36,7 @@ def test_pdf_options(): pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32, 45) pdf_opts.set_page_margin_at(6, "top") pdf_opts.page_orientation = "portrait" - conf = cop.config.OutputConfig(filetype="pdf", converter="openoffice", pdf_options=pdf_opts) + conf = cop.config.OutputConfig(filetype="pdf", converter="openoffice", page_number_start_at="5", pdf_options=pdf_opts) conf_expected = { "output_even_page": True, "output_merge_making_even": False, @@ -53,6 +53,7 @@ def test_pdf_options(): "output_type": "pdf", "output_encoding": "raw", "output_converter": "openoffice", + "output_page_number_start_at": "5", "output_page_width": 500, "output_page_height": 500, "lock_form": True, @@ -272,11 +273,11 @@ def test_request_option(): def run(): test_pdf_options() - # test_csv_options() - # test_printer() - # test_cloud_access_tokens() - # test_commands() - # test_route_paths() + test_csv_options() + test_printer() + test_cloud_access_tokens() + test_commands() + test_route_paths() if __name__ == "__main__": From 03a3336e20c179962214e4629b084ada10bc6730 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Fri, 27 Dec 2024 11:52:58 +0545 Subject: [PATCH 07/42] add new option to hide pptx slides --- cloudofficeprint/elements/elements.py | 19 +++++++++++++++++++ tests/test_elements.py | 13 ++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index f51923d..1bfb5d6 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -1063,6 +1063,25 @@ def __init__(self, name: str, value: Union[bool, str]): @property def available_tags(self) -> FrozenSet[str]: return frozenset({"{ " + self.name + "?" + "}"}) + + +class HideSlide(Property): + """Allows hiding a slide. + LIMITATION using _hide for slide hide is that it can only be used for hiding slides during generation(!slideGeneration) + """ + def __init__(self, name: str, value: bool): + """ + Args: + name (str): The name of slide to hide. + value (bool): True (to hide) or False + The document should be, pptx. + """ + super().__init__(name, value) + + @property + def as_dict(self) -> Dict: + result = {self.name + "_hide": self.value} + return result class ExcelInsert(Element): """Inside Excel it is posiible to insert word, powerpoint, excel and pdf file using AOP tag {?insert fileToInsert}. diff --git a/tests/test_elements.py b/tests/test_elements.py index 5c26931..4dd30fa 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -380,6 +380,16 @@ def test_remove_txt_box(): "greetings":False } assert remove.as_dict == remove_expected + +def test_hide_slide_pptx(): + remove = cop.elements.HideSlide('product', True) + remove_expected = { + "product_hide": True + } + # for debug + # print("Actual output of remove.as_dict:", remove.as_dict) + assert remove.as_dict == remove_expected + def test_embed_element(): embedElement = cop.elements.Embed("fileToEmbed","base64EncodedValue") embedElement_expected = { @@ -442,7 +452,8 @@ def run(): # test_embed_element() # test_excel_insert_element() # test_cell_validation() - test_remove_txt_box + test_remove_txt_box() + test_hide_slide_pptx() # COP charts get tested in test_charts.py From 6b48a39fdb4c0a4624a8240cc95b94ad06a28116 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Mon, 30 Dec 2024 10:56:45 +0545 Subject: [PATCH 08/42] add new options in autolink and hyperlink tags to provide font_color and underline_color --- cloudofficeprint/elements/elements.py | 25 +++++++++++++++++++++++-- tests/test_elements.py | 15 ++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index 1bfb5d6..9d4d5a0 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -484,21 +484,36 @@ class AutoLink(Property): """ This tag allows you to insert text into the document detecting links. """ - def __init__(self, name: str, value: str): + def __init__(self, name: str, value: str, font_color: str = None, underline_color: str = None): """ Args: name (str): The name for this element. value (str): The value of the autoLink. + font_color (str, optional): The font color of autolink. + underline_color (str, optional): The underline color of autolink. """ super().__init__(name, value) + self.value: str = value + self.font_color: str = font_color + self.underline_color: str = underline_color @property def available_tags(self) -> FrozenSet[str]: return frozenset({"{*auto " + self.name + "}"}) + + @property + def as_dict(self) -> Dict: + result = {self.name: self.value} + + if self.font_color is not None: + result[self.name + "_font_color"] = self.font_color + if self.underline_color is not None: + result[self.name + "_underline_color"] = self.underline_color + return result class Hyperlink(Element): - def __init__(self, name: str, url: str, text: str = None): + def __init__(self, name: str, url: str, text: str = None, font_color: str = None, underline_color: str = None): """ Args: name (str): The name for this element. @@ -508,6 +523,8 @@ def __init__(self, name: str, url: str, text: str = None): super().__init__(name) self.url: str = url self.text: str = text + self.font_color: str = font_color + self.underline_color: str = underline_color @property def available_tags(self) -> FrozenSet[str]: @@ -519,6 +536,10 @@ def as_dict(self) -> Dict: if self.text is not None: result[self.name + "_text"] = self.text + if (self.text is not None) and (self.font_color is not None): + result[self.text + "_font_color"] = self.font_color + if (self.text is not None) and (self.underline_color is not None): + result[self.text + "_underline_color"] = self.underline_color return result diff --git a/tests/test_elements.py b/tests/test_elements.py index 4dd30fa..ce5c83e 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -149,9 +149,14 @@ def test_autoLink(): autoLink = cop.elements.AutoLink( name='autoLink', value='sample text with hyperlinks', + font_color='red', + underline_color='#ffffffff' ) autoLink_expected = { - 'autoLink': 'sample text with hyperlinks' + 'autoLink': 'sample text with hyperlinks', + 'autoLink_font_color': 'red', + 'autoLink_underline_color': '#ffffffff' + } assert autoLink.as_dict == autoLink_expected @@ -160,11 +165,15 @@ def test_hyperlink(): hyperlink = cop.elements.Hyperlink( name='hyperlink', url='url', - text='hyperlink_text' + text='hyperlink_text', + font_color='red', + underline_color='#ffffffff' ) hyperlink_expected = { 'hyperlink': 'url', - 'hyperlink_text': 'hyperlink_text' + 'hyperlink_text': 'hyperlink_text', + 'hyperlink_text_font_color': 'red', + 'hyperlink_text_underline_color': '#ffffffff' } assert hyperlink.as_dict == hyperlink_expected From 26c494ade2e91528493df14e99fdab4dbfb3a3fc Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Mon, 30 Dec 2024 13:57:39 +0545 Subject: [PATCH 09/42] add feature to attach files in pdf and updated output options to retrive attachments in pdf --- cloudofficeprint/config/pdf.py | 11 +++++++ cloudofficeprint/printjob.py | 7 +++++ tests/test_config.py | 2 ++ tests/test_printjob.py | 53 ++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/cloudofficeprint/config/pdf.py b/cloudofficeprint/config/pdf.py index 3518f75..083b7d0 100644 --- a/cloudofficeprint/config/pdf.py +++ b/cloudofficeprint/config/pdf.py @@ -39,6 +39,8 @@ def __init__( sign_certificate_password: str = None, sign_certificate_txt: str = None, convert_to_pdfa: str = None, + attachment_name: str = None, + convert_attachment_to_json: bool = None, ): """ @@ -69,6 +71,8 @@ def __init__( sign_certificate_password (str, optional): If you are signing with a password protected certificate, you can specify the password as a plain string. Defaults to None. sign_certificate_txt (str, optional) Add custom text in any language to the signature field convert_to_pdfa (str, optional): For generating PDF/A format. While converting using openoffice converter, specifying it will create PDF/A format, values can be either 1b or 2b which are the variants of PDF/A specification. + attachment_name (str, optional): To retrieve specific attachment. output_type must be get_attachments. + convert_attachment_to_json (bool, optional): To retrieve data of the XML attachment as a JSON. output_type must be get_attachments. """ self.even_page: bool = even_page self.merge_making_even: bool = merge_making_even @@ -96,6 +100,9 @@ def __init__( self.sign_certificate_password: str = sign_certificate_password self.sign_certificate_txt: str = sign_certificate_txt self.convert_to_pdfa: str = convert_to_pdfa + self.attachment_name: str = attachment_name + self.convert_attachment_to_json: bool = convert_attachment_to_json + def __str__(self) -> str: """Get the string representation of these PDF options. @@ -180,6 +187,10 @@ def as_dict(self) -> Dict: result["output_sign_certificate_txt"] = self.sign_certificate_txt if self.convert_to_pdfa is not None: result["output_convert_to_pdfa"] = self.convert_to_pdfa + if self.attachment_name is not None: + result["output_attachment_name"] = self.attachment_name + if self.convert_attachment_to_json is not None: + result["output_convert_attachment_to_json"] = self.convert_attachment_to_json return result diff --git a/cloudofficeprint/printjob.py b/cloudofficeprint/printjob.py index 11ac545..cf62d88 100644 --- a/cloudofficeprint/printjob.py +++ b/cloudofficeprint/printjob.py @@ -40,6 +40,7 @@ def __init__( subtemplates: Dict[str, Resource] = {}, prepend_files: List[Resource] = [], append_files: List[Resource] = [], + attachments : List[Resource] = [], cop_verbose: bool = False, ): """ @@ -51,6 +52,7 @@ def __init__( subtemplates (Dict[str, Resource], optional): Subtemplates for this print job, accessible (in docx) through `{?include subtemplate_dict_key}`. Defaults to {}. prepend_files (List[Resource], optional): Files to prepend to the output file. Defaults to []. append_files (List[Resource], optional): Files to append to the output file. Defaults to []. + attachments (List[Resource], optional): Files to attach to the pdf file. Defaults to []. The file must be PDF. cop_verbose (bool, optional): Whether or not verbose mode should be activated. Defaults to False. """ self.data: Union[Element, Mapping[str, Element], RESTSource] = data @@ -60,6 +62,7 @@ def __init__( self.subtemplates: Dict[str, Resource] = subtemplates self.prepend_files: List[Resource] = prepend_files self.append_files: List[Resource] = append_files + self.attachments: List[Resource] = attachments self.cop_verbose: bool = cop_verbose def execute(self) -> Response: @@ -228,6 +231,10 @@ def as_dict(self) -> Dict: result["append_files"] = [ file.secondary_file_dict for file in self.append_files ] + if len(self.attachments) > 0: + result["attachments"] = [ + file.secondary_file_dict for file in self.attachments + ] if len(self.subtemplates) > 0: result["templates"] = [ diff --git a/tests/test_config.py b/tests/test_config.py index 1452fb9..08067de 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -32,6 +32,7 @@ def test_pdf_options(): sign_certificate_password="test_certificate_password", sign_certificate_txt="text in english", convert_to_pdfa = "1b", + convert_attachment_to_json= True, ) pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32, 45) pdf_opts.set_page_margin_at(6, "top") @@ -71,6 +72,7 @@ def test_pdf_options(): "output_sign_certificate_password": "test_certificate_password", "output_sign_certificate_txt": "text in english", "output_convert_to_pdfa": "1b", + "output_convert_attachment_to_json": True, "identify_form_fields": True, "output_split": True, } diff --git a/tests/test_printjob.py b/tests/test_printjob.py index 5036f85..5d5ffd9 100644 --- a/tests/test_printjob.py +++ b/tests/test_printjob.py @@ -1,3 +1,7 @@ +# import cloudofficeprint as cop + +import sys +sys.path.insert(0, "D:/UC/cloudofficeprint-python") import cloudofficeprint as cop @@ -76,7 +80,55 @@ def test_printjob(): assert printjob.as_dict == printjob_expected # printjob.execute().to_file("tests/data/prepend_append_subtemplate_test") # Works as expected +def test_pdf_attachment(): + """ + """ + SERVER_URL = "https://api.cloudofficeprint.com/" + API_KEY = "YOUR_API_KEY" + + server = cop.config.Server(SERVER_URL, cop.config.ServerConfig(api_key=API_KEY)) + resource = cop.Resource.from_local_file("./tests/data/template.docx") + template = cop.Template.from_local_file( + "./tests/data/template_prepend_append_subtemplate.docx" + ) + + resource_base64 = resource.data + template_base64 = template.resource.data + + data = cop.elements.ElementCollection("data") + data.add(cop.elements.Property("textTag1", "test_text_tag1")) + + output_conf = cop.config.OutputConfig(filetype="pdf") + + printjob = cop.PrintJob( + data=data, + server=server, + template=template, + output_config=output_conf, + attachments=[resource], + ) + + printjob_expected = { + "api_key": server.config.api_key, + "attachments": [ + { + "file_content": resource_base64, + "file_source": "base64", + "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + } + ], + "files": [{"data": {"textTag1": "test_text_tag1"}}], + "output": { + "output_converter": "libreoffice", + "output_encoding": "raw", + "output_type": "pdf", + }, + "template": {"file": template_base64, "template_type": "docx"}, + "tool": "python", + "python_sdk_version": cop.printjob.STATIC_OPTS["python_sdk_version"], + } + def test_template_hashing_printjob(): """Test all options for printjob""" @@ -138,6 +190,7 @@ def test_template_hashing_printjob(): def run(): test_printjob() + test_pdf_attachment() if __name__ == "__main__": From 806a1e6f513175bdebbd65e572e81d27790f1c55 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Mon, 30 Dec 2024 17:21:34 +0545 Subject: [PATCH 10/42] add _distribute option to distribute the data evenly among the columns in docx --- cloudofficeprint/elements/elements.py | 16 ++++++++++++++++ tests/test_elements.py | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index 9d4d5a0..a0b7684 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -1103,6 +1103,22 @@ def __init__(self, name: str, value: bool): def as_dict(self) -> Dict: result = {self.name + "_hide": self.value} return result +class Distribute(Property): + """Allows to distribute the data evenly among the columns in horizontal loop + """ + def __init__(self, name: str, value: bool): + """ + Args: + name (str): The name of data. + value (bool): True (to hide) or False + The document should be, docx. + """ + super().__init__(name, value) + + @property + def as_dict(self) -> Dict: + result = {self.name + "_distribute": self.value} + return result class ExcelInsert(Element): """Inside Excel it is posiible to insert word, powerpoint, excel and pdf file using AOP tag {?insert fileToInsert}. diff --git a/tests/test_elements.py b/tests/test_elements.py index ce5c83e..10dea54 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -399,6 +399,13 @@ def test_hide_slide_pptx(): # print("Actual output of remove.as_dict:", remove.as_dict) assert remove.as_dict == remove_expected +def test_distribute(): + remove = cop.elements.Distribute('product_b', True) + remove_expected = { + "product_b__distribute": True + } + assert remove.as_dict == remove_expected + def test_embed_element(): embedElement = cop.elements.Embed("fileToEmbed","base64EncodedValue") embedElement_expected = { From c41bdd4f8731682bfdef5c5d16f63fa8dd2702a4 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Tue, 31 Dec 2024 10:55:57 +0545 Subject: [PATCH 11/42] add preserve_tag_style option for hyperlink and autolink --- cloudofficeprint/elements/elements.py | 14 ++++++++++++-- tests/test_elements.py | 12 ++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index a0b7684..b6964f3 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -484,18 +484,20 @@ class AutoLink(Property): """ This tag allows you to insert text into the document detecting links. """ - def __init__(self, name: str, value: str, font_color: str = None, underline_color: str = None): + def __init__(self, name: str, value: str, font_color: str = None, underline_color: str = None, preserve_tag_style: Union[str, bool] = None): """ Args: name (str): The name for this element. value (str): The value of the autoLink. font_color (str, optional): The font color of autolink. underline_color (str, optional): The underline color of autolink. + preserve_tag_style (str or bool, optional): take the styling of hyperlink text defined in the template (blue and underlined by default). """ super().__init__(name, value) self.value: str = value self.font_color: str = font_color self.underline_color: str = underline_color + self.preserve_tag_style: Union[str, bool] = preserve_tag_style @property def available_tags(self) -> FrozenSet[str]: @@ -509,22 +511,28 @@ def as_dict(self) -> Dict: result[self.name + "_font_color"] = self.font_color if self.underline_color is not None: result[self.name + "_underline_color"] = self.underline_color + if self.preserve_tag_style is not None: + result[self.name + "_preserve_tag_style"] = self.preserve_tag_style return result class Hyperlink(Element): - def __init__(self, name: str, url: str, text: str = None, font_color: str = None, underline_color: str = None): + def __init__(self, name: str, url: str, text: str = None, font_color: str = None, underline_color: str = None, preserve_tag_style: Union[str, bool] = None): """ Args: name (str): The name for this element. url (str): The URL for the hyperlink. text (str, optional): The text for the hyperlink. Defaults to None. + font_color (str, optional): The font color of text for hyperlink. + underline_color (str, optional): The underline color of text for hyperlink. + preserve_tag_style (str or bool, optional): take the styling of hyperlink text defined in the template (blue and underlined by default). """ super().__init__(name) self.url: str = url self.text: str = text self.font_color: str = font_color self.underline_color: str = underline_color + self.preserve_tag_style: Union[str, bool] = preserve_tag_style @property def available_tags(self) -> FrozenSet[str]: @@ -540,6 +548,8 @@ def as_dict(self) -> Dict: result[self.text + "_font_color"] = self.font_color if (self.text is not None) and (self.underline_color is not None): result[self.text + "_underline_color"] = self.underline_color + if (self.preserve_tag_style is not None): + result[self.name + "_preserve_tag_style"] = self.preserve_tag_style return result diff --git a/tests/test_elements.py b/tests/test_elements.py index 10dea54..c271dac 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -150,12 +150,14 @@ def test_autoLink(): name='autoLink', value='sample text with hyperlinks', font_color='red', - underline_color='#ffffffff' + underline_color='#ffffffff', + preserve_tag_style = True ) autoLink_expected = { 'autoLink': 'sample text with hyperlinks', 'autoLink_font_color': 'red', - 'autoLink_underline_color': '#ffffffff' + 'autoLink_underline_color': '#ffffffff', + 'autoLink_preserve_tag_style': True } assert autoLink.as_dict == autoLink_expected @@ -167,13 +169,15 @@ def test_hyperlink(): url='url', text='hyperlink_text', font_color='red', - underline_color='#ffffffff' + underline_color='#ffffffff', + preserve_tag_style = 'yes' ) hyperlink_expected = { 'hyperlink': 'url', 'hyperlink_text': 'hyperlink_text', 'hyperlink_text_font_color': 'red', - 'hyperlink_text_underline_color': '#ffffffff' + 'hyperlink_text_underline_color': '#ffffffff', + 'hyperlink_preserve_tag_style': 'yes' } assert hyperlink.as_dict == hyperlink_expected From cd797d51866be7490401fd25027ef35505ab7730 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Tue, 31 Dec 2024 12:30:25 +0545 Subject: [PATCH 12/42] add new option for xlsx cell style property --- cloudofficeprint/elements/elements.py | 28 +++++++++++++++++++++++++++ tests/test_elements.py | 14 ++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index b6964f3..e39dbc5 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -210,6 +210,11 @@ def __init__( text_h_alignment: str = None, text_v_alignment: str = None, text_rotation: Union[int, str] = None, + wrap_text: bool = False, + width: Union[int, str] = None, + height: Union[int, str] = None, + max_characters : Union[int, str] = None, + height_scaling : Union[int, str] = None, ): """ Args: @@ -239,6 +244,11 @@ def __init__( text_h_alignment (str, optional): [top|bottom|center|justify]. Defaults to None. text_v_alignment (str, optional): [top|bottom|center|justify]. Defaults to None. text_rotation (Union[int, str], optional): rotation of text value from 0-90 degrees. Defaults to None. + wrap_text (bool, optional): set to true for wrap text. The default is false. + width (Union[int, str], optional): provide a custom width to the cell. Supported units: inch, cm, px, pt, em, Excel Units(eu) + height (Union[int, str], optional): provide custom height to the cell. Supported units: inch, cm, px, pt, em, Excel Units(eu) + max_characters (Union[int, str], optional): provide width for the cell. + height_scaling (Union[int, str], optional): adjusts cell height for consistent rendering. """ super().__init__() self.cell_locked: bool = cell_locked @@ -267,6 +277,14 @@ def __init__( self.text_h_alignment: str = text_h_alignment self.text_v_alignment: str = text_v_alignment self.text_rotation: Union[int, str] = text_rotation + self.wrap_text: bool = wrap_text + self.width: Union[int, str] = width + self.height: Union[int, str] = height + self.max_characters: Union[int, str] = max_characters + self.height_scaling: Union[int, str] = height_scaling + + + @property def _dict_suffixes(self): @@ -324,6 +342,16 @@ def _dict_suffixes(self): result["_text_v_alignment"] = self.text_v_alignment if self.text_rotation is not None: result["_text_rotation"] = self.text_rotation + if self.wrap_text is not None: + result["_wrap_text"] = self.wrap_text + if self.width is not None: + result["_width"] = self.width + if self.height is not None: + result["_height"] = self.height + if self.max_characters is not None: + result["_max_characters"] = self.max_characters + if self.height_scaling is not None: + result["_height_scaling"] = self.height_scaling return result diff --git a/tests/test_elements.py b/tests/test_elements.py index c271dac..2c11542 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -106,7 +106,12 @@ def test_cell_style_property_xlsx(): border_diagonal_color='#ff0000', text_h_alignment='center', text_v_alignment='justify', - text_rotation=45 + text_rotation=45, + wrap_text=True, + width='auto', + height=40, + max_characters=60, + height_scaling=0.75 ) style_property = cop.elements.CellStyleProperty( name='name', @@ -140,7 +145,12 @@ def test_cell_style_property_xlsx(): 'name_border_diagonal_color': '#ff0000', 'name_text_h_alignment': 'center', 'name_text_v_alignment': 'justify', - 'name_text_rotation': 45 + 'name_text_rotation': 45, + 'name_wrap_text': True, + 'name_width': 'auto', + 'name_height': 40, + 'name_max_characters': 60, + 'name_height_scaling': 0.75 } assert style_property.as_dict == style_property_expected From 0a82907e50bfbc05491bf4654dd5d2e293493d73 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Tue, 31 Dec 2024 15:24:04 +0545 Subject: [PATCH 13/42] add new option for html property --- cloudofficeprint/elements/elements.py | 42 +++++++++++++++++++++++++-- tests/test_elements.py | 23 +++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index e39dbc5..28a6ee7 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -467,18 +467,56 @@ def as_dict(self) -> Dict: class Html(Property): - def __init__(self, name: str, value: str): + def __init__( + self, name: str, + value: str, + custom_table_style: str, + unordered_list_style: Union[str, int], + ordered_list_style: Union[str, int], + use_tag_style: bool, + ignore_cell_margin: bool, + ignore_empty_p: bool + ): """ Args: name (str): The name for this property. value (str): The value for this property. + custom_table_style (str): Specify custom table style + unordered_list_style (str): create and customize ordered list + ordered_list_style (str): create and customize unordered + use_tag_style (bool): use the styling from the template instead of default Word styling + ignore_cell_margin (bool): ignore empty paragraphs within HTML content + ignore_empty_p (bool): ignore the cell margins in an HTML table cell when the text content is large """ super().__init__(name, value) - + self.custom_table_style: str = custom_table_style + self.unordered_list_style: Union[str, int] = unordered_list_style + self.ordered_list_style: Union[str, int] = ordered_list_style + self.use_tag_style: bool = use_tag_style + self.ignore_cell_margin: bool = ignore_cell_margin + self.ignore_empty_p: bool = ignore_empty_p + @property def available_tags(self) -> FrozenSet[str]: return frozenset({"{_" + self.name + "}"}) + + @property + def as_dict(self) -> Dict: + result = {self.name: self.value} + if self.custom_table_style is not None: + result[self.name + "_custom_table_style"] = self.custom_table_style + if self.unordered_list_style is not None: + result[self.name + "_unordered_list_style"] = self.unordered_list_style + if self.ordered_list_style is not None: + result[self.name + "_ordered_list_style"] = self.ordered_list_style + if self.use_tag_style is not None: + result[self.name + "_use_tag_style"] = self.use_tag_style + if self.ignore_cell_margin is not None: + result[self.name + "_ignore_cell_margin"] = self.ignore_cell_margin + if self.ignore_empty_p is not None: + result[self.name + "_ignore_empty_p"] = self.ignore_empty_p + return result class RightToLeft(Property): def __init__(self, name: str, value: str): diff --git a/tests/test_elements.py b/tests/test_elements.py index 2c11542..7c53c9e 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -13,6 +13,29 @@ def test_property(): 'name': 'value' } assert prop.as_dict == prop_expected + +def test_html(): + """Test for Html property.""" + html_prop = cop.elements.Html( + name='name', + value='

An ordered HTML list

  1. Coffee
  2. Tea
  3. Milk
', + custom_table_style='CustomTableAOP', + ordered_list_style='1', + unordered_list_style='2', + use_tag_style=False, + ignore_cell_margin=True, + ignore_empty_p=False, + ) + html_prop_expected = { + 'name': '

An ordered HTML list

  1. Coffee
  2. Tea
  3. Milk
', + 'name_custom_table_style': 'CustomTableAOP', + 'name_ordered_list_style': '1', + 'name_unordered_list_style': '2', + 'name_use_tag_style': False, + 'name_ignore_cell_margin': True, + 'name_ignore_empty_p': False + } + assert html_prop.as_dict == html_prop_expected def test_cell_style_property_docx(): From 63915e6b39049eb5e23d5801c5b975f31dc60486 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Thu, 2 Jan 2025 10:06:09 +0545 Subject: [PATCH 14/42] add option to update table of content of word document --- cloudofficeprint/config/output.py | 5 +++++ cloudofficeprint/elements/elements.py | 4 ++-- tests/test_config.py | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cloudofficeprint/config/output.py b/cloudofficeprint/config/output.py index 7ce68a2..3d6bc82 100644 --- a/cloudofficeprint/config/output.py +++ b/cloudofficeprint/config/output.py @@ -23,6 +23,7 @@ def __init__(self, output_polling: bool = None, secret_key: str = None, page_number_start_at: str = None, + update_toc: bool = None, request_option: requestOptions = None): """If the parameters are not provided default value will be used. @@ -39,6 +40,7 @@ def __init__(self, secret_key (str, optional): A secret key can be specified to encrypt the file stored on the server (ussed with output polling). Defaults to None. request_option (requestOptions, optional): AOP makes a call to the given option with response/output of the current request. Defaults to None. page_number_start_at (str, optional): Provide start of the page number. Defaults to None. + update_toc (bool, optional): Update table of contents of Word document. """ self.filetype: str = filetype self.converter: str = converter @@ -51,6 +53,7 @@ def __init__(self, self.output_polling = output_polling self.secret_key = secret_key self.page_number_start_at = page_number_start_at + self.update_toc = update_toc self.request_option = request_option @property @@ -94,6 +97,8 @@ def as_dict(self) -> Dict: result['secret_key'] = self.secret_key if self.page_number_start_at is not None: result['output_page_number_start_at'] = self.page_number_start_at + if self.update_toc is not None: + result['update_toc'] = self.update_toc if self.request_option is not None: result['request_option'] = self.request_option.as_dict return result diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index 28a6ee7..178b9a8 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -483,8 +483,8 @@ def __init__( value (str): The value for this property. custom_table_style (str): Specify custom table style unordered_list_style (str): create and customize ordered list - ordered_list_style (str): create and customize unordered - use_tag_style (bool): use the styling from the template instead of default Word styling + ordered_list_style (str): create and customize unordered list + use_tag_style (bool): use the styling from the template instead of default Word styling ignore_cell_margin (bool): ignore empty paragraphs within HTML content ignore_empty_p (bool): ignore the cell margins in an HTML table cell when the text content is large """ diff --git a/tests/test_config.py b/tests/test_config.py index 08067de..e5ac3bf 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -37,7 +37,7 @@ def test_pdf_options(): pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32, 45) pdf_opts.set_page_margin_at(6, "top") pdf_opts.page_orientation = "portrait" - conf = cop.config.OutputConfig(filetype="pdf", converter="openoffice", page_number_start_at="5", pdf_options=pdf_opts) + conf = cop.config.OutputConfig(filetype="pdf", converter="openoffice", page_number_start_at="5", update_toc=True, pdf_options=pdf_opts) conf_expected = { "output_even_page": True, "output_merge_making_even": False, @@ -55,6 +55,7 @@ def test_pdf_options(): "output_encoding": "raw", "output_converter": "openoffice", "output_page_number_start_at": "5", + "update_toc": True, "output_page_width": 500, "output_page_height": 500, "lock_form": True, From eb8d7f8150009a6512e04609386d8ed10c6bf4d2 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Thu, 2 Jan 2025 11:16:55 +0545 Subject: [PATCH 15/42] add new option to insert barcode in pdf config --- cloudofficeprint/config/output.py | 2 +- cloudofficeprint/config/pdf.py | 5 +++++ tests/test_config.py | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cloudofficeprint/config/output.py b/cloudofficeprint/config/output.py index 3d6bc82..e72a26b 100644 --- a/cloudofficeprint/config/output.py +++ b/cloudofficeprint/config/output.py @@ -37,7 +37,7 @@ def __init__(self, append_per_page (bool, optional): Ability to append file after each page of output. Defaults to None. prepend_per_page (bool, optional): Ability to prepend file after each page of output. Defaults to None. output_polling (bool, optional): A unique link for each request is sent back, which can be used later to download the output file. Defaults to None. - secret_key (str, optional): A secret key can be specified to encrypt the file stored on the server (ussed with output polling). Defaults to None. + secret_key (str, optional): A secret key can be specified to encrypt the file stored on the server (used with output polling). Defaults to None. request_option (requestOptions, optional): AOP makes a call to the given option with response/output of the current request. Defaults to None. page_number_start_at (str, optional): Provide start of the page number. Defaults to None. update_toc (bool, optional): Update table of contents of Word document. diff --git a/cloudofficeprint/config/pdf.py b/cloudofficeprint/config/pdf.py index 083b7d0..aa01e54 100644 --- a/cloudofficeprint/config/pdf.py +++ b/cloudofficeprint/config/pdf.py @@ -41,6 +41,7 @@ def __init__( convert_to_pdfa: str = None, attachment_name: str = None, convert_attachment_to_json: bool = None, + insert_barcode: bool = None, ): """ @@ -73,6 +74,7 @@ def __init__( convert_to_pdfa (str, optional): For generating PDF/A format. While converting using openoffice converter, specifying it will create PDF/A format, values can be either 1b or 2b which are the variants of PDF/A specification. attachment_name (str, optional): To retrieve specific attachment. output_type must be get_attachments. convert_attachment_to_json (bool, optional): To retrieve data of the XML attachment as a JSON. output_type must be get_attachments. + insert_barcode (bool, optional): To insert barcode in pdf """ self.even_page: bool = even_page self.merge_making_even: bool = merge_making_even @@ -102,6 +104,7 @@ def __init__( self.convert_to_pdfa: str = convert_to_pdfa self.attachment_name: str = attachment_name self.convert_attachment_to_json: bool = convert_attachment_to_json + self.insert_barcode: bool = insert_barcode def __str__(self) -> str: @@ -191,6 +194,8 @@ def as_dict(self) -> Dict: result["output_attachment_name"] = self.attachment_name if self.convert_attachment_to_json is not None: result["output_convert_attachment_to_json"] = self.convert_attachment_to_json + if self.insert_barcode is not None: + result["output_insert_barcode"] = self.insert_barcode return result diff --git a/tests/test_config.py b/tests/test_config.py index e5ac3bf..91d4c6f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -33,6 +33,7 @@ def test_pdf_options(): sign_certificate_txt="text in english", convert_to_pdfa = "1b", convert_attachment_to_json= True, + insert_barcode= True, ) pdf_opts.set_watermark("new_watermark", "grey", "Arial", 51, 32, 45) pdf_opts.set_page_margin_at(6, "top") @@ -74,6 +75,7 @@ def test_pdf_options(): "output_sign_certificate_txt": "text in english", "output_convert_to_pdfa": "1b", "output_convert_attachment_to_json": True, + "output_insert_barcode": True, "identify_form_fields": True, "output_split": True, } From efd715ca83095e7defd40a86cc1f9e8c966c4647 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Thu, 2 Jan 2025 12:52:35 +0545 Subject: [PATCH 16/42] add new options firstSliceAngle and holeSize in chart options for dopughnut chart --- cloudofficeprint/elements/charts.py | 13 ++++++++++++- tests/test_charts.py | 9 ++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cloudofficeprint/elements/charts.py b/cloudofficeprint/elements/charts.py index 9b04e12..180590d 100644 --- a/cloudofficeprint/elements/charts.py +++ b/cloudofficeprint/elements/charts.py @@ -193,7 +193,10 @@ def __init__(self, background_opacity: int = None, title: str = None, title_style: ChartTextStyle = None, - grid: bool = None): + grid: bool = None, + holeSize: int = None, + firstSliceAngle: int = None + ): """ Args: x_axis (ChartAxisOptions, optional): The options for the x-axis. Defaults to None. @@ -210,6 +213,8 @@ def __init__(self, title (str, optional): The title of the chart. Defaults to None. title_style (ChartTextStyle, optional): The styling for the title of the chart. Defaults to None. grid (bool, optional): Whether or not the chart should have a grid. Defaults to None. + holeSize (int, optional): hole size for doughnut chart (0 - 100). + firstSLiceAngle (int , optional): angle of first slice for dough chart (0 - 320). Must be specified for holeSize option to work """ self._legend_options: dict = None self._data_labels_options: dict = None @@ -226,6 +231,8 @@ def __init__(self, self.title: str = title self.title_style: ChartTextStyle = title_style self.grid: bool = grid + self.holeSize: int = holeSize + self.firstSliceAngle: int = firstSliceAngle def set_legend(self, position: str = 'r', style: ChartTextStyle = None): """Setter for the legend of the chart. @@ -325,6 +332,10 @@ def as_dict(self) -> Dict: result["titleStyle"] = self.title_style.as_dict if self.grid is not None: result["grid"] = self.grid + if self.firstSliceAngle is not None: + result["firstSliceAngle"] = self.firstSliceAngle + if self.holeSize is not None: + result["holeSize"] = self.holeSize if self._legend_options is not None: result["legend"] = self._legend_options if self._data_labels_options is not None: diff --git a/tests/test_charts.py b/tests/test_charts.py index 8e58d25..9df78e3 100644 --- a/tests/test_charts.py +++ b/tests/test_charts.py @@ -1,5 +1,8 @@ +import sys +sys.path.insert(0, "D:/UC/cloudofficeprint-python") import cloudofficeprint as cop + """This file contains tests for all the possible charts""" @@ -78,7 +81,9 @@ def test_chart_options(): color='red', font='Arial' ), - grid=True + grid=True, + firstSliceAngle=45, + holeSize=50 ) options.set_legend( position='l', @@ -197,6 +202,8 @@ def test_chart_options(): 'font': 'Arial', }, 'grid': True, + 'firstSliceAngle': 45, + 'holeSize': 50, 'legend': { 'showLegend': True, 'position': 'l', From 460029e254963c5c97fe2df54777ccba4344c6da Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Thu, 2 Jan 2025 13:21:06 +0545 Subject: [PATCH 17/42] add new chart option enableAreaTransparency for Area Chart --- cloudofficeprint/elements/charts.py | 9 +++++++-- tests/test_charts.py | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cloudofficeprint/elements/charts.py b/cloudofficeprint/elements/charts.py index 180590d..eeb5efa 100644 --- a/cloudofficeprint/elements/charts.py +++ b/cloudofficeprint/elements/charts.py @@ -195,7 +195,8 @@ def __init__(self, title_style: ChartTextStyle = None, grid: bool = None, holeSize: int = None, - firstSliceAngle: int = None + firstSliceAngle: int = None, + enableAreaTransparency: bool = None ): """ Args: @@ -214,7 +215,8 @@ def __init__(self, title_style (ChartTextStyle, optional): The styling for the title of the chart. Defaults to None. grid (bool, optional): Whether or not the chart should have a grid. Defaults to None. holeSize (int, optional): hole size for doughnut chart (0 - 100). - firstSLiceAngle (int , optional): angle of first slice for dough chart (0 - 320). Must be specified for holeSize option to work + firstSLiceAngle (int , optional): angle of first slice for dough chart (0 - 320). Must be specified for holeSize option to work + enableAreaTransparency (bool, option): whether to make area chart transparent. """ self._legend_options: dict = None self._data_labels_options: dict = None @@ -233,6 +235,7 @@ def __init__(self, self.grid: bool = grid self.holeSize: int = holeSize self.firstSliceAngle: int = firstSliceAngle + self.enableAreaTransparency: bool = enableAreaTransparency def set_legend(self, position: str = 'r', style: ChartTextStyle = None): """Setter for the legend of the chart. @@ -336,6 +339,8 @@ def as_dict(self) -> Dict: result["firstSliceAngle"] = self.firstSliceAngle if self.holeSize is not None: result["holeSize"] = self.holeSize + if self.enableAreaTransparency is not None: + result["enableAreaTransparency"] = self.enableAreaTransparency if self._legend_options is not None: result["legend"] = self._legend_options if self._data_labels_options is not None: diff --git a/tests/test_charts.py b/tests/test_charts.py index 9df78e3..4a6a367 100644 --- a/tests/test_charts.py +++ b/tests/test_charts.py @@ -83,7 +83,8 @@ def test_chart_options(): ), grid=True, firstSliceAngle=45, - holeSize=50 + holeSize=50, + enableAreaTransparency =True ) options.set_legend( position='l', @@ -204,6 +205,7 @@ def test_chart_options(): 'grid': True, 'firstSliceAngle': 45, 'holeSize': 50, + 'enableAreaTransparency': True, 'legend': { 'showLegend': True, 'position': 'l', From 6277a6350e4bb10563a836c4ff466486fb9c9b1c Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Thu, 2 Jan 2025 15:50:24 +0545 Subject: [PATCH 18/42] add new chart types AreaStacked and LineStacked chart --- cloudofficeprint/elements/charts.py | 41 ++++++++++++++++++++++++++++- tests/test_charts.py | 4 +-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/cloudofficeprint/elements/charts.py b/cloudofficeprint/elements/charts.py index eeb5efa..d063ed9 100644 --- a/cloudofficeprint/elements/charts.py +++ b/cloudofficeprint/elements/charts.py @@ -671,7 +671,7 @@ def from_dataframe(cls, data: 'pandas.DataFrame', name: str = None) -> 'StockSer # better to have a series for every possible chart for future-proofing, in case their options diverge later BarSeries = BarStackedSeries = BarStackedPercentSeries = ColumnSeries = ColumnStackedSeries = ColumnStackedPercentSeries = ScatterSeries = XYSeries -RadarSeries = LineSeries +RadarSeries = LineStackedSeries = LineSeries class Chart(Element, ABC): @@ -731,6 +731,26 @@ def as_dict(self) -> Dict: "lines": [line.as_dict for line in self.lines], "type": "line" }) + +class LineStackedChart(Chart): + """Class for a line chart""" + + def __init__(self, name: str, lines: Tuple[Union[LineStackedSeries, XYSeries]], options: ChartOptions = None): + """ + Args: + name (str): The name of the chart. + lines (Tuple[Union[LineStackedSeries, XYSeries]]): Iterable of line series. + options (Union[ChartOptions, dict], optional): The options for the chart. Defaults to None. + """ + super().__init__(name, options) + self.lines: Tuple[Union[LineStackedSeries, XYSeries]] = lines + + @property + def as_dict(self) -> Dict: + return self._get_dict({ + "lines": [line.as_dict for line in self.lines], + "type": "lineStacked" + }) class BarChart(Chart): @@ -963,6 +983,25 @@ def as_dict(self) -> Dict: "areas": [area.as_dict for area in self.areas], "type": "area" }) +class AreaStackedChart(Chart): + """Class for an area stacked chart""" + + def __init__(self, name: str, areas: Tuple[Union[AreaSeries, XYSeries]], options: ChartOptions = None): + """ + Args: + name (str): The name of the chart. + areas (Tuple[Union[AreaSeries, XYSeries]]): Iterable of area series. + options (Union[ChartOptions, dict], optional): The options for the chart. Defaults to None. + """ + super().__init__(name, options) + self.areas: Tuple[Union[AreaSeries, XYSeries]] = areas + + @property + def as_dict(self) -> Dict: + return self._get_dict({ + "areas": [area.as_dict for area in self.areas], + "type": "areaStacked" + }) class ScatterChart(Chart): diff --git a/tests/test_charts.py b/tests/test_charts.py index 4a6a367..1a639b1 100644 --- a/tests/test_charts.py +++ b/tests/test_charts.py @@ -231,7 +231,7 @@ def test_chart_options(): def test_chart_line(): - """Test for LineChart. Also serves as a test for RadarChart (RadarSeries is equivalent to LineSeries)""" + """Test for LineChart. Also serves as a test for RadarChart and LineStackedChart (RadarSeries is equivalent to LineSeries)""" line1 = cop.elements.LineSeries( x=('a', 'b', 'c'), y=(1, 2, 3), @@ -450,7 +450,7 @@ def test_chart_pie(): def test_chart_area(): - """Test for AreaChart""" + """Test for AreaChart and AreaStacked""" area1 = cop.elements.AreaSeries( x=('a', 'b', 'c'), y=(1, 2, 3), From 1ef961d61f883ee7f20375e5afd2dcf442d1b1a9 Mon Sep 17 00:00:00 2001 From: Anush Mali Date: Mon, 6 Jan 2025 10:21:48 +0545 Subject: [PATCH 19/42] update documentation and examples --- BeginerGuide/UsingCharts/usingChart.py | 2 + BeginerGuide/UsingElements/output/output.docx | Bin 61208 -> 61179 bytes BeginerGuide/UsingElements/using_elements.py | 9 +- cloudofficeprint/config/pdf.py | 2 +- cloudofficeprint/elements/charts.py | 6 +- cloudofficeprint/elements/elements.py | 57 +- cloudofficeprint/printjob.py | 2 +- docs/cloudofficeprint/config/cloud.html | 566 +-- docs/cloudofficeprint/config/csv.html | 173 +- docs/cloudofficeprint/config/index.html | 57 +- docs/cloudofficeprint/config/output.html | 254 +- docs/cloudofficeprint/config/pdf.html | 507 +-- .../config/request_option.html | 65 +- docs/cloudofficeprint/config/server.html | 783 +--- docs/cloudofficeprint/elements/charts.html | 2835 +++++--------- docs/cloudofficeprint/elements/codes.html | 1314 ++----- docs/cloudofficeprint/elements/elements.html | 3268 ++++++----------- docs/cloudofficeprint/elements/images.html | 589 +-- docs/cloudofficeprint/elements/index.html | 55 +- docs/cloudofficeprint/elements/loops.html | 363 +- docs/cloudofficeprint/elements/pdf.html | 452 +-- .../elements/rest_source.html | 255 +- docs/cloudofficeprint/exceptions.html | 177 +- docs/cloudofficeprint/index.html | 847 ++--- .../own_utils/file_utils.html | 143 +- docs/cloudofficeprint/own_utils/index.html | 50 +- .../own_utils/type_utils.html | 146 +- docs/cloudofficeprint/printjob.html | 424 +-- docs/cloudofficeprint/resource.html | 840 ++--- docs/cloudofficeprint/response.html | 235 +- docs/cloudofficeprint/template.html | 545 +-- 31 files changed, 4710 insertions(+), 10311 deletions(-) diff --git a/BeginerGuide/UsingCharts/usingChart.py b/BeginerGuide/UsingCharts/usingChart.py index e8ddfdb..8385941 100644 --- a/BeginerGuide/UsingCharts/usingChart.py +++ b/BeginerGuide/UsingCharts/usingChart.py @@ -26,6 +26,8 @@ '2px', 'sysDash' ) + +# To make line stacked chart you can simply use cop.elements.LineStackedChart line_chart = cop.elements.LineChart( 'line_chart_name', (line1, line2) diff --git a/BeginerGuide/UsingElements/output/output.docx b/BeginerGuide/UsingElements/output/output.docx index e051adce0ee2d3d5be2e7791c938653b414a20fd..076f625e049b22107c0ca0503cb5a14c6b640f7c 100644 GIT binary patch delta 2422 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 diff --git a/BeginerGuide/UsingElements/using_elements.py b/BeginerGuide/UsingElements/using_elements.py index 1f11d08..4f5f1f1 100644 --- a/BeginerGuide/UsingElements/using_elements.py +++ b/BeginerGuide/UsingElements/using_elements.py @@ -1,7 +1,7 @@ # Install cloudofficeprint using pip install cloudofficeprint #Import the cloudofficeprint libary. import sys -sys.path.insert(0, "PATH_TO_COP_DIR") +sys.path.insert(0, "D:/UC/cloudofficeprint-python") import cloudofficeprint as cop # Main object that holds the data @@ -48,11 +48,7 @@ docx_column1_cell_style = cop.elements.CellStyleDocx( cell_background_color='red', border_color='0d72c7', - border_top='double', - border_top_size=20, - border_left='dotDash', - border_bottom_color='yellow', - border_left_size='38' + border_top='double' ) docx_column1_table_style_property = cop.elements.CellStyleProperty( name='column1', @@ -62,7 +58,6 @@ collection.add(docx_column1_table_style_property) docx_column2_cell_style = cop.elements.CellStyleDocx( - border_right_space=15, border_diagonal_down='single', border_diagonal_down_size=10, border_diagonal_up='single', diff --git a/cloudofficeprint/config/pdf.py b/cloudofficeprint/config/pdf.py index aa01e54..7647f1c 100644 --- a/cloudofficeprint/config/pdf.py +++ b/cloudofficeprint/config/pdf.py @@ -70,7 +70,7 @@ def __init__( identify_form_fields (bool, optional): Identify the form fields in a PDF-form by filling the name of each field into the respective field. Defaults to None. sign_certificate (str, optional): Signing certificate for the output PDF (pkcs #12 .p12/.pfx) as a base64 string, URL, FTP location or a server path. The function read_file_as_base64() from file_utils.py can be used to read local .p12 or .pfx file as base64. Defaults to None. sign_certificate_password (str, optional): If you are signing with a password protected certificate, you can specify the password as a plain string. Defaults to None. - sign_certificate_txt (str, optional) Add custom text in any language to the signature field + sign_certificate_txt (str, optional): Add custom text in any language to the signature field convert_to_pdfa (str, optional): For generating PDF/A format. While converting using openoffice converter, specifying it will create PDF/A format, values can be either 1b or 2b which are the variants of PDF/A specification. attachment_name (str, optional): To retrieve specific attachment. output_type must be get_attachments. convert_attachment_to_json (bool, optional): To retrieve data of the XML attachment as a JSON. output_type must be get_attachments. diff --git a/cloudofficeprint/elements/charts.py b/cloudofficeprint/elements/charts.py index d063ed9..0855b92 100644 --- a/cloudofficeprint/elements/charts.py +++ b/cloudofficeprint/elements/charts.py @@ -214,9 +214,9 @@ def __init__(self, title (str, optional): The title of the chart. Defaults to None. title_style (ChartTextStyle, optional): The styling for the title of the chart. Defaults to None. grid (bool, optional): Whether or not the chart should have a grid. Defaults to None. - holeSize (int, optional): hole size for doughnut chart (0 - 100). - firstSLiceAngle (int , optional): angle of first slice for dough chart (0 - 320). Must be specified for holeSize option to work - enableAreaTransparency (bool, option): whether to make area chart transparent. + holeSize (int, optional): Hole size for doughnut chart (0-100). + firstSLiceAngle (int , optional): Angle of first slice for dough chart (0-360). Must be specified for holeSize option to work + enableAreaTransparency (bool, option): Whether to make area chart transparent. """ self._legend_options: dict = None self._data_labels_options: dict = None diff --git a/cloudofficeprint/elements/elements.py b/cloudofficeprint/elements/elements.py index 178b9a8..941a6ca 100644 --- a/cloudofficeprint/elements/elements.py +++ b/cloudofficeprint/elements/elements.py @@ -77,6 +77,35 @@ def __init__( Args: cell_background_color (str, optional): The background color of the cell. Defaults to None. width (Union[int, str], optional): The width of the cell. Defaults to None. + preserve_total_width_of_table (str, optional): Keeps table width constant by redistributing removed column's width to others. + border (str, optional): Applies the specified border style to all table edges (top, bottom, left, right). + border_top (str, optional): Applies the specified border style to the top edge of the table. + border_bottom (str, optional): Applies the specified border style to the bottom edge of the table. + border_left (str, optional): Applies the specified border style to the left edge of the table. + border_right (str, optional): Applies the specified border style to the right edge of the table. + border_diagonal_down (str, optional): Applies the specified border style to the diagonal line going from the top-left to the bottom-right corner. + border_diagonal_up (str, optional): Applies the specified border style to the diagonal line going from the bottom-left to the top-right corner. + border_color (str, optional): Sets the color of the borders (top, bottom, left, right). + border_top_color (str, optional): Sets the color of the top border. + border_bottom_color (str, optional): Sets the color of the bottom border. + border_left_color (str, optional): Sets the color of the left border. + border_right_color (str, optional): Sets the color of the right border. + border_diagonal_up_color (str, optional): Sets the color of the diagonal up border. + border_diagonal_down_color (str, optional): Sets the color of the diagonal down border. + border_size (Union[int, str], optional): Sets the width of the borders (top, bottom, left, right) in points. + border_top_size (Union[int, str], optional): Sets the width of the top border in points. + border_bottom_size (Union[int, str], optional): Sets the width of the bottom border in points. + border_left_size (Union[int, str], optional): Sets the width of the left border in points. + border_right_size (Union[int, str], optional): Sets the width of the right border in points. + border_diagonal_up_size (Union[int, str], optional): Sets the width of the diagonal up border in points. + border_diagonal_down_size (Union[int, str], optional): Sets the width of the diagonal down border in points. + border_space (Union[int, str], optional): Sets the spacing between the content and borders (top, bottom, left, right) in points. + border_top_space (Union[int, str], optional): Sets the spacing between the content and the top border in points. + border_bottom_space (Union[int, str], optional): Sets the spacing between the content and the bottom border in points. + border_left_space (Union[int, str], optional): Sets the spacing between the content and the left border in points. + border_right_space (Union[int, str], optional): Sets the spacing between the content and the right border in points. + border_diagonal_up_space (Union[int, str], optional): Sets the spacing between the content and the diagonal up border in points. + border_diagonal_down_space (Union[int, str], optional): Sets the spacing between the content and the diagonal down border in points. """ super().__init__() self.cell_background_color: str = cell_background_color @@ -243,12 +272,12 @@ def __init__( border_diagonal_color (str, optional): hex color e.g: #000000. Defaults to None. text_h_alignment (str, optional): [top|bottom|center|justify]. Defaults to None. text_v_alignment (str, optional): [top|bottom|center|justify]. Defaults to None. - text_rotation (Union[int, str], optional): rotation of text value from 0-90 degrees. Defaults to None. - wrap_text (bool, optional): set to true for wrap text. The default is false. - width (Union[int, str], optional): provide a custom width to the cell. Supported units: inch, cm, px, pt, em, Excel Units(eu) - height (Union[int, str], optional): provide custom height to the cell. Supported units: inch, cm, px, pt, em, Excel Units(eu) - max_characters (Union[int, str], optional): provide width for the cell. - height_scaling (Union[int, str], optional): adjusts cell height for consistent rendering. + text_rotation (Union[int, str], optional): Rotation of text value from 0-90 degrees. Defaults to None. + wrap_text (bool, optional): Set to true for wrap text. The default is false. + width (Union[int, str], optional): Provide a custom width to the cell. Supported units: inch, cm, px, pt, em, Excel Units(eu) + height (Union[int, str], optional): Provide custom height to the cell. Supported units: inch, cm, px, pt, em, Excel Units(eu) + max_characters (Union[int, str], optional): Provide width for the cell. + height_scaling (Union[int, str], optional): Adjusts cell height for consistent rendering. """ super().__init__() self.cell_locked: bool = cell_locked @@ -482,11 +511,11 @@ def __init__( name (str): The name for this property. value (str): The value for this property. custom_table_style (str): Specify custom table style - unordered_list_style (str): create and customize ordered list - ordered_list_style (str): create and customize unordered list - use_tag_style (bool): use the styling from the template instead of default Word styling - ignore_cell_margin (bool): ignore empty paragraphs within HTML content - ignore_empty_p (bool): ignore the cell margins in an HTML table cell when the text content is large + unordered_list_style (str): Create and customize ordered list + ordered_list_style (str): Create and customize unordered list + use_tag_style (bool): Use the styling from the template instead of default Word styling + ignore_cell_margin (bool): Ignore empty paragraphs within HTML content + ignore_empty_p (bool): Ignore the cell margins in an HTML table cell when the text content is large """ super().__init__(name, value) self.custom_table_style: str = custom_table_style @@ -557,7 +586,7 @@ def __init__(self, name: str, value: str, font_color: str = None, underline_colo value (str): The value of the autoLink. font_color (str, optional): The font color of autolink. underline_color (str, optional): The underline color of autolink. - preserve_tag_style (str or bool, optional): take the styling of hyperlink text defined in the template (blue and underlined by default). + preserve_tag_style (str or bool, optional): Take the styling of hyperlink text defined in the template (blue and underlined by default). """ super().__init__(name, value) self.value: str = value @@ -591,7 +620,7 @@ def __init__(self, name: str, url: str, text: str = None, font_color: str = None text (str, optional): The text for the hyperlink. Defaults to None. font_color (str, optional): The font color of text for hyperlink. underline_color (str, optional): The underline color of text for hyperlink. - preserve_tag_style (str or bool, optional): take the styling of hyperlink text defined in the template (blue and underlined by default). + preserve_tag_style (str or bool, optional): Take the styling of hyperlink text defined in the template (blue and underlined by default). """ super().__init__(name) self.url: str = url @@ -1164,7 +1193,7 @@ def available_tags(self) -> FrozenSet[str]: class HideSlide(Property): """Allows hiding a slide. - LIMITATION using _hide for slide hide is that it can only be used for hiding slides during generation(!slideGeneration) + LIMITATION: using _hide for slide hide is that it can only be used for hiding slides during generation(!slideGeneration) """ def __init__(self, name: str, value: bool): """ diff --git a/cloudofficeprint/printjob.py b/cloudofficeprint/printjob.py index cf62d88..7e81172 100644 --- a/cloudofficeprint/printjob.py +++ b/cloudofficeprint/printjob.py @@ -52,7 +52,7 @@ def __init__( subtemplates (Dict[str, Resource], optional): Subtemplates for this print job, accessible (in docx) through `{?include subtemplate_dict_key}`. Defaults to {}. prepend_files (List[Resource], optional): Files to prepend to the output file. Defaults to []. append_files (List[Resource], optional): Files to append to the output file. Defaults to []. - attachments (List[Resource], optional): Files to attach to the pdf file. Defaults to []. The file must be PDF. + attachments (List[Resource], optional): Files to attach to the pdf file. Defaults to []. The file must be PDF. cop_verbose (bool, optional): Whether or not verbose mode should be activated. Defaults to False. """ self.data: Union[Element, Mapping[str, Element], RESTSource] = data diff --git a/docs/cloudofficeprint/config/cloud.html b/docs/cloudofficeprint/config/cloud.html index de57404..bfec7fc 100644 --- a/docs/cloudofficeprint/config/cloud.html +++ b/docs/cloudofficeprint/config/cloud.html @@ -2,18 +2,32 @@ - - + + cloudofficeprint.config.cloud API documentation - - - - - - + + + + + + - - + +
@@ -22,259 +36,6 @@

Module cloudofficeprint.config.cloud

-
- -Expand source code - -
import json
-from typing import Dict, List
-from abc import ABC, abstractmethod
-
-__all__ = [
-    "CloudAccessToken",
-    "OAuthToken",
-    "AWSToken",
-    "FTPToken"
-]
-
-SERVICES = [
-    "dropbox",
-    "gdrive",
-    "onedrive",
-    "aws_s3",
-    "sftp",
-    "ftp"
-]
-
-
-class CloudAccessToken(ABC):
-    """Abstract base class for classes used to specify cloud access information for outputting to a cloud service."""
-
-    def __init__(self, service: str):
-        """
-        Args:
-            service (str): name of the cloud service
-
-        Raises:
-            ValueError: raise error if the given name for the cloud service is not known
-        """
-        if not self.is_valid_service(service):
-            raise ValueError(f'Unsupported cloud service "{service}".')
-        self._service = service
-
-    @property
-    def service(self) -> str:
-        """Returns which cloud service is being used.
-
-        Returns:
-            str: which cloud service is being used
-        """
-        return self._service
-
-    @service.setter
-    def service(self, value: str):
-        """Setter for self._service
-
-        Args:
-            value (str): new value for self._service
-
-        Raises:
-            ValueError: raise error if the given name for the cloud service is not known
-        """
-        if not self.is_valid_service(value):
-            raise ValueError(f'Unsupported cloud service "{value}".')
-        self._service = value
-
-    @property
-    @abstractmethod
-    def as_dict(self) -> Dict:
-        """The cloud access token as a dict, for building the JSON.
-
-        Returns:
-            Dict: dict representation for this cloud access token
-        """
-        return {
-            "output_location": self.service
-        }
-
-    @property
-    def json(self) -> str:
-        """The cloud access token as JSON.
-
-        Returns:
-            str: JSON representation for this cloud access token
-        """
-        return json.dumps(self.as_dict)
-
-    @staticmethod
-    def is_valid_service(value: str) -> bool:
-        """Check if the given value is a valid service string.
-
-        Args:
-            value (str): the service to check
-
-        Returns:
-            bool: whether value is valid
-        """
-        return value in SERVICES
-
-    @staticmethod
-    def list_available_services() -> List[str]:
-        """List all available services.
-
-        Returns:
-            List[str]: list of available service strings
-        """
-        return SERVICES
-
-    @staticmethod
-    def from_OAuth(service: str, token: str) -> 'OAuthToken':
-        """Create a token from an OAuth string and service name.
-
-        Args:
-            service (str): cloud service
-            token (str): OAuth access token
-
-        Returns:
-            OAuthToken: created token
-        """
-        return OAuthToken(service, token)
-
-    @staticmethod
-    def from_AWS(key_id: str, secret_key: str):
-        """Create a token from Amazon S3 access key id and secret access key.
-
-        Args:
-            key_id (str): AWS access key ID
-            secret_key (str): AWS secret access key
-
-        Returns:
-            AWSToken: created token
-        """
-        return AWSToken(key_id, secret_key)
-
-    @staticmethod
-    def from_FTP(host: str, port: int = None, user: str = None, password: str = None) -> 'FTPToken':
-        """Create a token from FTP info
-
-        When an argument is / defaults to None, no data about it is sent to the Cloud Office Print server.
-        The Cloud Office Print server will then fill in default values.
-
-        Args:
-            host (str): host name or IP address
-            port (int, optional): port to use. Defaults to None.
-            user (str, optional): user name. Defaults to None.
-            password (str, optional): password for user. Defaults to None.
-
-        Returns:
-            FTPToken: created token
-        """
-        return FTPToken(host, False, port, user, password)
-
-    @staticmethod
-    def from_SFTP(host: str, port: int = None, user: str = None, password: str = None) -> 'FTPToken':
-        """Create a token from SFTP info
-
-        When an argument is / defaults to None, no data about it is sent to the Cloud Office Print server.
-        The Cloud Office Print server will then fill in default values.
-
-        Args:
-            host (str): host name or IP address
-            port (int, optional): port to use. Defaults to None.
-            user (str, optional): user name. Defaults to None.
-            password (str, optional): password for user. Defaults to None.
-
-        Returns:
-            FTPToken: created token
-                      This is an FTPToken object, with sftp=True passed into the constructor.
-                      The only difference with FTP is CloudAccessToken.servicename.
-        """
-        return FTPToken(host, True, port, user, password)
-
-
-class OAuthToken(CloudAccessToken):
-    """`CloudAccessToken` to be used for OAuth tokens"""
-
-    def __init__(self, service: str, token: str):
-        """
-        Args:
-            service (str): `CloudAccessToken.service`
-            token (str): OAuth token
-        """
-        super().__init__(service)
-        self.token: str = token
-
-    @property
-    def as_dict(self) -> Dict:
-        result = super().as_dict
-        result.update({
-            "cloud_access_token": self.token
-        })
-        return result
-
-
-class AWSToken(CloudAccessToken):
-    """`CloudAccessToken` to be used for AWS tokens"""
-
-    def __init__(self, key_id: str, secret_key: str):
-        """
-        Args:
-            key_id (str): AWS access key ID
-            secret_key (str): AWS secret key
-        """
-        super().__init__("aws_s3")
-        self.key_id: str = key_id
-        self.secret_key: str = secret_key
-
-    @property
-    def as_dict(self) -> Dict:
-        result = super().as_dict
-        result.update({
-            "cloud_access_token": {
-                "access_key": self.key_id,
-                "secret_access_key": self.secret_key
-            }
-        })
-        return result
-
-
-class FTPToken(CloudAccessToken):
-    """`CloudAccessToken` to be used for FTP/SFTP tokens"""
-
-    def __init__(self, host: str, sftp: bool = False, port: int = None, user: str = None, password: str = None):
-        """
-        Args:
-            host (str): Host name or IP address of the FTP/SFTP server.
-            sftp (bool, optional): whether to use SFTP (else FTP). Defaults to False.
-            port (int, optional): Port number of the FTP/SFTP server. Defaults to None.
-            user (str, optional): User name for the FTP/SFTP server. Defaults to None.
-            password (str, optional): Password for the user. Defaults to None.
-        """
-        super().__init__("sftp" if sftp else "ftp")
-        self.host: str = host
-        self.port: int = port
-        self.user: str = user
-        self.password: str = password
-
-    @property
-    def as_dict(self) -> Dict:
-        cloud_access_token = {
-            "host": self.host
-        }
-        if self.port is not None:
-            cloud_access_token["port"] = self.port
-        if self.user is not None:
-            cloud_access_token["user"] = self.user
-        if self.password is not None:
-            cloud_access_token["password"] = self.password
-
-        result = super().as_dict
-        result.update({
-            "cloud_access_token": cloud_access_token
-        })
-
-        return result
-
@@ -290,14 +51,6 @@

Classes

(key_id: str, secret_key: str)
-

CloudAccessToken to be used for AWS tokens

-

Args

-
-
key_id : str
-
AWS access key ID
-
secret_key : str
-
AWS secret key
-
Expand source code @@ -326,6 +79,14 @@

Args

}) return result
+

CloudAccessToken to be used for AWS tokens

+

Args

+
+
key_id : str
+
AWS access key ID
+
secret_key : str
+
AWS secret key
+

Ancestors

  • CloudAccessToken
  • @@ -353,17 +114,6 @@

    Inherited members

    (service: str)
    -

    Abstract base class for classes used to specify cloud access information for outputting to a cloud service.

    -

    Args

    -
    -
    service : str
    -
    name of the cloud service
    -
    -

    Raises

    -
    -
    ValueError
    -
    raise error if the given name for the cloud service is not known
    -
    Expand source code @@ -512,6 +262,17 @@

    Raises

    """ return FTPToken(host, True, port, user, password)
    +

    Abstract base class for classes used to specify cloud access information for outputting to a cloud service.

    +

    Args

    +
    +
    service : str
    +
    name of the cloud service
    +
    +

    Raises

    +
    +
    ValueError
    +
    raise error if the given name for the cloud service is not known
    +

    Ancestors

    • abc.ABC
    • @@ -528,19 +289,6 @@

      Static methods

      def from_AWS(key_id: str, secret_key: str)
      -

      Create a token from Amazon S3 access key id and secret access key.

      -

      Args

      -
      -
      key_id : str
      -
      AWS access key ID
      -
      secret_key : str
      -
      AWS secret access key
      -
      -

      Returns

      -
      -
      AWSToken
      -
      created token
      -
      Expand source code @@ -558,30 +306,24 @@

      Returns

      """ return AWSToken(key_id, secret_key)
      -
      -
      -def from_FTP(host: str, port: int = None, user: str = None, password: str = None) ‑> FTPToken -
      -
      -

      Create a token from FTP info

      -

      When an argument is / defaults to None, no data about it is sent to the Cloud Office Print server. -The Cloud Office Print server will then fill in default values.

      +

      Create a token from Amazon S3 access key id and secret access key.

      Args

      -
      host : str
      -
      host name or IP address
      -
      port : int, optional
      -
      port to use. Defaults to None.
      -
      user : str, optional
      -
      user name. Defaults to None.
      -
      password : str, optional
      -
      password for user. Defaults to None.
      +
      key_id : str
      +
      AWS access key ID
      +
      secret_key : str
      +
      AWS secret access key

      Returns

      -
      FTPToken
      +
      AWSToken
      created token
      +
      +
      +def from_FTP(host: str, port: int = None, user: str = None, password: str = None) ‑> FTPToken +
      +
      Expand source code @@ -604,24 +346,30 @@

      Returns

      """ return FTPToken(host, False, port, user, password)
      -
      -
      -def from_OAuth(service: str, token: str) ‑> OAuthToken -
      -
      -

      Create a token from an OAuth string and service name.

      +

      Create a token from FTP info

      +

      When an argument is / defaults to None, no data about it is sent to the Cloud Office Print server. +The Cloud Office Print server will then fill in default values.

      Args

      -
      service : str
      -
      cloud service
      -
      token : str
      -
      OAuth access token
      +
      host : str
      +
      host name or IP address
      +
      port : int, optional
      +
      port to use. Defaults to None.
      +
      user : str, optional
      +
      user name. Defaults to None.
      +
      password : str, optional
      +
      password for user. Defaults to None.

      Returns

      -
      OAuthToken
      +
      FTPToken
      created token
      +
      +
      +def from_OAuth(service: str, token: str) ‑> OAuthToken +
      +
      Expand source code @@ -639,32 +387,24 @@

      Returns

      """ return OAuthToken(service, token)
      -
      -
      -def from_SFTP(host: str, port: int = None, user: str = None, password: str = None) ‑> FTPToken -
      -
      -

      Create a token from SFTP info

      -

      When an argument is / defaults to None, no data about it is sent to the Cloud Office Print server. -The Cloud Office Print server will then fill in default values.

      +

      Create a token from an OAuth string and service name.

      Args

      -
      host : str
      -
      host name or IP address
      -
      port : int, optional
      -
      port to use. Defaults to None.
      -
      user : str, optional
      -
      user name. Defaults to None.
      -
      password : str, optional
      -
      password for user. Defaults to None.
      +
      service : str
      +
      cloud service
      +
      token : str
      +
      OAuth access token

      Returns

      -
      FTPToken
      -
      created token -This is an FTPToken object, with sftp=True passed into the constructor. -The only difference with FTP is CloudAccessToken.servicename.
      +
      OAuthToken
      +
      created token
      +
      +
      +def from_SFTP(host: str, port: int = None, user: str = None, password: str = None) ‑> FTPToken +
      +
      Expand source code @@ -689,22 +429,32 @@

      Returns

      """ return FTPToken(host, True, port, user, password)
      -
      -
      -def is_valid_service(value: str) ‑> bool -
      -
      -

      Check if the given value is a valid service string.

      +

      Create a token from SFTP info

      +

      When an argument is / defaults to None, no data about it is sent to the Cloud Office Print server. +The Cloud Office Print server will then fill in default values.

      Args

      -
      value : str
      -
      the service to check
      +
      host : str
      +
      host name or IP address
      +
      port : int, optional
      +
      port to use. Defaults to None.
      +
      user : str, optional
      +
      user name. Defaults to None.
      +
      password : str, optional
      +
      password for user. Defaults to None.

      Returns

      -
      bool
      -
      whether value is valid
      +
      FTPToken
      +
      created token +This is an FTPToken object, with sftp=True passed into the constructor. +The only difference with FTP is CloudAccessToken.servicename.
      +
      +
      +def is_valid_service(value: str) ‑> bool +
      +
      Expand source code @@ -721,17 +471,22 @@

      Returns

      """ return value in SERVICES
      +

      Check if the given value is a valid service string.

      +

      Args

      +
      +
      value : str
      +
      the service to check
      +
      +

      Returns

      +
      +
      bool
      +
      whether value is valid
      +
      def list_available_services() ‑> List[str]
      -

      List all available services.

      -

      Returns

      -
      -
      List[str]
      -
      list of available service strings
      -
      Expand source code @@ -745,18 +500,18 @@

      Returns

      """ return SERVICES
      +

      List all available services.

      +

      Returns

      +
      +
      List[str]
      +
      list of available service strings
      +

      Instance variables

      -
      var as_dict : Dict
      +
      prop as_dict : Dict
      -

      The cloud access token as a dict, for building the JSON.

      -

      Returns

      -
      -
      Dict
      -
      dict representation for this cloud access token
      -
      Expand source code @@ -773,15 +528,15 @@

      Returns

      "output_location": self.service }
      -
      -
      var json : str
      -
      -

      The cloud access token as JSON.

      +

      The cloud access token as a dict, for building the JSON.

      Returns

      -
      str
      -
      JSON representation for this cloud access token
      +
      Dict
      +
      dict representation for this cloud access token
      +
      +
      prop json : str
      +
      Expand source code @@ -795,15 +550,15 @@

      Returns

      """ return json.dumps(self.as_dict)
      -
      -
      var service : str
      -
      -

      Returns which cloud service is being used.

      +

      The cloud access token as JSON.

      Returns

      str
      -
      which cloud service is being used
      +
      JSON representation for this cloud access token
      +
      +
      prop service : str
      +
      Expand source code @@ -817,28 +572,20 @@

      Returns

      """ return self._service
      +

      Returns which cloud service is being used.

      +

      Returns

      +
      +
      str
      +
      which cloud service is being used
      +
    class FTPToken -(host: str, sftp: bool = False, port: int = None, user: str = None, password: str = None) +(host: str,
    sftp: bool = False,
    port: int = None,
    user: str = None,
    password: str = None)
    -

    CloudAccessToken to be used for FTP/SFTP tokens

    -

    Args

    -
    -
    host : str
    -
    Host name or IP address of the FTP/SFTP server.
    -
    sftp : bool, optional
    -
    whether to use SFTP (else FTP). Defaults to False.
    -
    port : int, optional
    -
    Port number of the FTP/SFTP server. Defaults to None.
    -
    user : str, optional
    -
    User name for the FTP/SFTP server. Defaults to None.
    -
    password : str, optional
    -
    Password for the user. Defaults to None.
    -
    Expand source code @@ -880,6 +627,20 @@

    Args

    return result
    +

    CloudAccessToken to be used for FTP/SFTP tokens

    +

    Args

    +
    +
    host : str
    +
    Host name or IP address of the FTP/SFTP server.
    +
    sftp : bool, optional
    +
    whether to use SFTP (else FTP). Defaults to False.
    +
    port : int, optional
    +
    Port number of the FTP/SFTP server. Defaults to None.
    +
    user : str, optional
    +
    User name for the FTP/SFTP server. Defaults to None.
    +
    password : str, optional
    +
    Password for the user. Defaults to None.
    +

    Ancestors

    • CloudAccessToken
    • @@ -907,14 +668,6 @@

      Inherited members

      (service: str, token: str)
      -

      CloudAccessToken to be used for OAuth tokens

      -

      Args

      -
      -
      service : str
      -
      CloudAccessToken.service
      -
      token : str
      -
      OAuth token
      -
      Expand source code @@ -939,6 +692,14 @@

      Args

      }) return result
      +

      CloudAccessToken to be used for OAuth tokens

      +

      Args

      +
      +
      service : str
      +
      CloudAccessToken.service
      +
      token : str
      +
      OAuth token
      +

      Ancestors

      • CloudAccessToken
      • @@ -965,7 +726,6 @@

        Inherited members

      @@ -260,7 +202,6 @@

      Returns

    @@ -404,7 +313,6 @@

    Returns

@@ -865,7 +683,6 @@

Args