From 10c026c7ad1b224598f33f76e905e9c40271c9ff Mon Sep 17 00:00:00 2001
From: Bepr4 <63661977@qq.com>
Date: Fri, 28 Aug 2026 18:06:31 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A0=B4=E6=8D=9F=20HTML=20=E8=A1=A8?=
=?UTF-8?q?=E6=A0=BC=E6=89=AB=E6=8F=8F=E5=A4=B1=E8=B4=A5=E5=85=B3=E9=97=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 9 ++++++++-
pyproject.toml | 2 +-
src/mdpolish/_html_table.py | 10 ++++++++--
src/mdpolish/modifiers/html_table_entities.py | 2 +-
src/mdpolish/modifiers/html_table_layout.py | 2 +-
tests/test_html_table_entities.py | 16 ++++++++++++++++
tests/test_html_table_layout.py | 16 ++++++++++++++++
tests/test_mapped_line_join.py | 2 +-
8 files changed, 52 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index e0e7bdf..045ea41 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,8 @@
`mdpolish` 是实验室共用的、项目无关的 Python Markdown 修改库。它提供函数式 `Modifier`、精确文本编辑执行器、
有序 `Pipeline`、正则修改器工厂,以及少量可以用合成样例完整说明的通用修改器。
-当前发布版本是 [`v0.6.0`](https://github.com/Bepr4/mdpolish/releases/tag/v0.6.0)。库只处理内存中的 Markdown 字符串,
+当前发布版本是 [`v0.6.0`](https://github.com/Bepr4/mdpolish/releases/tag/v0.6.0),当前工作树候选版本是尚未发布的
+`0.6.1`。库只处理内存中的 Markdown 字符串,
不读取或写入文件,不提供默认流水线,也不包含任何项目的规则集合、
数据清单、实验脚本或评审界面。
@@ -373,3 +374,9 @@ Release wheel `mdpolish-0.6.0-py3-none-any.whl` 共 17 个文件,包含 `text_
`_text_ranges.py`、tests、Wiki、报告或真实数据;在仓库外全新虚拟环境中无依赖安装后,版本、公共导入、精确混合行尾范围、
行尾集合、空行判断和 wheel 清单 smoke test 通过。Release wheel 的 SHA-256 是
`695502b1a443d4e98dbf63e8bdcee59452baea2185cb7e1e13160127f70c920f`。
+
+尚未发布的 `0.6.1` 候选修复了严格 HTML 表格扫描在未闭合或无法解析的外层 `
` 中继续处理完整内层表格的问题;
+两个 HTML Modifier 的版本均为 `1.0.1`。2026-08-28 在 Python 3.13.11 开发环境中 Ruff 和 mypy 通过,pytest 为
+`294 passed, 3 skipped`。安装候选 wheel 的全部 extras 后,Python 3.11.15 和 Python 3.13.11 环境分别得到
+`297 passed`,没有 skip。候选 wheel 共 17 个文件并通过内容检查,SHA-256 是
+`f628658a6d0b2860720e9425d190a477b78723dcda9b2d4b5a5e9f6e252faf07`。
diff --git a/pyproject.toml b/pyproject.toml
index f4ad777..17a5d5d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mdpolish"
-version = "0.6.0"
+version = "0.6.1"
description = "Deterministic functional core for composing exact Markdown modifiers"
requires-python = ">=3.11"
dependencies = []
diff --git a/src/mdpolish/_html_table.py b/src/mdpolish/_html_table.py
index fd72cee..fd8cd77 100644
--- a/src/mdpolish/_html_table.py
+++ b/src/mdpolish/_html_table.py
@@ -175,13 +175,19 @@ def strict_html_tables(markdown: str) -> tuple[HtmlTable, ...]:
if start < 0:
break
opening = _parse_tag(markdown, start)
+ name_end = start + len(""
+ ):
+ # 无法解析的 table 形开头同样会使后续嵌套关系不可信。
+ break
if opening is None or opening.closing or opening.name != "table":
position = start + 1
continue
balanced_end = _balanced_table_end(markdown, opening)
if balanced_end is None:
- position = opening.end
- continue
+ # 未闭合外层之后无法证明后续 table 是否位于它内部, 停止发现以失败关闭。
+ break
table = _strict_table_at(markdown, start, balanced_end)
if table is not None:
tables.append(table)
diff --git a/src/mdpolish/modifiers/html_table_entities.py b/src/mdpolish/modifiers/html_table_entities.py
index 2ee3ea3..1ccc496 100644
--- a/src/mdpolish/modifiers/html_table_entities.py
+++ b/src/mdpolish/modifiers/html_table_entities.py
@@ -50,7 +50,7 @@ def html_table_entity_unescape() -> Modifier:
return Modifier(
modifier_id="markdown.html_table_entity_unescape",
- version="1.0.0",
+ version="1.0.1",
parameters=(),
applicability=_APPLICABILITY,
propose=propose,
diff --git a/src/mdpolish/modifiers/html_table_layout.py b/src/mdpolish/modifiers/html_table_layout.py
index a590284..566fa15 100644
--- a/src/mdpolish/modifiers/html_table_layout.py
+++ b/src/mdpolish/modifiers/html_table_layout.py
@@ -50,7 +50,7 @@ def html_table_layout() -> Modifier:
return Modifier(
modifier_id="markdown.html_table_layout",
- version="1.0.0",
+ version="1.0.1",
parameters=(),
applicability=_APPLICABILITY,
propose=propose,
diff --git a/tests/test_html_table_entities.py b/tests/test_html_table_entities.py
index 1629403..0f0d4d1 100644
--- a/tests/test_html_table_entities.py
+++ b/tests/test_html_table_entities.py
@@ -23,6 +23,7 @@ def test_unescapes_one_layer_only_in_strict_cell_text() -> None:
"\noutside <"
)
assert len(result.changes) == 3
+ assert result.modifiers[0].version == "1.0.1"
def test_multiple_tables_and_cells_report_source_order() -> None:
@@ -47,6 +48,21 @@ def test_non_strict_or_outside_content_is_preserved(markdown: str) -> None:
assert transform(markdown).output_markdown == markdown
+@pytest.mark.parametrize(
+ "markdown",
+ [
+ "broken",
+ "",
+ ],
+)
+def test_damaged_outer_table_does_not_expose_complete_inner_table(markdown: str) -> None:
+ result = transform(markdown)
+
+ assert result.status is RunStatus.SUCCESS
+ assert result.output_markdown == markdown
+ assert result.changes == ()
+
+
def test_fenced_table_is_not_protected_by_the_lexical_subset() -> None:
markdown = "```html\n\n```"
assert transform(markdown).output_markdown == "```html\n\n```"
diff --git a/tests/test_html_table_layout.py b/tests/test_html_table_layout.py
index ea13880..cdfd02d 100644
--- a/tests/test_html_table_layout.py
+++ b/tests/test_html_table_layout.py
@@ -23,6 +23,7 @@ def test_expands_rows_without_changing_tags_attributes_or_cells() -> None:
"
\nafter"
)
assert len(result.changes) == 1
+ assert result.modifiers[0].version == "1.0.1"
@pytest.mark.parametrize("line_ending", ["\n", "\r\n", "\r"])
@@ -56,6 +57,21 @@ def test_mixed_multiline_or_non_strict_tables_are_preserved(markdown: str) -> No
assert transform(markdown).output_markdown == markdown
+@pytest.mark.parametrize(
+ "markdown",
+ [
+ "broken",
+ "",
+ ],
+)
+def test_damaged_outer_table_does_not_expose_complete_inner_table(markdown: str) -> None:
+ result = transform(markdown)
+
+ assert result.status is RunStatus.SUCCESS
+ assert result.output_markdown == markdown
+ assert result.changes == ()
+
+
def test_successful_output_is_idempotent() -> None:
pipeline = Pipeline([html_table_layout()])
first = pipeline.transform(TABLE)
diff --git a/tests/test_mapped_line_join.py b/tests/test_mapped_line_join.py
index acc6fb0..063cca8 100644
--- a/tests/test_mapped_line_join.py
+++ b/tests/test_mapped_line_join.py
@@ -362,7 +362,7 @@ def test_empty_document_and_empty_rule_set_are_no_ops() -> None:
def test_installed_package_version_matches_delivery_candidate() -> None:
- assert distribution_version("mdpolish") == "0.6.0"
+ assert distribution_version("mdpolish") == "0.6.1"
def test_parameters_preserve_rule_order_and_record_all_options() -> None: