|
6 | 6 | import re |
7 | 7 | import shutil |
8 | 8 | import zipfile |
9 | | -from types import SimpleNamespace |
10 | 9 | import pytest |
| 10 | +from types import SimpleNamespace |
11 | 11 | from unittest.mock import MagicMock |
12 | 12 |
|
13 | 13 | import markitdown._uri_utils as uri_utils |
@@ -374,6 +374,77 @@ def test_docx_equations() -> None: |
374 | 374 | assert block_equations, "No block equations found in the document." |
375 | 375 |
|
376 | 376 |
|
| 377 | +def test_xlsx_legacy_show_zeroes_sheetview(tmp_path) -> None: |
| 378 | + from openpyxl import Workbook |
| 379 | + |
| 380 | + base_path = tmp_path / "base.xlsx" |
| 381 | + xlsx_path = tmp_path / "legacy_show_zeroes.xlsx" |
| 382 | + |
| 383 | + workbook = Workbook() |
| 384 | + sheet = workbook.active |
| 385 | + sheet.title = "Data" |
| 386 | + sheet["A1"] = "hello" |
| 387 | + sheet["B1"] = "world" |
| 388 | + # A cell whose text is byte-identical to the malformed attribute. openpyxl stores it |
| 389 | + # as an inline string, so it lands in the very worksheet part that gets repaired. |
| 390 | + sheet["A2"] = ' showZeroes="0" ' |
| 391 | + workbook.save(base_path) |
| 392 | + |
| 393 | + with zipfile.ZipFile(base_path) as source: |
| 394 | + with zipfile.ZipFile(xlsx_path, "w", zipfile.ZIP_DEFLATED) as target: |
| 395 | + for item in source.infolist(): |
| 396 | + data = source.read(item.filename) |
| 397 | + if item.filename == "xl/worksheets/sheet1.xml": |
| 398 | + data = data.replace( |
| 399 | + b"<sheetView ", b'<sheetView showZeroes="0" ', 1 |
| 400 | + ) |
| 401 | + # Guard the fixture: the sheet view attribute and the cell text must |
| 402 | + # both live here, or this test stops exercising the repair. |
| 403 | + assert data.count(b'showZeroes="0"') == 2 |
| 404 | + target.writestr(item, data) |
| 405 | + |
| 406 | + result = MarkItDown().convert(str(xlsx_path)) |
| 407 | + |
| 408 | + assert "## Data" in result.markdown |
| 409 | + assert "hello" in result.markdown |
| 410 | + assert "world" in result.markdown |
| 411 | + |
| 412 | + # The repair must not reach into the worksheet's data: the cell keeps its text ... |
| 413 | + assert 'showZeroes="0"' in result.markdown |
| 414 | + # ... and no part of the document was silently renamed on the way through. |
| 415 | + assert "showZeros" not in result.markdown |
| 416 | + |
| 417 | + |
| 418 | +def test_xlsx_show_zeroes_rename_is_scoped_to_sheet_view_tags() -> None: |
| 419 | + from markitdown.converters._xlsx_converter import _rename_show_zeroes_attribute |
| 420 | + |
| 421 | + worksheet_xml = ( |
| 422 | + b"<worksheet><sheetViews>" |
| 423 | + b'<sheetView showZeroes="0" workbookViewId="0"/>' |
| 424 | + b'<sheetView\n\tshowZeroes="1"\ttabSelected="1"/>' |
| 425 | + b"</sheetViews>" |
| 426 | + # openpyxl ignores custom sheet views entirely, so they need no repair |
| 427 | + b'<customSheetViews><customSheetView showZeroes="0"/></customSheetViews>' |
| 428 | + b'<sheetData><row r="1">' |
| 429 | + b'<c r="A1" t="inlineStr"><is><t xml:space="preserve"> showZeroes="0" ' |
| 430 | + b"</t></is></c>" |
| 431 | + b'<c r="B1"><f>IF(A1=" showZeroes=","x","y")</f><v>y</v></c>' |
| 432 | + b"</row></sheetData></worksheet>" |
| 433 | + ) |
| 434 | + |
| 435 | + repaired = _rename_show_zeroes_attribute(worksheet_xml) |
| 436 | + |
| 437 | + # Every <sheetView> start tag is repaired, whatever separates its attributes ... |
| 438 | + assert repaired.count(b"showZeros=") == 2 |
| 439 | + assert b'<sheetView showZeros="0" workbookViewId="0"/>' in repaired |
| 440 | + assert b'<sheetView\n\tshowZeros="1"\ttabSelected="1"/>' in repaired |
| 441 | + |
| 442 | + # ... and nothing outside those start tags is rewritten. |
| 443 | + assert b'<t xml:space="preserve"> showZeroes="0" </t>' in repaired |
| 444 | + assert b'<f>IF(A1=" showZeroes=","x","y")</f>' in repaired |
| 445 | + assert b'<customSheetView showZeroes="0"/>' in repaired |
| 446 | + |
| 447 | + |
377 | 448 | def test_input_as_strings() -> None: |
378 | 449 | markitdown = MarkItDown() |
379 | 450 |
|
|
0 commit comments