formatferry 1.0.12 → 1.0.13

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 +763 -28
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -20376,6 +20376,9 @@ var ConversionEngine = class {
20376
20376
  this.addPdfPageMarkerRules(this.githubService);
20377
20377
  this.addPdfPageMarkerRules(this.commonmarkService);
20378
20378
  this.addPdfPageMarkerRules(this.customService);
20379
+ this.addSlideMarkerRules(this.githubService);
20380
+ this.addSlideMarkerRules(this.commonmarkService);
20381
+ this.addSlideMarkerRules(this.customService);
20379
20382
  this.discordService = this.createDiscordService();
20380
20383
  this.redditService = this.createRedditService();
20381
20384
  this.rmarkdownService = this.createRMarkdownService();
@@ -20386,6 +20389,8 @@ var ConversionEngine = class {
20386
20389
  };
20387
20390
  this.slackService = createSlackService(deps);
20388
20391
  this.confluenceService = createConfluenceService(deps);
20392
+ this.addSlideMarkerRules(this.slackService);
20393
+ this.addSlideMarkerRules(this.confluenceService);
20389
20394
  }
20390
20395
  /**
20391
20396
  * Create Discord-optimized Turndown service
@@ -20405,6 +20410,7 @@ var ConversionEngine = class {
20405
20410
  this.addGoogleDocsRules(service);
20406
20411
  this.addListRules(service);
20407
20412
  this.addPdfPageMarkerRules(service);
20413
+ this.addSlideMarkerRules(service);
20408
20414
  service.addRule("discordUnderline", {
20409
20415
  filter: ["u", "ins"],
20410
20416
  replacement: (content) => content.trim() ? `__${content}__` : content
@@ -20583,6 +20589,7 @@ ${plainText}
20583
20589
  this.addTableRules(service);
20584
20590
  this.addListRules(service);
20585
20591
  this.addPdfPageMarkerRules(service);
20592
+ this.addSlideMarkerRules(service);
20586
20593
  service.addRule("redditSuperscript", {
20587
20594
  filter: (node) => node.nodeName === "SUP",
20588
20595
  replacement: (content) => {
@@ -20641,6 +20648,7 @@ ${plainText}
20641
20648
  this.addTableRules(service);
20642
20649
  this.addListRules(service);
20643
20650
  this.addPdfPageMarkerRules(service);
20651
+ this.addSlideMarkerRules(service);
20644
20652
  service.addRule("rmdSuperscript", {
20645
20653
  filter: (node) => node.nodeName === "SUP",
20646
20654
  replacement: (content) => {
@@ -20924,6 +20932,21 @@ ${plainText}
20924
20932
  });
20925
20933
  return markdown;
20926
20934
  }
20935
+ addSlideMarkerRules(service) {
20936
+ service.addRule("pptxSlideMarker", {
20937
+ filter: (node) => node.nodeName === "DIV" && node.hasAttribute("data-slide-marker"),
20938
+ replacement: (_content, node) => {
20939
+ const slideNumber = node.getAttribute("data-slide-marker");
20940
+ return `
20941
+
20942
+ ---
20943
+
20944
+ **[Slide ${slideNumber}]**
20945
+
20946
+ `;
20947
+ }
20948
+ });
20949
+ }
20927
20950
  /**
20928
20951
  * Add PDF page marker rules to convert data-page-marker divs to Markdown comments
20929
20952
  * This preserves page boundaries from PDF documents (Req 3.5)
@@ -21348,7 +21371,9 @@ var _flags = {
21348
21371
  enableXlsx: true,
21349
21372
  enablePdf: true,
21350
21373
  enableHtml: true,
21351
- enablePdfTableDetection: true
21374
+ enablePdfTableDetection: true,
21375
+ enableData: true,
21376
+ enablePptx: true
21352
21377
  };
21353
21378
  function getFeatureFlags() {
21354
21379
  return { ..._flags };
@@ -21453,17 +21478,21 @@ function clusterIntoRows(items) {
21453
21478
  }
21454
21479
  function detectColumns(rows) {
21455
21480
  if (rows.length === 0) return [];
21456
- const allItems = rows.flatMap((r) => r.items);
21481
+ const columnRows = selectColumnRows(rows);
21482
+ if (columnRows.length < 2) return [];
21483
+ const allItems = columnRows.flatMap((r) => r.items);
21457
21484
  if (allItems.length === 0) return [];
21458
21485
  const medianCharWidth = median(allItems.map((it) => estimateCharWidth(it)).filter((w) => w > 0)) || 1;
21459
21486
  const gapTol = medianCharWidth * COLUMN_GAP_MERGE_FACTOR;
21460
- const hardBoundaries = voteColumnBoundaries(rows, medianCharWidth);
21487
+ const separationGap = Math.max(gapTol, medianCharWidth * 1.5);
21488
+ const hardBoundaries = voteColumnBoundaries(columnRows, medianCharWidth);
21461
21489
  const intervals = allItems.map((it) => ({ x0: it.x, x1: it.right })).sort((a, b) => a.x0 - b.x0);
21462
21490
  const merged = [];
21463
21491
  for (const iv of intervals) {
21464
21492
  const last = merged[merged.length - 1];
21465
21493
  const crossesHardBoundary = last !== void 0 && hardBoundaries.some((b) => b >= last.x1 && b <= iv.x0);
21466
- if (last && iv.x0 - last.x1 <= gapTol && !crossesHardBoundary) {
21494
+ const hasStrongRowSeparation = last !== void 0 && hasConsistentRowSeparation(columnRows, last, iv, separationGap);
21495
+ if (last && iv.x0 - last.x1 <= gapTol && !crossesHardBoundary && !hasStrongRowSeparation) {
21467
21496
  last.x1 = Math.max(last.x1, iv.x1);
21468
21497
  } else {
21469
21498
  merged.push({ ...iv });
@@ -21471,16 +21500,50 @@ function detectColumns(rows) {
21471
21500
  }
21472
21501
  const anchors = [];
21473
21502
  for (const iv of merged) {
21474
- const rowsWithHit = rows.filter(
21503
+ const rowsWithHit = columnRows.filter(
21475
21504
  (r) => r.items.some((it) => rangeOverlap(it.x, it.right, iv.x0, iv.x1))
21476
21505
  );
21477
- const support = rowsWithHit.length / rows.length;
21506
+ const support = rowsWithHit.length / columnRows.length;
21478
21507
  if (support < COLUMN_SUPPORT_THRESHOLD) continue;
21479
- const alignment = chooseAlignment(rows, iv);
21508
+ const alignment = chooseAlignment(columnRows, iv);
21480
21509
  anchors.push({ x0: iv.x0, x1: iv.x1, alignment, support });
21481
21510
  }
21482
21511
  return anchors;
21483
21512
  }
21513
+ function selectColumnRows(rows) {
21514
+ if (rows.length === 0) return [];
21515
+ const maxItems = Math.max(...rows.map((r) => r.items.length));
21516
+ if (maxItems < 2) return [];
21517
+ const minItems = Math.max(2, Math.ceil(maxItems * 0.5));
21518
+ const denseRows = rows.filter((r) => r.items.length >= minItems);
21519
+ if (denseRows.length < 2) return [];
21520
+ return denseRows;
21521
+ }
21522
+ function trimSparseBoundaryRows(rows) {
21523
+ if (rows.length === 0) return { rows: [], start: 0, end: -1 };
21524
+ let start = 0;
21525
+ let end = rows.length - 1;
21526
+ while (start <= end && rows[start].items.length < 2) start++;
21527
+ while (end >= start && rows[end].items.length < 2) end--;
21528
+ if (start > end) return { rows: [], start, end };
21529
+ return { rows: rows.slice(start, end + 1), start, end };
21530
+ }
21531
+ function hasConsistentRowSeparation(rows, left, right, minGap) {
21532
+ if (rows.length === 0) return false;
21533
+ let supporting = 0;
21534
+ for (const row of rows) {
21535
+ let leftRight = -Infinity;
21536
+ let rightLeft = Infinity;
21537
+ for (const it of row.items) {
21538
+ if (rangeOverlap(it.x, it.right, left.x0, left.x1)) leftRight = Math.max(leftRight, it.right);
21539
+ if (rangeOverlap(it.x, it.right, right.x0, right.x1)) rightLeft = Math.min(rightLeft, it.x);
21540
+ }
21541
+ if (leftRight > -Infinity && rightLeft < Infinity && rightLeft - leftRight > minGap) {
21542
+ supporting++;
21543
+ }
21544
+ }
21545
+ return supporting / rows.length >= COLUMN_SUPPORT_THRESHOLD;
21546
+ }
21484
21547
  function coalesceMultiLineCells(rows, columns) {
21485
21548
  if (rows.length < 2 || columns.length < 2) return rows;
21486
21549
  const gaps = rowGaps(rows);
@@ -21557,12 +21620,15 @@ function findTableRegions(items, options = {}) {
21557
21620
  let best = null;
21558
21621
  for (let j = i + 1; j < rows.length; j++) {
21559
21622
  const run = rows.slice(i, j + 1);
21560
- const columns = detectColumns(run);
21623
+ const trimmed = trimSparseBoundaryRows(run);
21624
+ if (trimmed.rows.length < 2) {
21625
+ continue;
21626
+ }
21627
+ const columns = detectColumns(trimmed.rows);
21561
21628
  if (columns.length < 2) {
21562
- if (best) break;
21563
21629
  continue;
21564
21630
  }
21565
- const coalesced = coalesceMultiLineCells(run, columns);
21631
+ const coalesced = coalesceMultiLineCells(trimmed.rows, columns);
21566
21632
  if (coalesced.length < 2) {
21567
21633
  continue;
21568
21634
  }
@@ -21579,15 +21645,15 @@ function findTableRegions(items, options = {}) {
21579
21645
  if (best) break;
21580
21646
  continue;
21581
21647
  }
21582
- const bbox = computeBbox(run);
21583
- const medianH = median(run.flatMap((r) => r.items.map((it) => it.height))) || 0;
21648
+ const bbox = computeBbox(coalesced);
21649
+ const medianH = median(coalesced.flatMap((r) => r.items.map((it) => it.height))) || 0;
21584
21650
  const mayContinue = options.pageBottomY !== void 0 && medianH > 0 && bbox.y0 - options.pageBottomY < medianH * 2;
21585
21651
  best = {
21586
21652
  rows: coalesced,
21587
21653
  columns,
21588
21654
  confidence,
21589
21655
  bbox,
21590
- rowRange: { start: i, end: j },
21656
+ rowRange: { start: i + trimmed.start, end: i + trimmed.end },
21591
21657
  mayContinue
21592
21658
  };
21593
21659
  }
@@ -22747,6 +22813,621 @@ var SpreadsheetAdapter = class {
22747
22813
  };
22748
22814
  var spreadsheetAdapter = new SpreadsheetAdapter();
22749
22815
 
22816
+ // ../../src/shared/html-escape.ts
22817
+ function escapeHtml3(text) {
22818
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
22819
+ }
22820
+
22821
+ // ../../src/shared/adapters/data-adapter.ts
22822
+ var DataParseError = class extends Error {
22823
+ constructor(message, code, originalError) {
22824
+ super(message);
22825
+ this.code = code;
22826
+ this.originalError = originalError;
22827
+ this.name = "DataParseError";
22828
+ }
22829
+ };
22830
+ var XML_MAX_STRUCTURED_DEPTH = 4;
22831
+ var TABLE_WIDE_COLUMN_WARNING = 15;
22832
+ var JSON_DEPTH_GUARD = 128;
22833
+ var XML_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_.:-]*$/;
22834
+ var XML_ATTRIBUTE_REGEX = /[A-Za-z_][A-Za-z0-9_.:-]*\s*=\s*(?:"[^"]*"|'[^']*')/y;
22835
+ function isXmlWhitespace(ch) {
22836
+ return ch === " " || ch === "\n" || ch === "\r" || ch === " ";
22837
+ }
22838
+ function findTagEnd(xml, start) {
22839
+ let quote = null;
22840
+ for (let i = start; i < xml.length; i++) {
22841
+ const ch = xml[i];
22842
+ if (quote) {
22843
+ if (ch === quote) quote = null;
22844
+ continue;
22845
+ }
22846
+ if (ch === '"' || ch === "'") {
22847
+ quote = ch;
22848
+ continue;
22849
+ }
22850
+ if (ch === ">") return i;
22851
+ }
22852
+ return -1;
22853
+ }
22854
+ function findDoctypeEnd(xml, start) {
22855
+ let bracketDepth = 0;
22856
+ let quote = null;
22857
+ for (let i = start; i < xml.length; i++) {
22858
+ const ch = xml[i];
22859
+ if (quote) {
22860
+ if (ch === quote) quote = null;
22861
+ continue;
22862
+ }
22863
+ if (ch === '"' || ch === "'") {
22864
+ quote = ch;
22865
+ continue;
22866
+ }
22867
+ if (ch === "[") {
22868
+ bracketDepth++;
22869
+ continue;
22870
+ }
22871
+ if (ch === "]") {
22872
+ bracketDepth = Math.max(0, bracketDepth - 1);
22873
+ continue;
22874
+ }
22875
+ if (ch === ">" && bracketDepth === 0) return i;
22876
+ }
22877
+ return -1;
22878
+ }
22879
+ function hasValidXmlAttributes(attrs) {
22880
+ let index = 0;
22881
+ while (index < attrs.length) {
22882
+ while (index < attrs.length && isXmlWhitespace(attrs[index])) index++;
22883
+ if (index >= attrs.length) return true;
22884
+ XML_ATTRIBUTE_REGEX.lastIndex = index;
22885
+ const match = XML_ATTRIBUTE_REGEX.exec(attrs);
22886
+ if (!match || match.index !== index) return false;
22887
+ index = XML_ATTRIBUTE_REGEX.lastIndex;
22888
+ }
22889
+ return true;
22890
+ }
22891
+ function isWellFormedXml(text) {
22892
+ const xml = text.replace(/^\uFEFF/, "");
22893
+ const stack = [];
22894
+ let seenRoot = false;
22895
+ let i = 0;
22896
+ while (i < xml.length) {
22897
+ const lt = xml.indexOf("<", i);
22898
+ if (lt === -1) {
22899
+ return seenRoot && stack.length === 0 && xml.slice(i).trim().length === 0;
22900
+ }
22901
+ if (xml.slice(i, lt).trim().length > 0 && stack.length === 0) {
22902
+ return false;
22903
+ }
22904
+ i = lt;
22905
+ if (xml.startsWith("<!--", i)) {
22906
+ const end = xml.indexOf("-->", i + 4);
22907
+ if (end === -1) return false;
22908
+ i = end + 3;
22909
+ continue;
22910
+ }
22911
+ if (xml.startsWith("<![CDATA[", i)) {
22912
+ if (stack.length === 0) return false;
22913
+ const end = xml.indexOf("]]>", i + 9);
22914
+ if (end === -1) return false;
22915
+ i = end + 3;
22916
+ continue;
22917
+ }
22918
+ if (xml.startsWith("<?", i)) {
22919
+ const end = xml.indexOf("?>", i + 2);
22920
+ if (end === -1) return false;
22921
+ i = end + 2;
22922
+ continue;
22923
+ }
22924
+ if (xml.startsWith("<!DOCTYPE", i) || xml.startsWith("<!doctype", i)) {
22925
+ if (seenRoot || stack.length > 0) return false;
22926
+ const end = findDoctypeEnd(xml, i + 9);
22927
+ if (end === -1) return false;
22928
+ i = end + 1;
22929
+ continue;
22930
+ }
22931
+ const gt = findTagEnd(xml, i + 1);
22932
+ if (gt === -1) return false;
22933
+ const raw = xml.slice(i + 1, gt).trim();
22934
+ if (raw.length === 0) return false;
22935
+ if (raw.startsWith("/")) {
22936
+ const name2 = raw.slice(1).trim();
22937
+ if (!XML_NAME_REGEX.test(name2)) return false;
22938
+ const open = stack.pop();
22939
+ if (open !== name2) return false;
22940
+ if (stack.length === 0) seenRoot = true;
22941
+ i = gt + 1;
22942
+ continue;
22943
+ }
22944
+ const selfClosing = raw.endsWith("/");
22945
+ const body = selfClosing ? raw.slice(0, -1).trim() : raw;
22946
+ if (body.length === 0) return false;
22947
+ const spaceIndex = body.search(/\s/);
22948
+ const name = spaceIndex === -1 ? body : body.slice(0, spaceIndex);
22949
+ const attrs = spaceIndex === -1 ? "" : body.slice(spaceIndex + 1).trim();
22950
+ if (!XML_NAME_REGEX.test(name)) return false;
22951
+ if (!hasValidXmlAttributes(attrs)) return false;
22952
+ if (selfClosing) {
22953
+ if (stack.length === 0) {
22954
+ if (seenRoot) return false;
22955
+ seenRoot = true;
22956
+ }
22957
+ } else {
22958
+ if (seenRoot && stack.length === 0) return false;
22959
+ stack.push(name);
22960
+ }
22961
+ i = gt + 1;
22962
+ }
22963
+ return seenRoot && stack.length === 0;
22964
+ }
22965
+ function jsonDepth(value, current = 0) {
22966
+ if (value === null || typeof value !== "object") return current;
22967
+ if (current >= JSON_DEPTH_GUARD) return JSON_DEPTH_GUARD + 1;
22968
+ const children = Object.values(value);
22969
+ if (children.length === 0) return current;
22970
+ let maxDepth = current;
22971
+ for (const child of children) {
22972
+ const depth = jsonDepth(child, current + 1);
22973
+ if (depth > maxDepth) {
22974
+ maxDepth = depth;
22975
+ if (maxDepth > JSON_DEPTH_GUARD) {
22976
+ return maxDepth;
22977
+ }
22978
+ }
22979
+ }
22980
+ return maxDepth;
22981
+ }
22982
+ function isTableArray(arr) {
22983
+ if (arr.length === 0) return false;
22984
+ const first = arr[0];
22985
+ if (first === null || typeof first !== "object" || Array.isArray(first)) return false;
22986
+ const keys = Object.keys(first);
22987
+ return arr.every((item) => {
22988
+ if (item === null || typeof item !== "object" || Array.isArray(item)) return false;
22989
+ const itemObj = item;
22990
+ const itemKeys = Object.keys(itemObj);
22991
+ return itemKeys.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(itemObj, key));
22992
+ });
22993
+ }
22994
+ function arrayToTable(arr) {
22995
+ const keys = Object.keys(arr[0]);
22996
+ const headerCells = keys.map((k) => `<th>${escapeHtml3(k)}</th>`).join("");
22997
+ const bodyRows = arr.map((row) => {
22998
+ const cells = keys.map((k) => `<td>${escapeHtml3(String(row[k] ?? ""))}</td>`).join("");
22999
+ return `<tr>${cells}</tr>`;
23000
+ }).join("\n");
23001
+ return `<table>
23002
+ <thead><tr>${headerCells}</tr></thead>
23003
+ <tbody>
23004
+ ${bodyRows}
23005
+ </tbody>
23006
+ </table>`;
23007
+ }
23008
+ function objectToTable(obj) {
23009
+ const rows = Object.entries(obj).map(([k, v]) => `<tr><td>${escapeHtml3(k)}</td><td>${escapeHtml3(String(v ?? ""))}</td></tr>`).join("\n");
23010
+ return `<table>
23011
+ <thead><tr><th>Key</th><th>Value</th></tr></thead>
23012
+ <tbody>
23013
+ ${rows}
23014
+ </tbody>
23015
+ </table>`;
23016
+ }
23017
+ function safeJsonStringify(value) {
23018
+ const seen = /* @__PURE__ */ new WeakSet();
23019
+ const stack = [];
23020
+ function push(v, indent) {
23021
+ if (v === null) {
23022
+ stack.push("null");
23023
+ return;
23024
+ }
23025
+ const t = typeof v;
23026
+ if (t === "string") {
23027
+ stack.push(JSON.stringify(v));
23028
+ return;
23029
+ }
23030
+ if (t === "number" || t === "boolean") {
23031
+ stack.push(String(v));
23032
+ return;
23033
+ }
23034
+ if (t !== "object") {
23035
+ stack.push("null");
23036
+ return;
23037
+ }
23038
+ if (typeof v === "object" && v !== null) {
23039
+ if (seen.has(v)) {
23040
+ stack.push("null");
23041
+ return;
23042
+ }
23043
+ seen.add(v);
23044
+ }
23045
+ if (Array.isArray(v)) {
23046
+ if (v.length === 0) {
23047
+ stack.push("[]");
23048
+ return;
23049
+ }
23050
+ const inner2 = " ".repeat(indent + 1);
23051
+ const close2 = " ".repeat(indent);
23052
+ stack.push("[\n");
23053
+ for (let i = 0; i < v.length; i++) {
23054
+ stack.push(inner2);
23055
+ push(v[i], indent + 1);
23056
+ stack.push(i < v.length - 1 ? ",\n" : "\n");
23057
+ }
23058
+ stack.push(close2 + "]");
23059
+ return;
23060
+ }
23061
+ const keys = Object.keys(v);
23062
+ if (keys.length === 0) {
23063
+ stack.push("{}");
23064
+ return;
23065
+ }
23066
+ const inner = " ".repeat(indent + 1);
23067
+ const close = " ".repeat(indent);
23068
+ stack.push("{\n");
23069
+ for (let i = 0; i < keys.length; i++) {
23070
+ const k = keys[i];
23071
+ stack.push(inner + JSON.stringify(k) + ": ");
23072
+ push(v[k], indent + 1);
23073
+ stack.push(i < keys.length - 1 ? ",\n" : "\n");
23074
+ }
23075
+ stack.push(close + "}");
23076
+ }
23077
+ push(value, 0);
23078
+ return stack.join("");
23079
+ }
23080
+ function jsonToCodeBlock(value) {
23081
+ const json = safeJsonStringify(value);
23082
+ return `<pre><code class="language-json">${escapeHtml3(json)}</code></pre>`;
23083
+ }
23084
+ function jsonValueToHtml(value) {
23085
+ const warnings = [];
23086
+ if (Array.isArray(value) && isTableArray(value)) {
23087
+ const keys = Object.keys(value[0]);
23088
+ if (keys.length > TABLE_WIDE_COLUMN_WARNING) {
23089
+ warnings.push(
23090
+ `Table has ${keys.length} columns which may not render well in narrow Markdown viewers.`
23091
+ );
23092
+ }
23093
+ return { html: arrayToTable(value), warnings };
23094
+ }
23095
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && jsonDepth(value) <= 1) {
23096
+ return { html: objectToTable(value), warnings };
23097
+ }
23098
+ return { html: jsonToCodeBlock(value), warnings };
23099
+ }
23100
+ function serializeXmlElement(el) {
23101
+ const serializerCtor = globalThis.XMLSerializer;
23102
+ if (typeof serializerCtor === "function") {
23103
+ return new serializerCtor().serializeToString(el);
23104
+ }
23105
+ const outer = el.outerHTML;
23106
+ if (typeof outer === "string") {
23107
+ return outer;
23108
+ }
23109
+ return String(el);
23110
+ }
23111
+ function xmlElementToHtml(el, depth) {
23112
+ const tagName = el.localName || el.nodeName;
23113
+ const directText = Array.from(el.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => (n.textContent || "").trim()).join(" ").trim();
23114
+ const children = Array.from(el.children);
23115
+ if (children.length === 0) {
23116
+ if (!directText) return "";
23117
+ const headingLevel2 = Math.min(depth + 1, 6);
23118
+ return `<h${headingLevel2}>${escapeHtml3(tagName)}</h${headingLevel2}>
23119
+ <p>${escapeHtml3(directText)}</p>
23120
+ `;
23121
+ }
23122
+ if (depth >= XML_MAX_STRUCTURED_DEPTH) {
23123
+ return `<pre><code class="language-xml">${escapeHtml3(serializeXmlElement(el))}</code></pre>
23124
+ `;
23125
+ }
23126
+ const headingLevel = Math.min(depth + 1, 6);
23127
+ let html = `<h${headingLevel}>${escapeHtml3(tagName)}</h${headingLevel}>
23128
+ `;
23129
+ if (directText) {
23130
+ html += `<p>${escapeHtml3(directText)}</p>
23131
+ `;
23132
+ }
23133
+ for (const child of children) {
23134
+ html += xmlElementToHtml(child, depth + 1);
23135
+ }
23136
+ return html;
23137
+ }
23138
+ var DataAdapter = class {
23139
+ async parseJsonToHtml(arrayBuffer) {
23140
+ const warnings = [];
23141
+ try {
23142
+ if (arrayBuffer.byteLength === 0) {
23143
+ throw new DataParseError("The JSON file is empty.", "EMPTY_FILE");
23144
+ }
23145
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
23146
+ let parsed;
23147
+ try {
23148
+ parsed = JSON.parse(text);
23149
+ } catch (err) {
23150
+ throw new DataParseError(
23151
+ "The file does not contain valid JSON.",
23152
+ "INVALID_JSON",
23153
+ err instanceof Error ? err : void 0
23154
+ );
23155
+ }
23156
+ const { html, warnings: convWarnings } = jsonValueToHtml(parsed);
23157
+ warnings.push(...convWarnings);
23158
+ return { html, warnings };
23159
+ } catch (error) {
23160
+ if (error instanceof DataParseError) throw error;
23161
+ throw new DataParseError(
23162
+ `Failed to parse JSON file: ${error instanceof Error ? error.message : String(error)}`,
23163
+ "PARSE_ERROR",
23164
+ error instanceof Error ? error : void 0
23165
+ );
23166
+ }
23167
+ }
23168
+ async parseXmlToHtml(arrayBuffer) {
23169
+ const warnings = [];
23170
+ try {
23171
+ if (arrayBuffer.byteLength === 0) {
23172
+ throw new DataParseError("The XML file is empty.", "EMPTY_FILE");
23173
+ }
23174
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
23175
+ if (!isWellFormedXml(text)) {
23176
+ throw new DataParseError("The file does not contain valid XML.", "INVALID_XML");
23177
+ }
23178
+ const parserCtor = globalThis.DOMParser;
23179
+ if (typeof parserCtor !== "function") {
23180
+ throw new DataParseError("XML parsing is unavailable in this runtime.", "PARSE_ERROR");
23181
+ }
23182
+ const doc = new parserCtor().parseFromString(text, "application/xml");
23183
+ const parseError = doc.querySelector("parsererror");
23184
+ const root = doc.documentElement;
23185
+ if (parseError || !root || /parsererror/i.test(root.nodeName)) {
23186
+ throw new DataParseError("The file does not contain valid XML.", "INVALID_XML");
23187
+ }
23188
+ const html = xmlElementToHtml(root, 0);
23189
+ if (!html.trim()) {
23190
+ warnings.push("The XML file contains no readable text content.");
23191
+ return { html: `<p><em>No text content found in XML file.</em></p>`, warnings };
23192
+ }
23193
+ return { html, warnings };
23194
+ } catch (error) {
23195
+ if (error instanceof DataParseError) throw error;
23196
+ throw new DataParseError(
23197
+ `Failed to parse XML file: ${error instanceof Error ? error.message : String(error)}`,
23198
+ "PARSE_ERROR",
23199
+ error instanceof Error ? error : void 0
23200
+ );
23201
+ }
23202
+ }
23203
+ };
23204
+ var dataAdapter = new DataAdapter();
23205
+
23206
+ // ../../src/shared/adapters/pptx-adapter.ts
23207
+ import JSZip from "jszip";
23208
+ var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
23209
+ var NS_P = "http://schemas.openxmlformats.org/presentationml/2006/main";
23210
+ var PptxParseError = class extends Error {
23211
+ constructor(message, code, originalError) {
23212
+ super(message);
23213
+ this.code = code;
23214
+ this.originalError = originalError;
23215
+ this.name = "PptxParseError";
23216
+ }
23217
+ };
23218
+ function getElements(parent, ns, localName) {
23219
+ return Array.from(parent.getElementsByTagNameNS(ns, localName));
23220
+ }
23221
+ function isTitle(shape) {
23222
+ const phElements = getElements(shape, NS_P, "ph");
23223
+ for (const ph of phElements) {
23224
+ const type = ph.getAttribute("type");
23225
+ if (type === "title" || type === "ctrTitle") return true;
23226
+ const idx = ph.getAttribute("idx");
23227
+ if (!type && (!idx || idx === "0")) return true;
23228
+ }
23229
+ return false;
23230
+ }
23231
+ function runToHtml(run) {
23232
+ const tElements = getElements(run, NS_A, "t");
23233
+ const text = tElements.map((t) => t.textContent || "").join("");
23234
+ if (!text) return "";
23235
+ const rPr = getElements(run, NS_A, "rPr")[0];
23236
+ if (!rPr) return escapeHtml3(text);
23237
+ let result = escapeHtml3(text);
23238
+ if (rPr.getAttribute("b") === "1") result = `<strong>${result}</strong>`;
23239
+ if (rPr.getAttribute("i") === "1") result = `<em>${result}</em>`;
23240
+ return result;
23241
+ }
23242
+ function paragraphToHtml(para, shapeTitleFlag) {
23243
+ const runs = getElements(para, NS_A, "r");
23244
+ let content = runs.map(runToHtml).join("");
23245
+ const fields = getElements(para, NS_A, "fld");
23246
+ content += fields.map((fld) => {
23247
+ const t = getElements(fld, NS_A, "t")[0];
23248
+ return t ? escapeHtml3(t.textContent || "") : "";
23249
+ }).join("");
23250
+ if (!content.trim()) return "";
23251
+ if (shapeTitleFlag) {
23252
+ return `<h2>${content}</h2>
23253
+ `;
23254
+ }
23255
+ const pPr = getElements(para, NS_A, "pPr")[0];
23256
+ const level = pPr ? parseInt(pPr.getAttribute("lvl") || "0", 10) : 0;
23257
+ if (level > 0) {
23258
+ return `<li>${content}</li>
23259
+ `;
23260
+ }
23261
+ return `<p>${content}</p>
23262
+ `;
23263
+ }
23264
+ function tableToHtml(tbl) {
23265
+ const rows = getElements(tbl, NS_A, "tr");
23266
+ if (rows.length === 0) return "";
23267
+ let html = "<table>\n";
23268
+ rows.forEach((row, rowIndex) => {
23269
+ html += "<tr>";
23270
+ const cells = getElements(row, NS_A, "tc");
23271
+ cells.forEach((cell) => {
23272
+ const paragraphs = getElements(cell, NS_A, "p");
23273
+ const text = paragraphs.flatMap((p) => getElements(p, NS_A, "r")).map((r) => getElements(r, NS_A, "t").map((t) => t.textContent || "").join("")).join(" ").trim();
23274
+ const tag = rowIndex === 0 ? "th" : "td";
23275
+ html += `<${tag}>${escapeHtml3(text)}</${tag}>`;
23276
+ });
23277
+ html += "</tr>\n";
23278
+ });
23279
+ html += "</table>\n";
23280
+ return html;
23281
+ }
23282
+ function parseSlide(xmlString) {
23283
+ const warnings = [];
23284
+ const parser = new DOMParser();
23285
+ const doc = parser.parseFromString(xmlString, "application/xml");
23286
+ if (doc.querySelector("parsererror")) {
23287
+ warnings.push("A slide could not be parsed and was skipped.");
23288
+ return { html: "", warnings };
23289
+ }
23290
+ let html = "";
23291
+ const shapes = getElements(doc, NS_P, "sp");
23292
+ for (const shape of shapes) {
23293
+ const txBodies = getElements(shape, NS_P, "txBody");
23294
+ if (txBodies.length === 0) continue;
23295
+ const titleFlag = isTitle(shape);
23296
+ const paragraphs = getElements(txBodies[0], NS_A, "p");
23297
+ let inList = false;
23298
+ for (const para of paragraphs) {
23299
+ const paraHtml = paragraphToHtml(para, titleFlag);
23300
+ if (!paraHtml) continue;
23301
+ if (paraHtml.startsWith("<li>")) {
23302
+ if (!inList) {
23303
+ html += "<ul>\n";
23304
+ inList = true;
23305
+ }
23306
+ } else if (inList) {
23307
+ html += "</ul>\n";
23308
+ inList = false;
23309
+ }
23310
+ html += paraHtml;
23311
+ }
23312
+ if (inList) {
23313
+ html += "</ul>\n";
23314
+ }
23315
+ }
23316
+ const graphicFrames = getElements(doc, NS_P, "graphicFrame");
23317
+ for (const frame of graphicFrames) {
23318
+ const tables = getElements(frame, NS_A, "tbl");
23319
+ for (const tbl of tables) {
23320
+ html += tableToHtml(tbl);
23321
+ }
23322
+ }
23323
+ const pics = getElements(doc, NS_P, "pic");
23324
+ if (pics.length > 0 && !html.trim()) {
23325
+ html += `<p><em>[${pics.length} image${pics.length > 1 ? "s" : ""} on this slide]</em></p>
23326
+ `;
23327
+ } else if (pics.length > 0) {
23328
+ html += `<p><em>[+ ${pics.length} image${pics.length > 1 ? "s" : ""}]</em></p>
23329
+ `;
23330
+ }
23331
+ return { html, warnings };
23332
+ }
23333
+ function parseNotes(xmlString) {
23334
+ const parser = new DOMParser();
23335
+ const doc = parser.parseFromString(xmlString, "application/xml");
23336
+ if (doc.querySelector("parsererror")) return "";
23337
+ const shapes = getElements(doc, NS_P, "sp");
23338
+ const lines = [];
23339
+ for (const shape of shapes) {
23340
+ const ph = getElements(shape, NS_P, "ph")[0];
23341
+ if (!ph) continue;
23342
+ const idx = ph.getAttribute("idx");
23343
+ if (idx !== "1") continue;
23344
+ const txBodies = getElements(shape, NS_P, "txBody");
23345
+ if (txBodies.length === 0) continue;
23346
+ const paragraphs = getElements(txBodies[0], NS_A, "p");
23347
+ for (const para of paragraphs) {
23348
+ const runs = getElements(para, NS_A, "r");
23349
+ const text = runs.map((r) => getElements(r, NS_A, "t").map((t) => t.textContent || "").join("")).join("").trim();
23350
+ if (text) lines.push(text);
23351
+ }
23352
+ }
23353
+ if (lines.length === 0) return "";
23354
+ const notesContent = lines.map((l) => `<p>${escapeHtml3(l)}</p>`).join("\n");
23355
+ return `<blockquote>
23356
+ <p><strong>Speaker notes:</strong></p>
23357
+ ${notesContent}
23358
+ </blockquote>
23359
+ `;
23360
+ }
23361
+ function sortSlides(paths2) {
23362
+ return paths2.slice().sort((a, b) => {
23363
+ const numA = parseInt(a.match(/(\d+)/)?.[1] ?? "0", 10);
23364
+ const numB = parseInt(b.match(/(\d+)/)?.[1] ?? "0", 10);
23365
+ return numA - numB;
23366
+ });
23367
+ }
23368
+ var PptxAdapter = class {
23369
+ async parseToHtml(arrayBuffer) {
23370
+ const warnings = [];
23371
+ try {
23372
+ let zip;
23373
+ try {
23374
+ zip = await JSZip.loadAsync(arrayBuffer);
23375
+ } catch (err) {
23376
+ throw new PptxParseError(
23377
+ "This file does not appear to be a valid PPTX document.",
23378
+ "CORRUPTED",
23379
+ err instanceof Error ? err : void 0
23380
+ );
23381
+ }
23382
+ if (!zip.files["ppt/presentation.xml"]) {
23383
+ throw new PptxParseError(
23384
+ "This file does not appear to be a valid PPTX document.",
23385
+ "INVALID_FORMAT"
23386
+ );
23387
+ }
23388
+ const slidePaths = sortSlides(
23389
+ Object.keys(zip.files).filter(
23390
+ (name) => /^ppt\/slides\/slide\d+\.xml$/.test(name)
23391
+ )
23392
+ );
23393
+ if (slidePaths.length === 0) {
23394
+ warnings.push("No slides were found in this presentation.");
23395
+ return { html: "<p><em>No slides found.</em></p>", slideCount: 0, warnings };
23396
+ }
23397
+ let html = "";
23398
+ for (let i = 0; i < slidePaths.length; i++) {
23399
+ const slideNumber = i + 1;
23400
+ const slidePath = slidePaths[i];
23401
+ html += `<div data-slide-marker="${slideNumber}"></div>
23402
+ `;
23403
+ const slideXml = await zip.files[slidePath].async("string");
23404
+ const { html: slideHtml, warnings: slideWarnings } = parseSlide(slideXml);
23405
+ warnings.push(...slideWarnings);
23406
+ html += slideHtml || `<p><em>[Empty slide]</em></p>
23407
+ `;
23408
+ const notesPath = slidePath.replace("ppt/slides/slide", "ppt/notesSlides/notesSlide");
23409
+ if (zip.files[notesPath]) {
23410
+ const notesXml = await zip.files[notesPath].async("string");
23411
+ const notesHtml = parseNotes(notesXml);
23412
+ if (notesHtml) {
23413
+ html += notesHtml;
23414
+ }
23415
+ }
23416
+ }
23417
+ return { html, slideCount: slidePaths.length, warnings };
23418
+ } catch (error) {
23419
+ if (error instanceof PptxParseError) throw error;
23420
+ const message = error instanceof Error ? error.message : String(error);
23421
+ throw new PptxParseError(
23422
+ `Failed to parse PPTX file: ${message}`,
23423
+ "PARSE_ERROR",
23424
+ error instanceof Error ? error : void 0
23425
+ );
23426
+ }
23427
+ }
23428
+ };
23429
+ var pptxAdapter = new PptxAdapter();
23430
+
22750
23431
  // ../../src/shared/atlassian-detector.ts
22751
23432
  var ATLASSIAN_MARKERS = [
22752
23433
  "ac:structured-macro",
@@ -23372,13 +24053,16 @@ function detectInputType(filePath) {
23372
24053
  ".docx": "docx",
23373
24054
  ".pdf": "pdf",
23374
24055
  ".xlsx": "xlsx",
23375
- ".csv": "csv"
24056
+ ".csv": "csv",
24057
+ ".json": "json",
24058
+ ".xml": "xml",
24059
+ ".pptx": "pptx"
23376
24060
  };
23377
24061
  const type = map[ext];
23378
24062
  if (!type) {
23379
24063
  process.stderr.write(
23380
24064
  `Unsupported file type: ${ext}
23381
- Supported: .html, .htm, .docx, .pdf, .xlsx, .csv
24065
+ Supported: .html, .htm, .docx, .pdf, .xlsx, .csv, .json, .xml, .pptx
23382
24066
  `
23383
24067
  );
23384
24068
  process.exit(1);
@@ -23459,6 +24143,39 @@ async function binaryToMarkdown(buffer, type, format) {
23459
24143
  });
23460
24144
  return converted.markdown;
23461
24145
  }
24146
+ case "json": {
24147
+ const result = await dataAdapter.parseJsonToHtml(arrayBuffer);
24148
+ const sanitized = sanitizeHTML(result.html);
24149
+ const converted = await engine.convert(sanitized, {
24150
+ format,
24151
+ preserveTables: true,
24152
+ preserveLists: true,
24153
+ customRules: []
24154
+ });
24155
+ return converted.markdown;
24156
+ }
24157
+ case "xml": {
24158
+ const result = await dataAdapter.parseXmlToHtml(arrayBuffer);
24159
+ const sanitized = sanitizeHTML(result.html);
24160
+ const converted = await engine.convert(sanitized, {
24161
+ format,
24162
+ preserveTables: true,
24163
+ preserveLists: true,
24164
+ customRules: []
24165
+ });
24166
+ return converted.markdown;
24167
+ }
24168
+ case "pptx": {
24169
+ const result = await pptxAdapter.parseToHtml(arrayBuffer);
24170
+ const sanitized = sanitizeHTML(result.html);
24171
+ const converted = await engine.convert(sanitized, {
24172
+ format,
24173
+ preserveTables: true,
24174
+ preserveLists: true,
24175
+ customRules: []
24176
+ });
24177
+ return converted.markdown;
24178
+ }
23462
24179
  }
23463
24180
  }
23464
24181
  var DEFAULT_PROXY_URL = "https://formatferry.vibingfun.com";
@@ -23630,7 +24347,7 @@ async function convertCommand(options) {
23630
24347
  import fg from "fast-glob";
23631
24348
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
23632
24349
  import { extname as extname2, basename, dirname as dirname2, join } from "path";
23633
- var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".xlsx", ".csv"]);
24350
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".xlsx", ".csv", ".json", ".xml", ".pptx"]);
23634
24351
  function getOutputPath(filePath, outputDir) {
23635
24352
  const ext = extname2(filePath);
23636
24353
  const stem = basename(filePath, ext);
@@ -23653,18 +24370,36 @@ async function convertFile(filePath, format) {
23653
24370
  }
23654
24371
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
23655
24372
  if (ext === ".docx") {
23656
- const r2 = await docxAdapter.parseToHtml(arrayBuffer);
23657
- const c2 = await engine.convert(sanitizeHTML(r2.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
23658
- return c2.markdown;
24373
+ const r = await docxAdapter.parseToHtml(arrayBuffer);
24374
+ const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
24375
+ return c.markdown;
23659
24376
  }
23660
24377
  if (ext === ".pdf") {
23661
- const r2 = await pdfAdapter.parseToText(arrayBuffer);
23662
- const c2 = await engine.convert(sanitizeHTML(r2.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
23663
- return c2.markdown;
24378
+ const r = await pdfAdapter.parseToText(arrayBuffer);
24379
+ const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
24380
+ return c.markdown;
24381
+ }
24382
+ if (ext === ".xlsx" || ext === ".csv") {
24383
+ const r = await spreadsheetAdapter.parseToHtml(arrayBuffer, ext.slice(1), 0);
24384
+ const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
24385
+ return c.markdown;
24386
+ }
24387
+ if (ext === ".json") {
24388
+ const r = await dataAdapter.parseJsonToHtml(arrayBuffer);
24389
+ const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
24390
+ return c.markdown;
24391
+ }
24392
+ if (ext === ".xml") {
24393
+ const r = await dataAdapter.parseXmlToHtml(arrayBuffer);
24394
+ const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
24395
+ return c.markdown;
24396
+ }
24397
+ if (ext === ".pptx") {
24398
+ const r = await pptxAdapter.parseToHtml(arrayBuffer);
24399
+ const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
24400
+ return c.markdown;
23664
24401
  }
23665
- const r = await spreadsheetAdapter.parseToHtml(arrayBuffer, ext.slice(1), 0);
23666
- const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
23667
- return c.markdown;
24402
+ throw new Error(`Unsupported file type: ${ext}`);
23668
24403
  }
23669
24404
  async function batchCommand(options) {
23670
24405
  const parsedTimeout = options.timeout ? parseInt(options.timeout, 10) : void 0;
@@ -23954,8 +24689,8 @@ import { readFileSync as readFileSync4 } from "fs";
23954
24689
  import { resolve, dirname as dirname3 } from "path";
23955
24690
  import { fileURLToPath } from "url";
23956
24691
  var getVersion = () => {
23957
- if ("1.0.12") {
23958
- return "1.0.12";
24692
+ if ("1.0.13") {
24693
+ return "1.0.13";
23959
24694
  }
23960
24695
  try {
23961
24696
  const __dirname = dirname3(fileURLToPath(import.meta.url));
@@ -23968,7 +24703,7 @@ var getVersion = () => {
23968
24703
  }
23969
24704
  };
23970
24705
  var VERSION = getVersion();
23971
- program.name("formatferry").version(VERSION).description("Convert HTML, DOCX, PDF, XLSX, CSV to Markdown").option("-i, --input <path>", "Input file path").option("-o, --output <path>", "Output file path").option("-f, --format <format>", "Markdown format (github, commonmark, slack, discord, reddit, confluence, gdocs, rmarkdown)", "github").option("--url <url>", "Fetch article from URL").option("--batch <glob>", "Batch convert files (Premium)").option("--output-dir <dir>", "Output directory for batch").option("--toonify", "Compress output to TOON format (Premium)").option("--offline", "Skip entitlement check (use cached or free tier)").option("--timeout <ms>", "License server timeout in milliseconds (default: 2000)", "2000").action(async (options) => {
24706
+ program.name("formatferry").version(VERSION).description("Convert HTML, DOCX, PDF, XLSX, CSV, JSON, XML, PPTX to Markdown").option("-i, --input <path>", "Input file path").option("-o, --output <path>", "Output file path").option("-f, --format <format>", "Markdown format (github, commonmark, slack, discord, reddit, confluence, gdocs, rmarkdown)", "github").option("--url <url>", "Fetch article from URL").option("--batch <glob>", "Batch convert files (Premium)").option("--output-dir <dir>", "Output directory for batch").option("--toonify", "Compress output to TOON format (Premium)").option("--offline", "Skip entitlement check (use cached or free tier)").option("--timeout <ms>", "License server timeout in milliseconds (default: 2000)", "2000").action(async (options) => {
23972
24707
  if (options.batch) {
23973
24708
  await batchCommand(options);
23974
24709
  } else {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "formatferry",
3
- "version": "1.0.12",
4
- "description": "FormatFerry CLI — Convert HTML, DOCX, PDF, XLSX, CSV to Markdown",
3
+ "version": "1.0.13",
4
+ "description": "FormatFerry CLI — Convert HTML, DOCX, PDF, XLSX, CSV, JSON, XML, PPTX to Markdown",
5
5
  "keywords": [
6
6
  "markdown",
7
7
  "converter",