diff --git a/src/ifctester/README.md b/src/ifctester/README.md index 976d0d2482..f145f00804 100644 --- a/src/ifctester/README.md +++ b/src/ifctester/README.md @@ -16,6 +16,7 @@ Available flags: - ``-r`` / ``--reporter``: The reporting method to view audit results. Availabe reporters: Console, Txt, Json, Html, Ods, Bcf - ``--no-color``: Disable colour output (supported by Console reporting). +- ``--excel-safe``: Make sure exported ODS is safely exported for Excel. - ``-o`` / ``--output``: Output file (supported for all types of reporting except Console). ### Code example diff --git a/src/ifctester/ifctester/__main__.py b/src/ifctester/ifctester/__main__.py index 05b2c6ec29..a49c4c1094 100644 --- a/src/ifctester/ifctester/__main__.py +++ b/src/ifctester/ifctester/__main__.py @@ -33,6 +33,9 @@ parser.add_argument( parser.add_argument( "--no-color", help="Disable colour output (supported by Console reporting)", action="store_true" ) +parser.add_argument( + "--excel-safe", help="Make sure exported ODS is safely exported for Excel", action="store_true" +) parser.add_argument( "-o", "--output", help="Output file (supported for all types of reporting except Console)" ) @@ -56,7 +59,7 @@ elif args.reporter == "Json": elif args.reporter == "Html": engine = reporter.Html(specs) elif args.reporter == "Ods": - engine = reporter.Ods(specs) + engine = reporter.Ods(specs, excel_safe=args.excel_safe) elif args.reporter == "Bcf": engine = reporter.Bcf(specs) diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py index d83dc3d080..90f7fd1c36 100644 --- a/src/ifctester/ifctester/reporter.py +++ b/src/ifctester/ifctester/reporter.py @@ -18,6 +18,7 @@ from __future__ import annotations import os +import re import sys import math import logging @@ -386,8 +387,9 @@ class Html(Json): class Ods(Json): - def __init__(self, ids: Ids): + def __init__(self, ids: Ids, excel_safe=False): super().__init__(ids) + self.excel_safe = excel_safe self.colours = { "h": "cccccc", # Header "p": "97cc64", # Pass @@ -395,6 +397,33 @@ class Ods(Json): "t": "ffffff", # Regular text } + def excel_safe_spreadsheet_name(self, name: str) -> str: + if not self.excel_safe: + return name + + warning = ( + f'WARNING. Sheet name "{name}" is not valid for Excel and will be changed. ' + "See: https://support.microsoft.com/en-us/office/rename-a-worksheet-3f1f7148-ee83-404d-8ef0-9ff99fbad1f9" + ) + + if not name or name == "History": + print(warning) + return "placeholder spreadsheet name" + + if name.startswith("'") or name.endswith("'"): + print(warning) + name = name.strip("'") + + pattern = r"[\\\/\?\*\:\[\]]" + if re.search(pattern, name): + name = re.sub(pattern, "", name) + print(warning) + + if len(name) > 31: + name = name[:31] + print(warning) + return name + def to_file(self, filepath: str) -> None: from odf.opendocument import OpenDocumentSpreadsheet from odf.style import Style, TableCellProperties @@ -410,7 +439,7 @@ class Ods(Json): self.doc.automaticstyles.addElement(style) self.cell_formats[key] = style - table = Table(name=self.results["title"]) + table = Table(name=self.excel_safe_spreadsheet_name(self.results["title"])) tr = TableRow() for header in ["Specification", "Status", "Total Pass", "Total Checks", "Percentage Pass"]: tc = TableCell(valuetype="string", stylename="h") @@ -447,7 +476,7 @@ class Ods(Json): for specification in self.results["specifications"]: if specification["status"]: continue - table = Table(name=specification["name"]) + table = Table(name=self.excel_safe_spreadsheet_name(specification["name"])) tr = TableRow() for header in [ "Requirement",