"""Tests for table cell merge (^ rowspan, > colspan).""" from __future__ import annotations import os from docx import Document from docx.shared import Mm, Pt from md2gost import package_dir from md2gost.checker import check_table_merge from md2gost.docx_elements import apply_cell_merge, create_table, create_table_cell, create_table_row from md2gost.extended_markdown import markdown from md2gost.extended_markdown.table import Table as MdTable from md2gost.layout_tracker import LayoutState from md2gost.renderable.caption import CaptionInfo from md2gost.renderable_factory import RenderableFactory SAMPLE_ROWSPAN = """\ %req Требования | Категория | Описание | |-----------|----------| | Производительность | Требование 1 | | ^ | Требование 2 | | ^ | Требование 3 | | Масштабируемость | Требование 4 | | ^ | Требование 5 | """ SAMPLE_COLSPAN = """\ %cspan Demo | A | B | C | |---|---|---| | wide | > | Z | """ def test_parse_rowspan_markers(): doc = markdown.parse(SAMPLE_ROWSPAN) tables = [c for c in doc.children if isinstance(c, MdTable)] assert len(tables) == 1 t = tables[0] assert len(t.children) == 6 assert t.children[1].children[0].merge_v == "restart" assert t.children[2].children[0].merge_v == "continue" assert t.children[3].children[0].merge_v == "continue" assert t.children[4].children[0].merge_v == "restart" assert t.children[5].children[0].merge_v == "continue" def test_parse_colspan_markers(): doc = markdown.parse(SAMPLE_COLSPAN) tables = [c for c in doc.children if isinstance(c, MdTable)] assert len(tables) == 1 t = tables[0] data = t.children[1].children assert data[0].merge_h == "restart" assert data[1].merge_h == "continue" assert data[2].merge_h == "none" def test_apply_cell_merge_oxml(): doc = Document() table = create_table(doc, 0, 2, doc.sections[0].page_width) assert 'w:type="dxa"' in table._tbl.xml row = create_table_row(table) cell = create_table_cell(row, doc.sections[0].page_width / 2) apply_cell_merge(cell, v_merge="restart", grid_span=2) xml = cell._tc.xml assert "vMerge" in xml assert "restart" in xml assert "gridSpan" in xml def test_create_table_fixed_width(): doc = Document() width = Mm(170) table = create_table(doc, 0, 3, width) xml = table._tbl.xml assert 'w:type="dxa"' in xml assert 'w:type="fixed"' in xml assert xml.count("gridCol") == 3 # ~170 mm ≈ 9638 twips, not EMU (~6e6) from md2gost.docx_elements import _twips assert 9000 < _twips(width) < 11000 import re m = re.search(r'w:tblW[^>]*w:w="(\d+)"', xml) or re.search(r'w:w="(\d+)"[^>]*w:type="dxa"', xml) # tblW attrs order may vary w_attr = re.search(r'', xml) assert w_attr tw = int(re.search(r'w:w="(\d+)"', w_attr.group(0)).group(1)) assert 9000 < tw < 11000, f"tblW too large (EMU mistaken for twips?): {tw}" def test_twips_from_length_sum(): """Length+Length yields EMU int — must convert to twips, not treat as twips.""" from md2gost.docx_elements import _twips from docx.shared import Twips as T summed = Mm(170) + T(108) # bare EMU int assert isinstance(summed, int) assert 9000 < _twips(summed) < 12000 def test_factory_sets_merge_and_oxml_row(): """Factory passes merge metadata; physical row has vMerge without full convert.""" parsed = markdown.parse(SAMPLE_ROWSPAN) md_table = next(c for c in parsed.children if isinstance(c, MdTable)) doc = Document(os.path.join(package_dir(), "Template.docx")) factory = RenderableFactory(doc._body) table = factory.create(md_table, CaptionInfo("req", "Требования")) assert table._merge[1][0][0] == "restart" assert table._merge[2][0][0] == "continue" for r in range(len(table._rows)): for c in range(table._cols): table._rows[r][c] = [] docx_table = create_table(doc, 0, table._cols, Mm(170)) layout = LayoutState(Mm(250), Mm(170)) row_el, _ = table._build_docx_row(docx_table, 1, True, layout) docx_table._element.append(row_el._element) row2, _ = table._build_docx_row(docx_table, 2, True, layout) docx_table._element.append(row2._element) xml = docx_table._tbl.xml assert "vMerge" in xml assert "restart" in xml def test_checker_merge_header_error(): bad = """\ | ^ | B | |---|---| | 1 | 2 | """ issues = check_table_merge(bad) assert any(i.id == "table.merge_header" for i in issues) def test_checker_merge_col_error(): bad = """\ | A | B | |---|---| | > | 2 | """ issues = check_table_merge(bad) assert any(i.id == "table.merge_col" for i in issues) def test_checker_merge_ok(): issues = check_table_merge(SAMPLE_ROWSPAN) assert issues == [] def test_table_continuation_modes(): from md2gost.profiles import TABLE_CONTINUATION_MODES, DEFAULT_TABLE_CONTINUATION assert DEFAULT_TABLE_CONTINUATION == "word" assert TABLE_CONTINUATION_MODES == ("off", "legacy", "caption", "soft", "word") doc = Document(os.path.join(package_dir(), "Template.docx")) from md2gost.renderable.table import Table as RTable from md2gost.renderable.caption import CaptionInfo t = RTable(doc._body, 1, 2, CaptionInfo("x", "y")) assert t._continuation_mode == "word" t.set_continuation_mode("legacy") assert t._continuation_mode == "legacy" t.set_continuation_mode("caption") assert t._continuation_mode == "caption" t.set_continuation_mode("soft") assert t._continuation_mode == "soft" assert not t._should_split_fragment(Pt(1000), LayoutState(Mm(250), Mm(170)), 3) t.set_continuation_mode("caption") assert t._should_split_fragment(Pt(1000), LayoutState(Mm(50), Mm(170)), 3) try: t.set_continuation_mode("nope") assert False except ValueError: pass