fix(html-snapshot): stop merged cells and nested tables misaligning parameter tables (closes #81) - #87
Conversation
…saligning parameter tables render_table 先前把整棵子樹的每個 tr 都當成自己的列(巢狀表格的列因此併進外層)、 完全不讀 colspan/rowspan(跨欄跨列的儲存格讓整列往左錯位)、並一律以第一列當表頭。 錯位的表格接著被 source_facts 當成參數表吃進來源事實,而假事實在 fail-closed 閘門下 會擋掉正確的擷取——operator 看到的是「來源明明寫了、擷取卻空著」這種完全誤導的訊息。 改法:只收自己的列(thead 優先、巢狀表格留給自己)、把 colspan/rowspan 展開成矩形 格網(重複 HTML 說它佔住的那些位置,留白會丟掉標示欄位歸屬的群組標籤)、巢狀表格 另render成自己的區塊而不併進外層,外層儲存格也不再把它的文字吞成跑馬燈。 超出範圍或非數字的 span 一律當 1,不夾到上限——colspan="9999" 是壞掉的文件, 把它當成「我們允許的最寬」等於生出一張沒人寫過的表。 以真實來源驗證:tappay 那張含三層巢狀與 rowspan=3 的表,修改前巢狀列混進外層, 修改後三個區塊各自欄數一致(14/12/7 列,4/5/6 欄)。
… source facts code review 找到一個 CRITICAL,而且它踩的正是這張票自己的否決項。 把跨欄儲存格的文字重複填進它蓋到的每一欄,會毀掉下游用來認出分組標題列的訊號 (source_facts/markdown.py:367 判「其餘欄位全空」)。實測 `<td colspan='3'>Header</td>` 在修改前後:main 得到 ['api_key'],上一版分支得到 ['Header', 'api_key'],並宣稱有個 型別是 Header、必要是 Header 的參數——來源根本沒寫的欄位變成來源事實,而假事實在 fail-closed 閘門下會擋掉正確的擷取。同一個機制也讓帶分組列的錯誤碼表整張作廢, 記載下界靜靜歸零(['1001'] → [])。 改法:文字只留在自己的位置,跨到的欄位補空;rowspan 例外,沿自己的欄往下重複—— 那裡帶下來的是每一列都成立的真值。對齊由格網幾何提供,不需要靠複製文字。 其餘 review 項目一併修掉:未閉合 </tr> 會讓後續每一列變成上一列的子節點,原本整個 表身消失,改為往 tr 內遞迴;重疊的 span 不再靜靜覆蓋,整張表放棄(與錯誤碼表「一列 壞掉作廢整張」同一偏誤);寫在 tbody 之前的 tfoot 排到最後;多列 thead 併成 GFM 唯一 允許的那一列表頭,不再降級成資料列。 順手把 own_rows / nested_tables / span / table_grid 提到模組層級(html_to_markdown 不再是一個 250 行的閉包),註解語言統一為英文,並同步 AGENTS.md 與正規化 spec。
|
Code review 的 CRITICAL 成立,而且踩的正是這張票自己的否決項。已修正並推上 CRITICAL — 跨欄重複填字會生出假參數。 我原本把跨欄儲存格的文字重複填進它蓋到的每一欄,那會毀掉下游認分組標題列的訊號( HIGH — 錯誤碼表被靜靜作廢。 同一個機制:帶 HIGH — 未閉合 MEDIUM 全部採納:重疊 span 不再靜靜覆蓋,整張表放棄(與錯誤碼表「一列壞掉作廢整張」同一偏誤);寫在 LOW 全部採納:新增七個回歸測試,其中兩個直接組合
|
Closes #81 —— #73 盤點裡排名第二的短板。
問題
html_snapshot.py的render_table有四個已驗證的缺口:_walk(table)收集整棵子樹的每個tr(巢狀表格的列併進外層)、colspan/rowspan屬性從未被讀取(跨欄跨列的儲存格讓整列往左錯位)、header, *body = rows一律以第一列當表頭、短列以空字串補齊而不視為錯誤。錯位的表格接著被
source_facts/markdown.py當成參數表吃進來源事實。假事實比漏掉事實貴:語意完整性閘門是 fail-closed 的,一筆來源根本沒寫的欄位會擋掉正確的擷取,而 operator 讀到的訊息是「來源明明寫了、擷取卻空著」——完全誤導。改法
own_rows遇到巢狀table就不往下走,thead的列排在前面(來源把tbody寫在thead之前也照樣正確)。colspan/rowspan成矩形格網,重複 HTML 說該儲存格佔住的那些位置。留白會丟掉「這幾列屬於哪個群組」的標籤,而那正是巢狀欄位的歸屬資訊。SubTypephoneString這種跑馬燈。colspan="9999"是壞掉的文件,把它當成「我們允許的最寬」等於生出一張沒人寫過的表。真實來源驗證
benchmarks/tappay-backend那張含三層巢狀與rowspan="3"的表,修改前巢狀列混進外層、欄數不一;修改後三個區塊各自欄數內部一致(14 列 3 欄 / 12 列 4 欄 / 7 列 5 欄)。範圍說明
benchmark 期望值未變動:各 case 的
sources/是已經正規化過的 Markdown,這次改的是產生它們的那一步,不會回頭重跑。既有 benchmark 全綠,代表沒有非預期的連帶影響。日後以normalize-html-snapshot重新產生來源時,受影響 case 的來源事實數會改變,屆時照實更新。版本 metadata 與 release notes 依慣例留給獨立的 release 準備 commit(0.36.0 剛發布)。
Test plan
tests/test_html_snapshot_tables.py(9 個案例,seam 是公開純函式html_to_markdown)uv run pytest—— 2184 passeduv run ruff check ./npm run docs:check