在 JavaScript 中將 CSV 轉換為 HTML 表格的可靠做法,是把以逗號分隔的文字貼到瀏覽器型的轉換器、切換第一列是否為標頭,然後把已跳脫的表格片段複製到你的目的頁面。這個簡單的工作流程依賴三件事,而大多數自行撰寫的腳本都會在這三件事上出錯:一個真正懂得引號欄位與雙引號跳脫的 CSV 解析器、為每個儲存格做 HTML 跳脫以避免 CSV 值變成作用中的元素,以及明確的預算限制以便在出錯時顯示錯誤,而不是悄悄截斷輸出。CSV to HTML Table Converter 在用戶端完成所有這些處理,所以你的資料從不離開目前的分頁,並產生一個你可以貼到任何 HTML 內容結構中的 thead-and-tbody 片段。對於已經在使用 JavaScript 的開發者來說,這個做法取代了脆弱的字串切割邏輯,也避免了用 dangerouslySetInnerHTML 注入所產生 HTML 的誘惑。

convert csv to html table javascript
在 JavaScript 中將 CSV 轉換為 HTML 表格:安全的作業流程

為什麼用 split-on-comma 在多數 JavaScript 嘗試中會失敗

最簡單的 JavaScript CSV 處理方式——對每一行呼叫 split(',')——在欄位中含有逗號時立刻就會壞掉。試算表匯出的檔案會把那些欄位包在雙引號中、把字面的雙引號加倍為 "",甚至可能在引號欄位內嵌入換行。一段天真地用 \n 再用 ,切割的迴圈,會產生多餘的欄、錯亂的標頭,以及下游極難除錯的隱性欄位移。

這些陷阱在各家部落格文章與 Stack Overflow 回答中一再出現:以 \n 切割行會忽略 CRLF,所以從 Windows 匯出的檔案看起來會少一行;在引號欄位內以逗號切割會產生幽靈欄位與錯位的紀錄;像 She said ""hi""" 這種欄位內的雙引號會視實作為準塌成空字串或單一字元;而含有 <、> 或 & 的儲存格會被當成標記寫入 DOM,如果來源不可信,這就是 XSS 漏洞。CSV to HTML Table Converter 透過有限狀態解析器與刻意收斂的 RFC 4180 風格方言,按設計就處理了每一個這類邊緣情況。如果你打算自己寫解析器,WHATWG HTML tables spec 定義了輸出結構,而 IETF 的 RFC 4180 文件定義了輸入文法;這個轉換器在單一次處理中同時套用了兩者。

CSV to HTML Table Converter 如何解析 CSV

這個工具採用刻意收斂的 RFC 4180 風格方言。規則集短到可以摘要,重點在於解析器拒絕猜測。逗號是唯一的分隔符,分號、tab 與直立線維持為一般儲存格字元;CRLF 是正式的紀錄結尾,而獨立的 LF 與 CR 作為紀錄在案的擴充被接受,以容納真實世界的產生者;最後一個紀錄結尾可有可無,所以不會因為編輯器沒加上結尾就產生幽靈列。引號欄位可有可無,且可以包含逗號與紀錄結尾;引號欄位內的兩個雙引號解碼為一個字面的雙引號;未引號欄位中間出現的雙引號會被拒絕;關閉引號之後只允許出現逗號、紀錄結尾或輸入結束。每一筆紀錄的欄位數必須與第一筆完全相同,中間與尾端的空白儲存格會被保留,不會對任何儲存格文字做修剪,而一個前置的 U+FEFF 字元會被視為 UTF-8 BOM 標記在解析前移除。Library of Congress 對 CSV 的格式描述 指出真實世界的產生者偶爾會破壞嚴格的 RFC 4180 文法;轉換器有紀錄的 LF/CR 接受規則反映這個事實,卻不犧牲可預測性。

文法規則行為
分隔符僅限逗號;分號、tab 與直立線視為一般儲存格字元
紀錄結尾CRLF 為正式;獨立的 LF 與 CR 作為紀錄在案的擴充被接受
引號欄位可有可無,以雙引號包起來;可包含逗號與紀錄結尾
雙引號引號欄位內的 "" 解碼為一個字面的 "
欄位中段的引號未引號欄位中出現的雙引號會被拒絕
關閉引號之後只允許出現逗號、紀錄結尾或輸入結束
欄位寬度每筆紀錄的欄數必須與第一筆相同
BOM一個前置的 U+FEFF 在解析前被移除;其餘保留為資料
儲存格修剪不修剪;儲存格文字逐字保留

在 JavaScript 中將 CSV 轉換為 HTML 表格:逐步作法

  1. 在瀏覽器中開啟 CSV to HTML Table Converter,把以逗號分隔的文字貼到輸入區。
  2. 決定第一筆紀錄是否包含欄標題,並據此切換標頭選項。
  3. 執行轉換步驟並讀取回報的列數與欄數;在複製任何內容之前,與來源比對以便提早發現任何紀錄寬度不符。
  4. 把頁面上的預覽與所產生的原始碼並列檢查;預覽透過 React 文位元組點渲染,因此任何看起來像 HTML 的內容都會以字面呈現,而不是被執行。
  5. 把已跳脫的表格片段複製到剪貼簿,再貼到你的 HTML 頁面、CMS 範本或元件檔案中。
  6. 加上 <caption>、為標頭儲存格設定 scope 屬性,並套用可自適應的 CSS,讓表格在狹窄的視窗下仍可用。
  7. 在具代表性的行動裝置斷點下並搭配輔助科技測試結果,因為語法上有效的表格若過寬或單獨依賴顏色,仍可能無法無障礙使用。

安全的 React 預覽內部

頁面上的預覽是這個工具中最重要的安全細節。儲存格會針對 HTML 文字內容做跳脫,所以 &、角括號、引號與撇號在任何標記組合之前都會先成為實體,而預覽本身是透過 React 文位元組點渲染,而不是透過 dangerouslySetInnerHTML 注入所產生的 HTML。兩層保護彼此獨立:跳脫消除了那些會變成作用中 HTML 的標記,而 React 文位元組點渲染則保證轉換器永遠不會請瀏覽器把組合出來的字串當成活的 DOM 來解讀。正是這個組合,讓你可以貼上一個內容是 <img src=x onerror=alert(1)> 的 CSV 儲存格,看它在預覽中以一般文字呈現,而不會觸發任何指令碼。

這個區隔在下游也很重要。複製片段之後,目的端應用程式負責把它放進 HTML 內容結構中,並維持自己的內容安全政策。WHATWG HTML tables spec 描述了正式的表格元素;你仍然應把複製來的片段包進一個尊重你所強制之 nonce 或 hash 需求的結構中。

限制、邊界與被拒絕的輸入

轉換器會強制執行明確的預算,並在輸入超出範圍時以清楚的錯誤訊息拒絕,而不是產生局部或被截斷的表格。越過任何邊界都會回傳錯誤,不會產生局部或悄悄截斷的表格。幾種格式錯誤的狀況也會在一開始就被拒絕:未引號欄位中段出現的雙引號、關閉引號之後出現的不是逗號或紀錄結尾的內容、未封閉的引號,以及任何欄位數與第一筆不同的紀錄。最後一個紀錄結尾可有可無,不會因此產生幽靈列。這種拒絕行為是這個工具在用戶端資料上值得信賴的原因:出錯時你拿到的不是半完成的表格,而是可在來源端修正的可行動錯誤訊息。

邊界
輸入字元數500,000
總列數10,000
欄數200
總儲存格數200,000
輸出字元數5,000,000

輸出是片段,不是完成的頁面

複製來的標記刻意保持精簡。它只包含 table、thead、tbody、tr、th 與 td 這些元素,沒有其他東西。沒有 doctype、沒有 html 或 head 或 body 的外殼、沒有 caption、沒有 CSS、沒有 JavaScript、沒有 ARIA 描述、沒有排序、沒有篩選、沒有分頁、也沒有自適應的外包。預期由你在目的端明確地補上這些。

在 React 專案中一個合理的模式,是把片段包進一個 figure,搭配一個為輔助科技命名該表格的 figcaption,然後在標頭儲存格設定 scope 屬性,讓螢幕閱讀器能把每個資料儲存格對應到所屬的欄。一個簡單的模式是 figure、figcaption、table,然後把複製來的片段放進 table 元素之中。一旦開始組合周圍的頁面,你可能也想切換到並排的 HTML 編輯器;這能避免你不小心重新引入轉換器所要避免的危險注入模式。對於表格元素本身,WHATWG HTML tables spec 是在現行瀏覽器中 thead、tbody、th 與 td 上哪些屬性有效之權威參考。

手寫的 JavaScript 做法與轉換器的比較

針對同樣的問題,手寫的 JavaScript 做法差異在於實際完成了多少安全工作。line.split(',') 迴圈寫起來很短,但會在每個被引號包起來的逗號上失敗、不做 HTML 跳脫、不顯示解析錯誤、也沒有輸入預算。在同樣的 split 之外手動加上引號處理可以救回欄位中有逗號的情況,但仍通常略過跳脫,而且仍然沒有預算。手寫的有限狀態解析器可以正確處理所有這些,但前提是你記得為每個儲存格跳脫、寫出錯誤處理流程,並自己決定大小限制。CSV to HTML Table Converter 為你提供了解析器、跳脫、預算與 React 文位元組點預覽,而不必你自己寫程式碼。如果你需要 CI 管線用的命令列替代方案,同樣的 CSV 轉 HTML 問題在跳脫 JavaScript 並改用具備內建的 shell 工具時會有不同的樣貌,如 CSV to HTML Table in Shell: Why It Breaks and a Safer Way 所述。

做法處理被引號的逗號跳脫儲存格 HTML顯示解析錯誤有明確預算
line.split(',') 迴圈
split 加上手動引號處理部分通常否
手寫的有限狀態解析器若你記得的話不一定不一定
CSV to HTML Table Converter是,每個儲存格是(500k 字元、10k 列、200 欄、200k 儲存格、5M 字元)

相關閱讀:How to Export a Table in Excel to CSV Without Breaking It