markdown-codec 6.4.3 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +20 -7
  2. package/dist/block/block.cjs +1 -1
  3. package/dist/block/block.d.cts +1 -1
  4. package/dist/block/block.d.ts +1 -1
  5. package/dist/block/block.js +1 -1
  6. package/dist/block/definitions.d.cts +1 -1
  7. package/dist/block/definitions.d.ts +1 -1
  8. package/dist/diagnostics/diagnostics.cjs +2 -1
  9. package/dist/diagnostics/diagnostics.d.cts +1 -1
  10. package/dist/diagnostics/diagnostics.d.ts +1 -1
  11. package/dist/diagnostics/diagnostics.js +2 -1
  12. package/dist/{diagnostics-eL5c8OWm.d.cts → diagnostics-DLum7kY6.d.cts} +1 -0
  13. package/dist/{diagnostics-eL5c8OWm.d.ts → diagnostics-DLum7kY6.d.ts} +1 -0
  14. package/dist/emit/emit.cjs +28 -5
  15. package/dist/emit/emit.js +28 -5
  16. package/dist/emit/front-matter.cjs +22 -5
  17. package/dist/emit/front-matter.js +22 -5
  18. package/dist/emit/html-table.cjs +104 -0
  19. package/dist/emit/html-table.d.cts +7 -0
  20. package/dist/emit/html-table.d.ts +7 -0
  21. package/dist/emit/html-table.js +102 -0
  22. package/dist/emit/inline.cjs +30 -6
  23. package/dist/emit/inline.d.cts +1 -13
  24. package/dist/emit/inline.d.ts +1 -13
  25. package/dist/emit/inline.js +30 -6
  26. package/dist/emit/table.cjs +10 -13
  27. package/dist/emit/table.d.cts +2 -2
  28. package/dist/emit/table.d.ts +2 -2
  29. package/dist/emit/table.js +10 -13
  30. package/dist/html/html-table.cjs +243 -0
  31. package/dist/html/html-table.d.cts +5 -0
  32. package/dist/html/html-table.d.ts +5 -0
  33. package/dist/html/html-table.js +242 -0
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.d.cts +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/inline-2-lUb0vo.d.ts +14 -0
  39. package/dist/inline-514HW3Pi.d.cts +14 -0
  40. package/dist/lower/front-matter.cjs +64 -20
  41. package/dist/lower/front-matter.d.cts +1 -1
  42. package/dist/lower/front-matter.d.ts +1 -1
  43. package/dist/lower/front-matter.js +64 -20
  44. package/dist/lower/inline.d.cts +1 -1
  45. package/dist/lower/inline.d.ts +1 -1
  46. package/dist/lower/lower.cjs +16 -5
  47. package/dist/lower/lower.js +16 -5
  48. package/dist/options/options.d.cts +1 -1
  49. package/dist/options/options.d.ts +1 -1
  50. package/dist/read.d.cts +1 -1
  51. package/dist/read.d.ts +1 -1
  52. package/package.json +1 -1
@@ -0,0 +1,102 @@
1
+ import { MarkdownDiagnosticCodes } from "../diagnostics/diagnostics.js";
2
+ import "../shared/style-constants.js";
3
+ import { colorToRgbHex } from "document-schema.js";
4
+ //#region src/emit/html-table.ts
5
+ function escapeHtmlText(text) {
6
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
7
+ }
8
+ function escapeHtmlAttribute(text) {
9
+ return escapeHtmlText(text).replaceAll("\"", "&quot;");
10
+ }
11
+ function blockNeedsHtmlFallback(block) {
12
+ return block.kind !== "paragraph" && block.kind !== "image";
13
+ }
14
+ function tableNeedsHtmlFallback(table) {
15
+ return table.rows.some((row) => row.cells.some((cell) => cell.colSpan !== void 0 || cell.rowSpan !== void 0 || cell.background !== void 0 || cell.blocks.some(blockNeedsHtmlFallback)));
16
+ }
17
+ function emitRunHtml(run) {
18
+ let text = escapeHtmlText(run.text);
19
+ if (run.fontFamily === "Courier New") text = `<code>${text}</code>`;
20
+ if (run.strike === true) text = `<del>${text}</del>`;
21
+ if (run.italic === true) text = `<em>${text}</em>`;
22
+ if (run.bold === true) text = `<strong>${text}</strong>`;
23
+ if (run.hyperlink !== void 0) text = `<a href="${escapeHtmlAttribute(run.hyperlink)}">${text}</a>`;
24
+ return text;
25
+ }
26
+ function emitHtmlImage(block, embedImages) {
27
+ const alt = escapeHtmlAttribute(block.altText ?? "");
28
+ if (!embedImages) return `<img alt="${alt}">`;
29
+ return `<img src="data:image/${block.format};base64,${block.base64}" alt="${alt}">`;
30
+ }
31
+ function backgroundStyleAttr(fill, sink) {
32
+ if (fill.kind === "solid") return `background-color:#${colorToRgbHex(fill.color)}`;
33
+ sink({
34
+ code: MarkdownDiagnosticCodes.TABLE_CELL_FORMATTING_DROPPED,
35
+ severity: "info",
36
+ message: "a table cell's own pattern background fill has no CSS equivalent this package's bounded HTML-table writer attempts (only a solid fill maps onto a plain background-color); the cell still renders as an ordinary unstyled cell"
37
+ });
38
+ }
39
+ function textAlignStyleAttr(cell) {
40
+ const first = cell.blocks[0];
41
+ if (first?.kind !== "paragraph" || first.alignment === void 0) return;
42
+ return `text-align:${first.alignment}`;
43
+ }
44
+ function emitCellHtml(cell, context) {
45
+ const soleBlock = cell.blocks.length === 1 ? cell.blocks[0] : void 0;
46
+ if (soleBlock?.kind === "table") return emitHtmlTable(soleBlock, context);
47
+ if (cell.blocks.length > 1) context.sink({
48
+ code: MarkdownDiagnosticCodes.TABLE_CELL_MULTI_PARAGRAPH_JOINED,
49
+ severity: "info",
50
+ message: `a table cell with ${String(cell.blocks.length)} blocks has no multi-block equivalent in an HTML table cell either; their own rendered content is joined with a literal <br> line break`
51
+ });
52
+ const parts = [];
53
+ for (const block of cell.blocks) {
54
+ if (block.kind === "paragraph") {
55
+ parts.push(block.runs.map(emitRunHtml).join(""));
56
+ continue;
57
+ }
58
+ if (block.kind === "image") {
59
+ parts.push(emitHtmlImage(block, context.embedImages));
60
+ continue;
61
+ }
62
+ if (block.kind === "table") {
63
+ context.sink({
64
+ code: MarkdownDiagnosticCodes.TABLE_CELL_FORMATTING_DROPPED,
65
+ severity: "info",
66
+ message: "a nested table mixed with other content in the same cell has no HTML representation this package attempts (a cell's own nested table must be its entire content); it is dropped, the cell's other content still renders"
67
+ });
68
+ continue;
69
+ }
70
+ context.sink({
71
+ code: MarkdownDiagnosticCodes.TABLE_CELL_FORMATTING_DROPPED,
72
+ severity: "info",
73
+ message: `a table cell containing a "${block.kind}" block has no HTML-table equivalent this package attempts; it is dropped entirely`
74
+ });
75
+ }
76
+ return parts.join("<br>");
77
+ }
78
+ function emitCellTag(cell, isHeader, context) {
79
+ const tag = isHeader ? "th" : "td";
80
+ const attrs = [];
81
+ if (cell.colSpan !== void 0) attrs.push(`colspan="${String(cell.colSpan)}"`);
82
+ if (cell.rowSpan !== void 0) attrs.push(`rowspan="${String(cell.rowSpan)}"`);
83
+ const styleParts = [];
84
+ if (cell.background !== void 0) {
85
+ const style = backgroundStyleAttr(cell.background, context.sink);
86
+ if (style !== void 0) styleParts.push(style);
87
+ }
88
+ const textAlign = textAlignStyleAttr(cell);
89
+ if (textAlign !== void 0) styleParts.push(textAlign);
90
+ if (styleParts.length > 0) attrs.push(`style="${escapeHtmlAttribute(styleParts.join(";"))}"`);
91
+ return `<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${emitCellHtml(cell, context)}</${tag}>`;
92
+ }
93
+ function emitHtmlTable(table, context) {
94
+ const [header, ...body] = table.rows;
95
+ const lines = ["<table>"];
96
+ if (header !== void 0) lines.push(`<tr>${header.cells.map((cell) => emitCellTag(cell, true, context)).join("")}</tr>`);
97
+ for (const row of body) lines.push(`<tr>${row.cells.map((cell) => emitCellTag(cell, false, context)).join("")}</tr>`);
98
+ lines.push("</table>");
99
+ return lines.join("\n");
100
+ }
101
+ //#endregion
102
+ export { emitHtmlTable, tableNeedsHtmlFallback };
@@ -121,9 +121,33 @@ function wrapForStyle(body, key, context, precedingText) {
121
121
  const delimiter = key === "bold" ? marker.repeat(2) : marker;
122
122
  return `${delimiter}${body}${delimiter}`;
123
123
  }
124
- function renderNestedStyles(runs, depth, context, base, constructs) {
125
- if (depth >= STYLE_KEYS.length) return runs.map((run, local) => renderLeaf(run, context, base + local, constructs)).join("");
126
- const key = STYLE_KEYS[depth];
124
+ function groupCount(runs, key) {
125
+ let count = 0;
126
+ let index = 0;
127
+ while (index < runs.length) {
128
+ const active = styleActive(runs[index], key);
129
+ count += 1;
130
+ index += 1;
131
+ while (index < runs.length && styleActive(runs[index], key) === active) index += 1;
132
+ }
133
+ return count;
134
+ }
135
+ function pickSplitKey(runs, remaining) {
136
+ let best = remaining[0];
137
+ let bestCount = groupCount(runs, best);
138
+ for (const key of remaining.slice(1)) {
139
+ const count = groupCount(runs, key);
140
+ if (count < bestCount) {
141
+ best = key;
142
+ bestCount = count;
143
+ }
144
+ }
145
+ return best;
146
+ }
147
+ function renderNestedStyles(runs, remainingKeys, context, base, constructs) {
148
+ if (remainingKeys.length === 0) return runs.map((run, local) => renderLeaf(run, context, base + local, constructs)).join("");
149
+ const key = pickSplitKey(runs, remainingKeys);
150
+ const rest = remainingKeys.filter((candidate) => candidate !== key);
127
151
  let out = "";
128
152
  let index = 0;
129
153
  while (index < runs.length) {
@@ -132,7 +156,7 @@ function renderNestedStyles(runs, depth, context, base, constructs) {
132
156
  const active = styleActive(current, key);
133
157
  let end = index + 1;
134
158
  while (end < runs.length && styleActive(runs[end], key) === active) end += 1;
135
- const inner = renderNestedStyles(runs.slice(index, end), depth + 1, context, base + index, constructs);
159
+ const inner = renderNestedStyles(runs.slice(index, end), rest, context, base + index, constructs);
136
160
  out += active ? wrapForStyle(inner, key, context, out) : inner;
137
161
  index = end;
138
162
  }
@@ -168,7 +192,7 @@ function emitRuns(runs, context, constructs) {
168
192
  if (run.hyperlink === void 0) {
169
193
  let end = index + 1;
170
194
  while (end < runs.length && runs[end]?.hyperlink === void 0) end += 1;
171
- out += renderNestedStyles(runs.slice(index, end), 0, context, index, constructs);
195
+ out += renderNestedStyles(runs.slice(index, end), STYLE_KEYS, context, index, constructs);
172
196
  index = end;
173
197
  continue;
174
198
  }
@@ -184,7 +208,7 @@ function emitRuns(runs, context, constructs) {
184
208
  const title = linkTitleCovering(constructs, index, groupEnd);
185
209
  if (title === void 0 && group.length === 1 && isPlainAutolink(group[0], footnoteReferenceAt(constructs, index))) out += `<${group[0].text}>`;
186
210
  else {
187
- const linkText = renderNestedStyles(group, 0, context, index, constructs);
211
+ const linkText = renderNestedStyles(group, STYLE_KEYS, context, index, constructs);
188
212
  out += `[${linkText}](${escapeLinkDestination(hyperlink)}${title === void 0 ? "" : ` "${renderLinkTitle(title)}"`})`;
189
213
  }
190
214
  index = groupEnd;
@@ -1,14 +1,2 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.cjs";
2
- import { ContentRun, RunConstructExtent } from "document-schema.js";
3
- //#region src/emit/inline.d.ts
4
- interface InlineEmitContext {
5
- readonly sink: MarkdownDiagnosticSink;
6
- readonly emphasisMarker: string;
7
- }
8
- declare function escapeMarkdownText(text: string): string;
9
- declare function escapeLinkDestination(destination: string): string;
10
- declare function renderLinkTitle(title: string): string;
11
- declare function emitRuns(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[]): string;
12
- declare function emitRunsSingleLine(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[]): string;
13
- //#endregion
1
+ import { a as escapeMarkdownText, i as escapeLinkDestination, n as emitRuns, o as renderLinkTitle, r as emitRunsSingleLine, t as InlineEmitContext } from "../inline-514HW3Pi.cjs";
14
2
  export { InlineEmitContext, emitRuns, emitRunsSingleLine, escapeLinkDestination, escapeMarkdownText, renderLinkTitle };
@@ -1,14 +1,2 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.js";
2
- import { ContentRun, RunConstructExtent } from "document-schema.js";
3
- //#region src/emit/inline.d.ts
4
- interface InlineEmitContext {
5
- readonly sink: MarkdownDiagnosticSink;
6
- readonly emphasisMarker: string;
7
- }
8
- declare function escapeMarkdownText(text: string): string;
9
- declare function escapeLinkDestination(destination: string): string;
10
- declare function renderLinkTitle(title: string): string;
11
- declare function emitRuns(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[]): string;
12
- declare function emitRunsSingleLine(runs: readonly ContentRun[], context: InlineEmitContext, constructs?: readonly RunConstructExtent[]): string;
13
- //#endregion
1
+ import { a as escapeMarkdownText, i as escapeLinkDestination, n as emitRuns, o as renderLinkTitle, r as emitRunsSingleLine, t as InlineEmitContext } from "../inline-2-lUb0vo.js";
14
2
  export { InlineEmitContext, emitRuns, emitRunsSingleLine, escapeLinkDestination, escapeMarkdownText, renderLinkTitle };
@@ -120,9 +120,33 @@ function wrapForStyle(body, key, context, precedingText) {
120
120
  const delimiter = key === "bold" ? marker.repeat(2) : marker;
121
121
  return `${delimiter}${body}${delimiter}`;
122
122
  }
123
- function renderNestedStyles(runs, depth, context, base, constructs) {
124
- if (depth >= STYLE_KEYS.length) return runs.map((run, local) => renderLeaf(run, context, base + local, constructs)).join("");
125
- const key = STYLE_KEYS[depth];
123
+ function groupCount(runs, key) {
124
+ let count = 0;
125
+ let index = 0;
126
+ while (index < runs.length) {
127
+ const active = styleActive(runs[index], key);
128
+ count += 1;
129
+ index += 1;
130
+ while (index < runs.length && styleActive(runs[index], key) === active) index += 1;
131
+ }
132
+ return count;
133
+ }
134
+ function pickSplitKey(runs, remaining) {
135
+ let best = remaining[0];
136
+ let bestCount = groupCount(runs, best);
137
+ for (const key of remaining.slice(1)) {
138
+ const count = groupCount(runs, key);
139
+ if (count < bestCount) {
140
+ best = key;
141
+ bestCount = count;
142
+ }
143
+ }
144
+ return best;
145
+ }
146
+ function renderNestedStyles(runs, remainingKeys, context, base, constructs) {
147
+ if (remainingKeys.length === 0) return runs.map((run, local) => renderLeaf(run, context, base + local, constructs)).join("");
148
+ const key = pickSplitKey(runs, remainingKeys);
149
+ const rest = remainingKeys.filter((candidate) => candidate !== key);
126
150
  let out = "";
127
151
  let index = 0;
128
152
  while (index < runs.length) {
@@ -131,7 +155,7 @@ function renderNestedStyles(runs, depth, context, base, constructs) {
131
155
  const active = styleActive(current, key);
132
156
  let end = index + 1;
133
157
  while (end < runs.length && styleActive(runs[end], key) === active) end += 1;
134
- const inner = renderNestedStyles(runs.slice(index, end), depth + 1, context, base + index, constructs);
158
+ const inner = renderNestedStyles(runs.slice(index, end), rest, context, base + index, constructs);
135
159
  out += active ? wrapForStyle(inner, key, context, out) : inner;
136
160
  index = end;
137
161
  }
@@ -167,7 +191,7 @@ function emitRuns(runs, context, constructs) {
167
191
  if (run.hyperlink === void 0) {
168
192
  let end = index + 1;
169
193
  while (end < runs.length && runs[end]?.hyperlink === void 0) end += 1;
170
- out += renderNestedStyles(runs.slice(index, end), 0, context, index, constructs);
194
+ out += renderNestedStyles(runs.slice(index, end), STYLE_KEYS, context, index, constructs);
171
195
  index = end;
172
196
  continue;
173
197
  }
@@ -183,7 +207,7 @@ function emitRuns(runs, context, constructs) {
183
207
  const title = linkTitleCovering(constructs, index, groupEnd);
184
208
  if (title === void 0 && group.length === 1 && isPlainAutolink(group[0], footnoteReferenceAt(constructs, index))) out += `<${group[0].text}>`;
185
209
  else {
186
- const linkText = renderNestedStyles(group, 0, context, index, constructs);
210
+ const linkText = renderNestedStyles(group, STYLE_KEYS, context, index, constructs);
187
211
  out += `[${linkText}](${escapeLinkDestination(hyperlink)}${title === void 0 ? "" : ` "${renderLinkTitle(title)}"`})`;
188
212
  }
189
213
  index = groupEnd;
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_diagnostics_diagnostics = require("../diagnostics/diagnostics.cjs");
3
3
  const require_emit_inline = require("./inline.cjs");
4
4
  const require_emit_image = require("./image.cjs");
5
+ const require_emit_html_table = require("./html-table.cjs");
5
6
  //#region src/emit/table.ts
6
7
  function toMarkdownAlignment(alignment) {
7
8
  return alignment === void 0 || alignment === "justify" ? "none" : alignment;
@@ -35,11 +36,6 @@ function escapeUnescapedPipes(text) {
35
36
  return out;
36
37
  }
37
38
  function renderCellText(cell, context) {
38
- if (cell.colSpan !== void 0 || cell.rowSpan !== void 0 || cell.background !== void 0) context.sink({
39
- code: require_diagnostics_diagnostics.MarkdownDiagnosticCodes.TABLE_CELL_FORMATTING_DROPPED,
40
- severity: "info",
41
- message: "a table cell's own colSpan/rowSpan/background has no GFM table equivalent; the cell renders as an ordinary unmerged, unstyled cell"
42
- });
43
39
  if (cell.blocks.length > 1) context.sink({
44
40
  code: require_diagnostics_diagnostics.MarkdownDiagnosticCodes.TABLE_CELL_MULTI_PARAGRAPH_JOINED,
45
41
  severity: "info",
@@ -56,20 +52,21 @@ function renderCellText(cell, context) {
56
52
  parts.push(require_emit_image.emitImage(block, context.embedImages));
57
53
  continue;
58
54
  }
59
- if (block.kind !== "paragraph") {
60
- context.sink({
61
- code: require_diagnostics_diagnostics.MarkdownDiagnosticCodes.TABLE_CELL_FORMATTING_DROPPED,
62
- severity: "info",
63
- message: `a table cell containing a "${block.kind}" block has no GFM table equivalent; it is dropped entirely`
64
- });
65
- continue;
66
- }
55
+ if (block.kind !== "paragraph") continue;
67
56
  const text = require_emit_inline.emitRunsSingleLine(block.runs, context, block.constructs);
68
57
  if (text.length > 0) parts.push(text);
69
58
  }
70
59
  return escapeUnescapedPipes(parts.join("<br>"));
71
60
  }
72
61
  function emitTable(table, context) {
62
+ if (require_emit_html_table.tableNeedsHtmlFallback(table)) {
63
+ context.sink({
64
+ code: require_diagnostics_diagnostics.MarkdownDiagnosticCodes.TABLE_HTML_FALLBACK,
65
+ severity: "info",
66
+ message: "a cell in this table needs colSpan/rowSpan/background, or holds a block a GFM table cell cannot represent at all (most commonly a nested table); GFM's own table extension holds inline content only (github.github.com/gfm, \"Tables (extension)\"), so no single cell can carry an HTML sub-block inside an otherwise pipe-syntax table -- the whole table is rendered as a raw HTML <table> block instead (CommonMark spec 0.31.2, HTML blocks condition 6, https://spec.commonmark.org/0.31.2/#html-blocks), which src/html/html-table.ts's own reader recognises back into an equal ContentTable"
67
+ });
68
+ return require_emit_html_table.emitHtmlTable(table, context);
69
+ }
73
70
  const [header, ...body] = table.rows;
74
71
  if (header === void 0) return "";
75
72
  const alignments = header.cells.map((cell) => toMarkdownAlignment(cell.blocks[0]?.kind === "paragraph" ? cell.blocks[0].alignment : void 0));
@@ -1,5 +1,5 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.cjs";
2
- import { InlineEmitContext } from "./inline.cjs";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-DLum7kY6.cjs";
2
+ import { t as InlineEmitContext } from "../inline-514HW3Pi.cjs";
3
3
  import { ContentTable } from "document-schema.js";
4
4
  //#region src/emit/table.d.ts
5
5
  interface TableEmitContext extends InlineEmitContext {
@@ -1,5 +1,5 @@
1
- import { i as MarkdownDiagnosticSink } from "../diagnostics-eL5c8OWm.js";
2
- import { InlineEmitContext } from "./inline.js";
1
+ import { i as MarkdownDiagnosticSink } from "../diagnostics-DLum7kY6.js";
2
+ import { t as InlineEmitContext } from "../inline-2-lUb0vo.js";
3
3
  import { ContentTable } from "document-schema.js";
4
4
  //#region src/emit/table.d.ts
5
5
  interface TableEmitContext extends InlineEmitContext {
@@ -1,6 +1,7 @@
1
1
  import { MarkdownDiagnosticCodes } from "../diagnostics/diagnostics.js";
2
2
  import { emitRunsSingleLine } from "./inline.js";
3
3
  import { emitImage } from "./image.js";
4
+ import { emitHtmlTable, tableNeedsHtmlFallback } from "./html-table.js";
4
5
  //#region src/emit/table.ts
5
6
  function toMarkdownAlignment(alignment) {
6
7
  return alignment === void 0 || alignment === "justify" ? "none" : alignment;
@@ -34,11 +35,6 @@ function escapeUnescapedPipes(text) {
34
35
  return out;
35
36
  }
36
37
  function renderCellText(cell, context) {
37
- if (cell.colSpan !== void 0 || cell.rowSpan !== void 0 || cell.background !== void 0) context.sink({
38
- code: MarkdownDiagnosticCodes.TABLE_CELL_FORMATTING_DROPPED,
39
- severity: "info",
40
- message: "a table cell's own colSpan/rowSpan/background has no GFM table equivalent; the cell renders as an ordinary unmerged, unstyled cell"
41
- });
42
38
  if (cell.blocks.length > 1) context.sink({
43
39
  code: MarkdownDiagnosticCodes.TABLE_CELL_MULTI_PARAGRAPH_JOINED,
44
40
  severity: "info",
@@ -55,20 +51,21 @@ function renderCellText(cell, context) {
55
51
  parts.push(emitImage(block, context.embedImages));
56
52
  continue;
57
53
  }
58
- if (block.kind !== "paragraph") {
59
- context.sink({
60
- code: MarkdownDiagnosticCodes.TABLE_CELL_FORMATTING_DROPPED,
61
- severity: "info",
62
- message: `a table cell containing a "${block.kind}" block has no GFM table equivalent; it is dropped entirely`
63
- });
64
- continue;
65
- }
54
+ if (block.kind !== "paragraph") continue;
66
55
  const text = emitRunsSingleLine(block.runs, context, block.constructs);
67
56
  if (text.length > 0) parts.push(text);
68
57
  }
69
58
  return escapeUnescapedPipes(parts.join("<br>"));
70
59
  }
71
60
  function emitTable(table, context) {
61
+ if (tableNeedsHtmlFallback(table)) {
62
+ context.sink({
63
+ code: MarkdownDiagnosticCodes.TABLE_HTML_FALLBACK,
64
+ severity: "info",
65
+ message: "a cell in this table needs colSpan/rowSpan/background, or holds a block a GFM table cell cannot represent at all (most commonly a nested table); GFM's own table extension holds inline content only (github.github.com/gfm, \"Tables (extension)\"), so no single cell can carry an HTML sub-block inside an otherwise pipe-syntax table -- the whole table is rendered as a raw HTML <table> block instead (CommonMark spec 0.31.2, HTML blocks condition 6, https://spec.commonmark.org/0.31.2/#html-blocks), which src/html/html-table.ts's own reader recognises back into an equal ContentTable"
66
+ });
67
+ return emitHtmlTable(table, context);
68
+ }
72
69
  const [header, ...body] = table.rows;
73
70
  if (header === void 0) return "";
74
71
  const alignments = header.cells.map((cell) => toMarkdownAlignment(cell.blocks[0]?.kind === "paragraph" ? cell.blocks[0].alignment : void 0));
@@ -0,0 +1,243 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_lower_image = require("../lower/image.cjs");
3
+ const require_shared_style_constants = require("../shared/style-constants.cjs");
4
+ let document_schema_js = require("document-schema.js");
5
+ //#region src/html/html-table.ts
6
+ function findBalancedClose(text, openEnd, tagNames) {
7
+ const names = tagNames.join("|");
8
+ const openPattern = new RegExp(`<(?:${names})(?=[\\s/>])`, "gi");
9
+ const closePattern = new RegExp(`</(?:${names})\\s*>`, "gi");
10
+ let depth = 1;
11
+ let pos = openEnd;
12
+ while (depth > 0) {
13
+ openPattern.lastIndex = pos;
14
+ closePattern.lastIndex = pos;
15
+ const openMatch = openPattern.exec(text);
16
+ const closeMatch = closePattern.exec(text);
17
+ if (closeMatch === null) return;
18
+ if (openMatch !== null && openMatch.index < closeMatch.index) {
19
+ depth += 1;
20
+ pos = openMatch.index + openMatch[0].length;
21
+ continue;
22
+ }
23
+ depth -= 1;
24
+ pos = closeMatch.index + closeMatch[0].length;
25
+ if (depth === 0) return {
26
+ start: closeMatch.index,
27
+ end: pos
28
+ };
29
+ }
30
+ }
31
+ function extractTopLevelElements(text, tagNames) {
32
+ const openPattern = new RegExp(`<(?:${tagNames.join("|")})\\b([^>]*)>`, "gi");
33
+ const elements = [];
34
+ let pos = 0;
35
+ for (;;) {
36
+ openPattern.lastIndex = pos;
37
+ const match = openPattern.exec(text);
38
+ if (match === null) break;
39
+ if (text.slice(pos, match.index).trim().length > 0) return;
40
+ const attrs = match[1] ?? "";
41
+ const openEnd = match.index + match[0].length;
42
+ const close = findBalancedClose(text, openEnd, tagNames);
43
+ if (close === void 0) return;
44
+ elements.push({
45
+ attrs,
46
+ inner: text.slice(openEnd, close.start)
47
+ });
48
+ pos = close.end;
49
+ }
50
+ if (text.slice(pos).trim().length > 0) return;
51
+ return elements;
52
+ }
53
+ function readAttr(attrs, name) {
54
+ return new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`, "i").exec(attrs)?.[1];
55
+ }
56
+ function readPositiveIntAttr(attrs, name) {
57
+ const raw = readAttr(attrs, name);
58
+ if (raw === void 0) return;
59
+ const value = Number.parseInt(raw, 10);
60
+ return Number.isFinite(value) && value > 0 ? value : void 0;
61
+ }
62
+ const BACKGROUND_COLOR_PATTERN = /background-color\s*:\s*#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/i;
63
+ function readBackgroundFill(attrs) {
64
+ const style = readAttr(attrs, "style");
65
+ if (style === void 0) return;
66
+ const hex = BACKGROUND_COLOR_PATTERN.exec(style)?.[1];
67
+ if (hex === void 0) return;
68
+ const normalised = hex.length === 3 ? hex.split("").map((digit) => digit + digit).join("") : hex;
69
+ return {
70
+ kind: "solid",
71
+ color: (0, document_schema_js.rgbHexToColor)(normalised)
72
+ };
73
+ }
74
+ const TEXT_ALIGN_PATTERN = /text-align\s*:\s*(left|right|center|justify)\b/i;
75
+ function readTextAlign(attrs) {
76
+ const style = readAttr(attrs, "style");
77
+ if (style === void 0) return;
78
+ switch (TEXT_ALIGN_PATTERN.exec(style)?.[1]?.toLowerCase()) {
79
+ case "left": return "left";
80
+ case "right": return "right";
81
+ case "center": return "center";
82
+ case "justify": return "justify";
83
+ default: return;
84
+ }
85
+ }
86
+ function unescapeHtml(text) {
87
+ return text.replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&quot;", "\"").replaceAll("&#39;", "'").replaceAll("&amp;", "&");
88
+ }
89
+ const INLINE_TAG_PATTERN = /<(a|strong|b|em|i|del|s|strike|code)\b([^>]*)>/i;
90
+ function applyTagStyle(style, tagName, attrs) {
91
+ switch (tagName) {
92
+ case "strong":
93
+ case "b": return {
94
+ ...style,
95
+ bold: true
96
+ };
97
+ case "em":
98
+ case "i": return {
99
+ ...style,
100
+ italic: true
101
+ };
102
+ case "del":
103
+ case "s":
104
+ case "strike": return {
105
+ ...style,
106
+ strike: true
107
+ };
108
+ case "code": return {
109
+ ...style,
110
+ fontFamily: require_shared_style_constants.MONOSPACE_FONT_FAMILY
111
+ };
112
+ case "a": {
113
+ const href = readAttr(attrs, "href");
114
+ return href === void 0 ? style : {
115
+ ...style,
116
+ hyperlink: unescapeHtml(href)
117
+ };
118
+ }
119
+ default: return style;
120
+ }
121
+ }
122
+ function pushPlainRun(runs, text, style) {
123
+ if (text.length === 0) return;
124
+ runs.push({
125
+ text: unescapeHtml(text),
126
+ ...style
127
+ });
128
+ }
129
+ function parseInlineHtml(text, style) {
130
+ const runs = [];
131
+ let pos = 0;
132
+ for (;;) {
133
+ const rest = text.slice(pos);
134
+ const match = INLINE_TAG_PATTERN.exec(rest);
135
+ if (match === null) {
136
+ pushPlainRun(runs, rest, style);
137
+ return runs;
138
+ }
139
+ pushPlainRun(runs, rest.slice(0, match.index), style);
140
+ const tagName = match[1].toLowerCase();
141
+ const attrs = match[2] ?? "";
142
+ const openEnd = pos + match.index + match[0].length;
143
+ const close = findBalancedClose(text, openEnd, [tagName]);
144
+ if (close === void 0) {
145
+ pushPlainRun(runs, rest, style);
146
+ return runs;
147
+ }
148
+ runs.push(...parseInlineHtml(text.slice(openEnd, close.start), applyTagStyle(style, tagName, attrs)));
149
+ pos = close.end;
150
+ }
151
+ }
152
+ const IMG_TAG_PATTERN = /^<img\b([^>]*?)\/?>$/i;
153
+ function parseHtmlImage(piece) {
154
+ const attrs = IMG_TAG_PATTERN.exec(piece)?.[1];
155
+ if (attrs === void 0) return;
156
+ const src = readAttr(attrs, "src");
157
+ if (src === void 0) return;
158
+ const alt = unescapeHtml(readAttr(attrs, "alt") ?? "");
159
+ const resolved = require_lower_image.resolveMarkdownImage(src, { alt }, void 0);
160
+ if (resolved === void 0) return;
161
+ return {
162
+ kind: "image",
163
+ format: resolved.format,
164
+ base64: resolved.base64,
165
+ widthPt: resolved.widthPt,
166
+ heightPt: resolved.heightPt,
167
+ altText: alt
168
+ };
169
+ }
170
+ function parseCellBlocks(inner, contentWidthPt) {
171
+ const trimmed = inner.trim();
172
+ if (trimmed.length === 0) return [];
173
+ const nestedTable = parseWholeTable(trimmed, contentWidthPt);
174
+ if (nestedTable !== void 0) return [nestedTable];
175
+ const blocks = [];
176
+ for (const segment of trimmed.split(/<br\s*\/?>/i)) {
177
+ const piece = segment.trim();
178
+ if (piece.length === 0) continue;
179
+ const image = parseHtmlImage(piece);
180
+ blocks.push(image ?? {
181
+ kind: "paragraph",
182
+ runs: parseInlineHtml(piece, {})
183
+ });
184
+ }
185
+ return blocks;
186
+ }
187
+ function applyTextAlign(blocks, alignment) {
188
+ const first = blocks[0];
189
+ if (alignment === void 0 || first?.kind !== "paragraph") return blocks;
190
+ return [{
191
+ ...first,
192
+ alignment
193
+ }, ...blocks.slice(1)];
194
+ }
195
+ function buildCell(cell, contentWidthPt) {
196
+ const colSpan = readPositiveIntAttr(cell.attrs, "colspan");
197
+ const rowSpan = readPositiveIntAttr(cell.attrs, "rowspan");
198
+ const background = readBackgroundFill(cell.attrs);
199
+ const textAlign = readTextAlign(cell.attrs);
200
+ const parsedBlocks = parseCellBlocks(cell.inner, contentWidthPt);
201
+ return {
202
+ blocks: applyTextAlign(parsedBlocks.length > 0 ? parsedBlocks : [{
203
+ kind: "paragraph",
204
+ runs: []
205
+ }], textAlign),
206
+ ...colSpan === void 0 ? {} : { colSpan },
207
+ ...rowSpan === void 0 ? {} : { rowSpan },
208
+ ...background === void 0 ? {} : { background }
209
+ };
210
+ }
211
+ function parseTableRows(inner, contentWidthPt) {
212
+ const rowElements = extractTopLevelElements(inner, ["tr"]);
213
+ if (rowElements === void 0 || rowElements.length === 0) return;
214
+ const rows = [];
215
+ for (const row of rowElements) {
216
+ const cellElements = extractTopLevelElements(row.inner, ["td", "th"]);
217
+ if (cellElements === void 0 || cellElements.length === 0) return;
218
+ rows.push({ cells: cellElements.map((cell) => buildCell(cell, contentWidthPt)) });
219
+ }
220
+ return rows;
221
+ }
222
+ function buildContentTable(rows, contentWidthPt) {
223
+ const header = rows[0];
224
+ const columnCount = Math.max(1, header.cells.reduce((sum, cell) => sum + (cell.colSpan ?? 1), 0));
225
+ return {
226
+ kind: "table",
227
+ rows,
228
+ columnWidthsPt: Array.from({ length: columnCount }, () => contentWidthPt / columnCount)
229
+ };
230
+ }
231
+ function parseWholeTable(text, contentWidthPt) {
232
+ const tableElements = extractTopLevelElements(text, ["table"]);
233
+ if (tableElements?.length !== 1) return;
234
+ const rows = parseTableRows(tableElements[0].inner, contentWidthPt);
235
+ return rows === void 0 ? void 0 : buildContentTable(rows, contentWidthPt);
236
+ }
237
+ function parseHtmlTable(literal, contentWidthPt) {
238
+ const trimmed = literal.trim();
239
+ if (!/^<table\b/i.test(trimmed)) return;
240
+ return parseWholeTable(trimmed, contentWidthPt);
241
+ }
242
+ //#endregion
243
+ exports.parseHtmlTable = parseHtmlTable;
@@ -0,0 +1,5 @@
1
+ import { ContentTable } from "document-schema.js";
2
+ //#region src/html/html-table.d.ts
3
+ declare function parseHtmlTable(literal: string, contentWidthPt: number): ContentTable | undefined;
4
+ //#endregion
5
+ export { parseHtmlTable };
@@ -0,0 +1,5 @@
1
+ import { ContentTable } from "document-schema.js";
2
+ //#region src/html/html-table.d.ts
3
+ declare function parseHtmlTable(literal: string, contentWidthPt: number): ContentTable | undefined;
4
+ //#endregion
5
+ export { parseHtmlTable };