From 214502277ff1cf6f136518e554cb01059c7dc1ec Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 23 Jul 2026 15:57:28 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Tabellenfarben=20beim=20gegl=C3=A4tteten?= =?UTF-8?q?=20Excel-Import=20erhalten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netbox_documentation/__init__.py | 2 +- netbox_documentation/importers.py | 55 ++++++++++++++++--------------- pyproject.toml | 2 +- tests/test_importers.py | 33 ++++++++++++++++--- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/netbox_documentation/__init__.py b/netbox_documentation/__init__.py index 8d63c6e..d30b857 100644 --- a/netbox_documentation/__init__.py +++ b/netbox_documentation/__init__.py @@ -5,7 +5,7 @@ class DocumentationConfig(PluginConfig): name = "netbox_documentation" verbose_name = "NetBox Dokumentation" description = "Wiki und Office-Dokumentation direkt in NetBox" - version = "0.7.7" + version = "0.7.8" author = "LKE" base_url = "documentation" min_version = "4.0.0" diff --git a/netbox_documentation/importers.py b/netbox_documentation/importers.py index b2d896d..a57e0e4 100644 --- a/netbox_documentation/importers.py +++ b/netbox_documentation/importers.py @@ -91,11 +91,11 @@ def _xlsx(content, flatten=False): rows.pop() if not rows: continue - rendered = _render_flattened_worksheet(sheet) if flatten else _render_excel_worksheet_html(sheet, workbook) - sections.append(f"## {sheet.title}\n\n{rendered}" if flatten else f"

{escape(sheet.title)}

\n{rendered}") + rendered = _render_flattened_worksheet(sheet, workbook) if flatten else _render_excel_worksheet_html(sheet, workbook) + sections.append(f"

{escape(sheet.title)}

\n{rendered}") if not sections: raise ImportFailure("Die Arbeitsmappe enthält keine Daten.") - return ImportResult("\n\n".join(sections), body_format="markdown" if flatten else "html") + return ImportResult("\n\n".join(sections), body_format="html") def import_excel_sheets( @@ -140,14 +140,14 @@ def import_excel_sheets( markdown = "" elif has_content: markdown = ( - _render_flattened_worksheet(sheet, max_rows=preview_max_rows) if flatten else + _render_flattened_worksheet(sheet, workbook, max_rows=preview_max_rows) if flatten else _render_excel_worksheet_html(sheet, workbook, max_rows=preview_max_rows) ) else: markdown = "" results.append(ExcelSheetResult( title=sheet.title, markdown=markdown, images=images, - body_format="markdown" if flatten else "html", + body_format="html", )) workbook.close() upload.seek(0) @@ -278,19 +278,26 @@ def _excel_table_cell_styles(sheet, workbook, max_row, max_column): return styles -def _render_excel_worksheet_html(sheet, workbook, max_rows=None): +def _render_excel_worksheet_html(sheet, workbook, max_rows=None, bounds=None): """Render worksheet values and the most relevant visual Excel cell formatting.""" from openpyxl.utils import get_column_letter + if bounds: + start_column, start_row, bound_max_column, bound_max_row = bounds + else: + start_column, start_row, bound_max_column, bound_max_row = 1, 1, sheet.max_column, sheet.max_row last_row = last_column = 0 - for row in sheet.iter_rows(): + for row in sheet.iter_rows( + min_row=start_row, max_row=bound_max_row, + min_col=start_column, max_col=bound_max_column, + ): for cell in row: if cell.value not in (None, ""): last_row = max(last_row, cell.row) last_column = max(last_column, cell.column) if not last_row: return "

Keine Inhalte

" - max_row = min(last_row, max_rows) if max_rows else last_row + max_row = min(last_row, start_row + max_rows - 1) if max_rows else last_row max_column = last_column table_styles = _excel_table_cell_styles(sheet, workbook, max_row, max_column) merged_starts, merged_children = {}, set() @@ -306,16 +313,16 @@ def _render_excel_worksheet_html(sheet, workbook, max_rows=None): if (row, column) != (merged.min_row, merged.min_col): merged_children.add((row, column)) columns = [] - for column in range(1, max_column + 1): + for column in range(start_column, max_column + 1): width = sheet.column_dimensions[get_column_letter(column)].width columns.append(f'') output = ['', *columns, ""] - for row in range(1, max_row + 1): + for row in range(start_row, max_row + 1): row_style = "" if sheet.row_dimensions[row].height: row_style = f' style="height: {float(sheet.row_dimensions[row].height):g}pt"' output.append(f"") - for column in range(1, max_column + 1): + for column in range(start_column, max_column + 1): if (row, column) in merged_children: continue cell = sheet.cell(row=row, column=column) @@ -336,12 +343,12 @@ def _render_excel_worksheet_html(sheet, workbook, max_rows=None): output.append(f"{value}") output.append("") output.append("
") - if max_rows and last_row > max_rows: - output.append(f"

Vorschau auf {max_rows} von {last_row} Zeilen begrenzt.

") + if max_rows and last_row > start_row + max_rows - 1: + output.append(f"

Vorschau auf {max_rows} Zeilen begrenzt.

") return "".join(output) -def _render_flattened_worksheet(sheet, max_rows=None): +def _render_flattened_worksheet(sheet, workbook, max_rows=None): """Flatten ordinary cells while preserving explicitly defined Excel tables.""" from openpyxl.utils.cell import range_boundaries @@ -373,21 +380,17 @@ def _render_flattened_worksheet(sheet, max_rows=None): continue value = sheet.cell(row=row_number, column=column_number).value if value not in (None, ""): - ordinary_values.append(_escape_markdown_text(value)) + ordinary_values.append(escape(str(value)).replace("\n", "
")) if ordinary_values: - blocks.append(" \n".join(ordinary_values)) + blocks.append("

" + "
".join(ordinary_values) + "

") for min_col, min_row, max_col, max_row in tables_starting_here: - rows = [] - for table_row in sheet.iter_rows( - min_row=min_row, max_row=min(max_row, rendered_max_row), - min_col=min_col, max_col=max_col, values_only=True - ): - rows.append(["" if value is None else str(value) for value in table_row]) - if rows: - blocks.append(_render_excel_rows(rows, flatten=False)) + blocks.append(_render_excel_worksheet_html( + sheet, workbook, + bounds=(min_col, min_row, max_col, min(max_row, rendered_max_row)), + )) if max_rows and sheet.max_row > max_rows: - blocks.append(f"_Vorschau auf {max_rows} von {sheet.max_row} Zeilen begrenzt._") - return "\n\n".join(blocks) or "_Keine Inhalte_" + blocks.append(f"

Vorschau auf {max_rows} von {sheet.max_row} Zeilen begrenzt.

") + return "\n".join(blocks) or "

Keine Inhalte

" def _pdf(content): diff --git a/pyproject.toml b/pyproject.toml index d555d05..4320c85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "netbox-documentation" -version = "0.7.7" +version = "0.7.8" description = "Integrated Markdown wiki and office document importer for NetBox" readme = "README.md" requires-python = ">=3.10" diff --git a/tests/test_importers.py b/tests/test_importers.py index 4d6cf4e..0aeadd1 100644 --- a/tests/test_importers.py +++ b/tests/test_importers.py @@ -145,7 +145,7 @@ def test_excel_preview_limits_rows_without_affecting_sheet_detection(): include_image_data=False, ) - assert "Vorschau auf 100 von 151 Zeilen begrenzt" in results[0].markdown + assert "Vorschau auf 100 Zeilen begrenzt" in results[0].markdown assert ">99<" in results[0].markdown assert ">100<" not in results[0].markdown @@ -160,10 +160,11 @@ def test_excel_flatten_turns_cells_into_document_flow(): results = import_excel_sheets(Upload(stream.getvalue(), "server.xlsx"), flatten=True) - assert results[0].markdown == "Name \nIP\n\ndb01 \n10.0.0.2" + assert results[0].body_format == "html" + assert results[0].markdown == "

Name
IP

\n

db01
10.0.0.2

" assert "Datensatz" not in results[0].markdown assert "Feld" not in results[0].markdown - assert "|" not in results[0].markdown + assert "