From f4707a0a3b928d7c6c3341a44ef708cde8efdcc3 Mon Sep 17 00:00:00 2001 From: lllleolin-max <244385774+lllleolin-max@users.noreply.github.com> Date: Sat, 12 Sep 2026 15:30:27 +0800 Subject: [PATCH] fix: preserve literal HTML in Markdown table exports Escape HTML-sensitive characters in table cell text and verify DOM-to-Markdown round trips. Assisted-by: OpenAI GPT-6 --- .../preserve-markdown-table-literals.md | 5 +++++ .../streamdown/__tests__/table-utils.test.ts | 21 +++++++++++++++++++ packages/streamdown/lib/table/utils.ts | 15 ++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .changeset/preserve-markdown-table-literals.md diff --git a/.changeset/preserve-markdown-table-literals.md b/.changeset/preserve-markdown-table-literals.md new file mode 100644 index 00000000..73ce3003 --- /dev/null +++ b/.changeset/preserve-markdown-table-literals.md @@ -0,0 +1,5 @@ +--- +"streamdown": patch +--- + +Preserve literal HTML tags and character entities when copying or downloading tables as Markdown. diff --git a/packages/streamdown/__tests__/table-utils.test.ts b/packages/streamdown/__tests__/table-utils.test.ts index b0c1e454..e82ac0aa 100644 --- a/packages/streamdown/__tests__/table-utils.test.ts +++ b/packages/streamdown/__tests__/table-utils.test.ts @@ -1,3 +1,4 @@ +import { marked } from "marked"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { escapeMarkdownTableCell, @@ -481,6 +482,26 @@ describe("Table Utils", () => { }); describe("tableDataToMarkdown", () => { + it("should preserve literal HTML and entities when rendered again", () => { + const table = document.createElement("table"); + table.innerHTML = ` + Type <T>Entities + + Array<string> + &copy; and &#124; + + `; + const data = extractTableDataFromElement(table); + const markdown = tableDataToMarkdown(data); + const container = document.createElement("div"); + container.innerHTML = marked.parse(markdown, { async: false }); + const restoredTable = container.querySelector( + "table" + ) as HTMLTableElement; + + expect(extractTableDataFromElement(restoredTable)).toEqual(data); + }); + it("should convert simple table data to Markdown", () => { const data: TableData = { headers: ["Name", "Age", "City"], diff --git a/packages/streamdown/lib/table/utils.ts b/packages/streamdown/lib/table/utils.ts index aece0df5..05475940 100644 --- a/packages/streamdown/lib/table/utils.ts +++ b/packages/streamdown/lib/table/utils.ts @@ -184,7 +184,14 @@ export const escapeMarkdownTableCell = (cell: string): string => { // OPTIMIZATION: Fast path for cells that don't need escaping - check chars directly let needsEscaping = false; for (const char of cell) { - if (char === "\\" || char === "|" || char === "\n") { + if ( + char === "\\" || + char === "|" || + char === "\n" || + char === "&" || + char === "<" || + char === ">" + ) { needsEscaping = true; break; } @@ -202,6 +209,12 @@ export const escapeMarkdownTableCell = (cell: string): string => { parts.push("\\|"); } else if (char === "\n") { parts.push("
"); + } else if (char === "&") { + parts.push("&"); + } else if (char === "<") { + parts.push("<"); + } else if (char === ">") { + parts.push(">"); } else { parts.push(char); }