From 89d29cf1336c4f1d216a2cb51041aab69dfc4c29 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 16 Apr 2024 16:40:05 +0500 Subject: [PATCH] ifctester.reporter.Ods - option for safe excel export #4427 The issue was related to the sheet names - Excel prohibits some symbols in the names and it doesn't allow sheet name to be longer than 31 symbols. If sheet names doesn't match this criteria then Excel says file appears to be corrupted - when file open data itself is not really corrupted besides the spreadsheet names that failed the criteria. --- src/ifctester/README.md | 1 + src/ifctester/ifctester/__main__.py | 5 ++++- src/ifctester/ifctester/reporter.py | 35 ++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) 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",