formatferry 1.0.20 → 1.0.22

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 (2) hide show
  1. package/dist/index.js +91 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19720,7 +19720,7 @@ var InputSanitizer = class {
19720
19720
  ],
19721
19721
  // Include data-page-marker for PDF page break markers (Req 3.5)
19722
19722
  // Include type and checked for task list checkbox support
19723
- ALLOWED_ATTR: ["href", "src", "alt", "title", "style", "class", "data-page-marker", "type", "checked", "data-math"]
19723
+ ALLOWED_ATTR: ["href", "src", "alt", "title", "style", "class", "data-page-marker", "data-docx-table-kind", "type", "checked", "data-math"]
19724
19724
  };
19725
19725
  switch (this.config.level) {
19726
19726
  case "strict":
@@ -20729,6 +20729,22 @@ ${plainText}
20729
20729
  });
20730
20730
  return lines.join("\n");
20731
20731
  }
20732
+ flattenTables(html) {
20733
+ if (typeof DOMParser === "undefined") return html;
20734
+ const parser = new DOMParser();
20735
+ const doc = parser.parseFromString(`<div>${html}</div>`, "text/html");
20736
+ Array.from(doc.querySelectorAll("table")).forEach((table) => {
20737
+ const replacement = doc.createElement("div");
20738
+ const lines = this.tableToPlainText(table).split("\n").filter(Boolean);
20739
+ lines.forEach((line) => {
20740
+ const paragraph = doc.createElement("p");
20741
+ paragraph.textContent = line;
20742
+ replacement.appendChild(paragraph);
20743
+ });
20744
+ table.replaceWith(replacement);
20745
+ });
20746
+ return doc.body.firstElementChild?.innerHTML ?? html;
20747
+ }
20732
20748
  addGoogleDocsRules(service) {
20733
20749
  service.addRule("googleDocsBold", {
20734
20750
  filter: (node) => {
@@ -20860,10 +20876,7 @@ ${plainText}
20860
20876
  let headerProcessed = false;
20861
20877
  rows.forEach((row, index) => {
20862
20878
  const cells = Array.from(row.querySelectorAll("th, td"));
20863
- const cellContents = cells.map((cell) => {
20864
- const cellContent = cell.textContent || "";
20865
- return cellContent.trim().replace(/\|/g, "\\|");
20866
- });
20879
+ const cellContents = cells.map((cell) => this.escapeTableCell(this.renderTableCell(cell)));
20867
20880
  if (cellContents.length > 0) {
20868
20881
  markdown += "| " + cellContents.join(" | ") + " |\n";
20869
20882
  if (!headerProcessed && (row.querySelector("th") || index === 0)) {
@@ -20877,6 +20890,73 @@ ${plainText}
20877
20890
  }
20878
20891
  });
20879
20892
  }
20893
+ renderTableCell(cell) {
20894
+ const blocks = [];
20895
+ let inlineBuffer = "";
20896
+ cell.childNodes.forEach((child) => {
20897
+ if (child.nodeType === Node.ELEMENT_NODE && this.isTableCellBlock(child)) {
20898
+ if (inlineBuffer.trim()) {
20899
+ blocks.push(inlineBuffer.trim());
20900
+ inlineBuffer = "";
20901
+ }
20902
+ blocks.push(this.renderTableBlock(child));
20903
+ return;
20904
+ }
20905
+ inlineBuffer += this.renderInlineNode(child);
20906
+ });
20907
+ if (inlineBuffer.trim()) blocks.push(inlineBuffer.trim());
20908
+ if (blocks.length === 0) return (cell.textContent || "").trim();
20909
+ return blocks.filter(Boolean).join("<br>");
20910
+ }
20911
+ isTableCellBlock(element) {
20912
+ return ["P", "DIV", "UL", "OL", "H1", "H2", "H3", "H4", "H5", "H6", "BLOCKQUOTE", "PRE"].includes(element.nodeName);
20913
+ }
20914
+ renderTableBlock(element) {
20915
+ if (element.nodeName === "UL" || element.nodeName === "OL") {
20916
+ return Array.from(element.children).filter((child) => child.nodeName === "LI").map((child) => `- ${this.renderInlineNode(child).trim()}`).join("<br>");
20917
+ }
20918
+ return this.renderInlineNode(element).trim();
20919
+ }
20920
+ renderInlineNode(node) {
20921
+ if (node.nodeType === Node.TEXT_NODE) return node.textContent || "";
20922
+ if (node.nodeType !== Node.ELEMENT_NODE) return "";
20923
+ const element = node;
20924
+ const content = Array.from(element.childNodes).map((child) => this.renderInlineNode(child)).join("");
20925
+ const trimmed = content.trim();
20926
+ switch (element.nodeName) {
20927
+ case "BR":
20928
+ return "<br>";
20929
+ case "STRONG":
20930
+ case "B":
20931
+ return trimmed ? `**${trimmed}**` : content;
20932
+ case "EM":
20933
+ case "I":
20934
+ return trimmed ? `_${trimmed}_` : content;
20935
+ case "CODE":
20936
+ return trimmed ? this.renderCodeSpan(trimmed) : content;
20937
+ case "A": {
20938
+ const href = element.getAttribute("href") || "";
20939
+ return href && trimmed ? `[${this.escapeMarkdownLinkText(trimmed)}](<${this.escapeMarkdownLinkHref(href)}>)` : content;
20940
+ }
20941
+ default:
20942
+ return content;
20943
+ }
20944
+ }
20945
+ renderCodeSpan(content) {
20946
+ if (!content.includes("`")) return `\`${content}\``;
20947
+ const runs = content.match(/`+/g) || ["`"];
20948
+ const delimiter = "`".repeat(Math.max(...runs.map((run) => run.length)) + 1);
20949
+ return `${delimiter} ${content} ${delimiter}`;
20950
+ }
20951
+ escapeMarkdownLinkText(text) {
20952
+ return text.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
20953
+ }
20954
+ escapeMarkdownLinkHref(href) {
20955
+ return href.replace(/</g, "%3C").replace(/>/g, "%3E");
20956
+ }
20957
+ escapeTableCell(content) {
20958
+ return content.replace(/\|/g, "\\|").replace(/\s*<br>\s*/g, "<br>").trim();
20959
+ }
20880
20960
  addListRules(service) {
20881
20961
  service.addRule("enhancedList", {
20882
20962
  filter: ["ul", "ol"],
@@ -21025,7 +21105,8 @@ ${plainText}
21025
21105
  service = this.githubService;
21026
21106
  break;
21027
21107
  }
21028
- let markdown = service.turndown(sanitizedHtml);
21108
+ const htmlForConversion = options.preserveTables ? sanitizedHtml : this.flattenTables(sanitizedHtml);
21109
+ let markdown = service.turndown(htmlForConversion);
21029
21110
  markdown = this.applyFormatPostProcessing(markdown, options.format);
21030
21111
  const endTime = performance.now();
21031
21112
  const metadata = {
@@ -22301,8 +22382,9 @@ function groupRadioButtons(annotations) {
22301
22382
  const w = urx - llx;
22302
22383
  const h = ury - lly;
22303
22384
  if (w <= 0 || h <= 0) continue;
22385
+ const isSelected = m === selected;
22304
22386
  items.push({
22305
- str: m === selected ? "[X]" : "[ ]",
22387
+ str: isSelected ? "[X]" : "[ ]",
22306
22388
  x: llx,
22307
22389
  y: lly,
22308
22390
  width: w,
@@ -25418,8 +25500,8 @@ import { readFileSync as readFileSync4 } from "fs";
25418
25500
  import { resolve, dirname as dirname3 } from "path";
25419
25501
  import { fileURLToPath } from "url";
25420
25502
  var getVersion = () => {
25421
- if ("1.0.20") {
25422
- return "1.0.20";
25503
+ if ("1.0.22") {
25504
+ return "1.0.22";
25423
25505
  }
25424
25506
  try {
25425
25507
  const __dirname = dirname3(fileURLToPath(import.meta.url));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formatferry",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "FormatFerry CLI — Convert HTML, DOCX, PDF, XLSX, CSV, JSON, XML, PPTX to Markdown",
5
5
  "keywords": [
6
6
  "markdown",