feat(chunks): rilevamento universale has_math/has_table + dollarmath plugin

- parser.py: integra dollarmath_plugin (graceful fallback) — blocchi $$…$$ → token math_block atomico
- segmenter.py: gestisce math_block → Block(kind=math, atomic=True, plain_text=[formula])
- packer.py: has_table rileva anche <table> in blocchi html; has_math rileva $$ e \begin{ nel contenuto
- +11 test (parser×3, segmenter×3, packer×5) — totale 65 test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 08:40:41 +02:00
parent 1980efb0d6
commit 3b61df73d3
6 changed files with 121 additions and 3 deletions
+32
View File
@@ -116,3 +116,35 @@ def test_assets_empty(cfg):
blocks = [make_block(1, "Testo.")]
chunks = pack(blocks, cfg, "test")
assert chunks[0].assets == []
def test_flags_has_math_via_kind(cfg):
blocks = [make_block(1, "$$\nE=mc^2\n$$", kind="math", atomic=True)]
chunks = pack(blocks, cfg, "test")
assert chunks[0].flags["has_math"] is True
def test_flags_has_math_via_content_dollars(cfg):
# Paragrafo con $$ inline (senza dollarmath plugin attivo)
blocks = [make_block(1, "Vedi la formula:\n$$\nx^2\n$$")]
chunks = pack(blocks, cfg, "test")
assert chunks[0].flags["has_math"] is True
def test_flags_has_math_via_content_begin(cfg):
blocks = [make_block(1, r"Equazione: \begin{align} x = 1 \end{align}")]
chunks = pack(blocks, cfg, "test")
assert chunks[0].flags["has_math"] is True
def test_flags_has_table_via_html(cfg):
html_content = "<table><tr><td>A</td></tr></table>"
blocks = [make_block(1, html_content, kind="html", atomic=True)]
chunks = pack(blocks, cfg, "test")
assert chunks[0].flags["has_table"] is True
def test_flags_no_math_plain_text(cfg):
blocks = [make_block(1, "Testo normale senza formule.")]
chunks = pack(blocks, cfg, "test")
assert chunks[0].flags["has_math"] is False
+23
View File
@@ -44,3 +44,26 @@ def test_lines_preserves_source():
_, lines = parse(md)
assert lines[0] == "Riga 1"
assert lines[1] == "Riga 2"
def test_parse_math_block():
md = "# T\n\n$$\nE=mc^2\n$$\n"
tokens, lines = parse(md)
types = [t.type for t in tokens]
assert "math_block" in types
def test_math_block_has_source_map():
md = "# T\n\n$$\nE=mc^2\n$$\n"
tokens, lines = parse(md)
mb = next(t for t in tokens if t.type == "math_block")
assert mb.map is not None
assert "E=mc^2" in mb.content
def test_parse_math_inline_stays_in_paragraph():
md = "Testo con $x^2$ inline.\n"
tokens, lines = parse(md)
types = [t.type for t in tokens]
assert "paragraph_open" in types
assert "math_block" not in types
+27
View File
@@ -126,3 +126,30 @@ def test_source_line_numbers(cfg):
para = next(b for b in blocks if b.kind == "paragraph")
assert para.start_line == 2
assert para.end_line == 3
def test_math_block_is_atomic(cfg):
md = "# T\n\n$$\nE=mc^2\n$$\n"
tokens, lines = parse(md)
blocks = segment(tokens, lines, cfg)
math = next((b for b in blocks if b.kind == "math"), None)
assert math is not None
assert math.atomic is True
assert "E=mc^2" in math.content
def test_math_block_plain_text(cfg):
md = "# T\n\n$$\n\\int_0^1 f(x) dx\n$$\n"
tokens, lines = parse(md)
blocks = segment(tokens, lines, cfg)
math = next(b for b in blocks if b.kind == "math")
assert math.plain_text == "[formula]"
def test_html_block_with_table(cfg):
md = "# T\n\n<table><tr><td>A</td></tr></table>\n"
tokens, lines = parse(md)
blocks = segment(tokens, lines, cfg)
html = next((b for b in blocks if b.kind == "html"), None)
assert html is not None
assert "<table" in html.content