@@ -0,0 +1,181 @@
|
||||
"""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'<w:tblW[^/]*/>', 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 == "off"
|
||||
assert TABLE_CONTINUATION_MODES == ("off", "legacy", "caption", "soft")
|
||||
|
||||
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 == "off"
|
||||
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
|
||||
Reference in New Issue
Block a user