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 &lt;" ) 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
&lt;
", + "
&lt;
", + ], +) +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
&lt;
\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
A
", + "
A
", + ], +) +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: