formatferry 1.0.12 → 1.0.14
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.
- package/dist/index.js +1410 -70
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -19705,9 +19705,12 @@ var InputSanitizer = class {
|
|
|
19705
19705
|
"table",
|
|
19706
19706
|
"thead",
|
|
19707
19707
|
"tbody",
|
|
19708
|
+
"tfoot",
|
|
19708
19709
|
"tr",
|
|
19709
19710
|
"td",
|
|
19710
19711
|
"th",
|
|
19712
|
+
"figure",
|
|
19713
|
+
"figcaption",
|
|
19711
19714
|
"a",
|
|
19712
19715
|
"img",
|
|
19713
19716
|
"sup",
|
|
@@ -20376,6 +20379,9 @@ var ConversionEngine = class {
|
|
|
20376
20379
|
this.addPdfPageMarkerRules(this.githubService);
|
|
20377
20380
|
this.addPdfPageMarkerRules(this.commonmarkService);
|
|
20378
20381
|
this.addPdfPageMarkerRules(this.customService);
|
|
20382
|
+
this.addSlideMarkerRules(this.githubService);
|
|
20383
|
+
this.addSlideMarkerRules(this.commonmarkService);
|
|
20384
|
+
this.addSlideMarkerRules(this.customService);
|
|
20379
20385
|
this.discordService = this.createDiscordService();
|
|
20380
20386
|
this.redditService = this.createRedditService();
|
|
20381
20387
|
this.rmarkdownService = this.createRMarkdownService();
|
|
@@ -20386,6 +20392,8 @@ var ConversionEngine = class {
|
|
|
20386
20392
|
};
|
|
20387
20393
|
this.slackService = createSlackService(deps);
|
|
20388
20394
|
this.confluenceService = createConfluenceService(deps);
|
|
20395
|
+
this.addSlideMarkerRules(this.slackService);
|
|
20396
|
+
this.addSlideMarkerRules(this.confluenceService);
|
|
20389
20397
|
}
|
|
20390
20398
|
/**
|
|
20391
20399
|
* Create Discord-optimized Turndown service
|
|
@@ -20405,6 +20413,7 @@ var ConversionEngine = class {
|
|
|
20405
20413
|
this.addGoogleDocsRules(service);
|
|
20406
20414
|
this.addListRules(service);
|
|
20407
20415
|
this.addPdfPageMarkerRules(service);
|
|
20416
|
+
this.addSlideMarkerRules(service);
|
|
20408
20417
|
service.addRule("discordUnderline", {
|
|
20409
20418
|
filter: ["u", "ins"],
|
|
20410
20419
|
replacement: (content) => content.trim() ? `__${content}__` : content
|
|
@@ -20583,6 +20592,7 @@ ${plainText}
|
|
|
20583
20592
|
this.addTableRules(service);
|
|
20584
20593
|
this.addListRules(service);
|
|
20585
20594
|
this.addPdfPageMarkerRules(service);
|
|
20595
|
+
this.addSlideMarkerRules(service);
|
|
20586
20596
|
service.addRule("redditSuperscript", {
|
|
20587
20597
|
filter: (node) => node.nodeName === "SUP",
|
|
20588
20598
|
replacement: (content) => {
|
|
@@ -20641,6 +20651,7 @@ ${plainText}
|
|
|
20641
20651
|
this.addTableRules(service);
|
|
20642
20652
|
this.addListRules(service);
|
|
20643
20653
|
this.addPdfPageMarkerRules(service);
|
|
20654
|
+
this.addSlideMarkerRules(service);
|
|
20644
20655
|
service.addRule("rmdSuperscript", {
|
|
20645
20656
|
filter: (node) => node.nodeName === "SUP",
|
|
20646
20657
|
replacement: (content) => {
|
|
@@ -20924,6 +20935,21 @@ ${plainText}
|
|
|
20924
20935
|
});
|
|
20925
20936
|
return markdown;
|
|
20926
20937
|
}
|
|
20938
|
+
addSlideMarkerRules(service) {
|
|
20939
|
+
service.addRule("pptxSlideMarker", {
|
|
20940
|
+
filter: (node) => node.nodeName === "DIV" && node.hasAttribute("data-slide-marker"),
|
|
20941
|
+
replacement: (_content, node) => {
|
|
20942
|
+
const slideNumber = node.getAttribute("data-slide-marker");
|
|
20943
|
+
return `
|
|
20944
|
+
|
|
20945
|
+
---
|
|
20946
|
+
|
|
20947
|
+
**[Slide ${slideNumber}]**
|
|
20948
|
+
|
|
20949
|
+
`;
|
|
20950
|
+
}
|
|
20951
|
+
});
|
|
20952
|
+
}
|
|
20927
20953
|
/**
|
|
20928
20954
|
* Add PDF page marker rules to convert data-page-marker divs to Markdown comments
|
|
20929
20955
|
* This preserves page boundaries from PDF documents (Req 3.5)
|
|
@@ -21348,7 +21374,11 @@ var _flags = {
|
|
|
21348
21374
|
enableXlsx: true,
|
|
21349
21375
|
enablePdf: true,
|
|
21350
21376
|
enableHtml: true,
|
|
21351
|
-
enablePdfTableDetection: true
|
|
21377
|
+
enablePdfTableDetection: true,
|
|
21378
|
+
enableData: true,
|
|
21379
|
+
enablePptx: true,
|
|
21380
|
+
enablePdfEnhanced: true,
|
|
21381
|
+
enablePdfMultiColumn: true
|
|
21352
21382
|
};
|
|
21353
21383
|
function getFeatureFlags() {
|
|
21354
21384
|
return { ..._flags };
|
|
@@ -21453,17 +21483,21 @@ function clusterIntoRows(items) {
|
|
|
21453
21483
|
}
|
|
21454
21484
|
function detectColumns(rows) {
|
|
21455
21485
|
if (rows.length === 0) return [];
|
|
21456
|
-
const
|
|
21486
|
+
const columnRows = selectColumnRows(rows);
|
|
21487
|
+
if (columnRows.length < 2) return [];
|
|
21488
|
+
const allItems = columnRows.flatMap((r) => r.items);
|
|
21457
21489
|
if (allItems.length === 0) return [];
|
|
21458
21490
|
const medianCharWidth = median(allItems.map((it) => estimateCharWidth(it)).filter((w) => w > 0)) || 1;
|
|
21459
21491
|
const gapTol = medianCharWidth * COLUMN_GAP_MERGE_FACTOR;
|
|
21460
|
-
const
|
|
21492
|
+
const separationGap = Math.max(gapTol, medianCharWidth * 1.5);
|
|
21493
|
+
const hardBoundaries = voteColumnBoundaries(columnRows, medianCharWidth);
|
|
21461
21494
|
const intervals = allItems.map((it) => ({ x0: it.x, x1: it.right })).sort((a, b) => a.x0 - b.x0);
|
|
21462
21495
|
const merged = [];
|
|
21463
21496
|
for (const iv of intervals) {
|
|
21464
21497
|
const last = merged[merged.length - 1];
|
|
21465
21498
|
const crossesHardBoundary = last !== void 0 && hardBoundaries.some((b) => b >= last.x1 && b <= iv.x0);
|
|
21466
|
-
|
|
21499
|
+
const hasStrongRowSeparation = last !== void 0 && hasConsistentRowSeparation(columnRows, last, iv, separationGap);
|
|
21500
|
+
if (last && iv.x0 - last.x1 <= gapTol && !crossesHardBoundary && !hasStrongRowSeparation) {
|
|
21467
21501
|
last.x1 = Math.max(last.x1, iv.x1);
|
|
21468
21502
|
} else {
|
|
21469
21503
|
merged.push({ ...iv });
|
|
@@ -21471,16 +21505,50 @@ function detectColumns(rows) {
|
|
|
21471
21505
|
}
|
|
21472
21506
|
const anchors = [];
|
|
21473
21507
|
for (const iv of merged) {
|
|
21474
|
-
const rowsWithHit =
|
|
21508
|
+
const rowsWithHit = columnRows.filter(
|
|
21475
21509
|
(r) => r.items.some((it) => rangeOverlap(it.x, it.right, iv.x0, iv.x1))
|
|
21476
21510
|
);
|
|
21477
|
-
const support = rowsWithHit.length /
|
|
21511
|
+
const support = rowsWithHit.length / columnRows.length;
|
|
21478
21512
|
if (support < COLUMN_SUPPORT_THRESHOLD) continue;
|
|
21479
|
-
const alignment = chooseAlignment(
|
|
21513
|
+
const alignment = chooseAlignment(columnRows, iv);
|
|
21480
21514
|
anchors.push({ x0: iv.x0, x1: iv.x1, alignment, support });
|
|
21481
21515
|
}
|
|
21482
21516
|
return anchors;
|
|
21483
21517
|
}
|
|
21518
|
+
function selectColumnRows(rows) {
|
|
21519
|
+
if (rows.length === 0) return [];
|
|
21520
|
+
const maxItems = Math.max(...rows.map((r) => r.items.length));
|
|
21521
|
+
if (maxItems < 2) return [];
|
|
21522
|
+
const minItems = Math.max(2, Math.ceil(maxItems * 0.5));
|
|
21523
|
+
const denseRows = rows.filter((r) => r.items.length >= minItems);
|
|
21524
|
+
if (denseRows.length < 2) return [];
|
|
21525
|
+
return denseRows;
|
|
21526
|
+
}
|
|
21527
|
+
function trimSparseBoundaryRows(rows) {
|
|
21528
|
+
if (rows.length === 0) return { rows: [], start: 0, end: -1 };
|
|
21529
|
+
let start = 0;
|
|
21530
|
+
let end = rows.length - 1;
|
|
21531
|
+
while (start <= end && rows[start].items.length < 2) start++;
|
|
21532
|
+
while (end >= start && rows[end].items.length < 2) end--;
|
|
21533
|
+
if (start > end) return { rows: [], start, end };
|
|
21534
|
+
return { rows: rows.slice(start, end + 1), start, end };
|
|
21535
|
+
}
|
|
21536
|
+
function hasConsistentRowSeparation(rows, left, right, minGap) {
|
|
21537
|
+
if (rows.length === 0) return false;
|
|
21538
|
+
let supporting = 0;
|
|
21539
|
+
for (const row of rows) {
|
|
21540
|
+
let leftRight = -Infinity;
|
|
21541
|
+
let rightLeft = Infinity;
|
|
21542
|
+
for (const it of row.items) {
|
|
21543
|
+
if (rangeOverlap(it.x, it.right, left.x0, left.x1)) leftRight = Math.max(leftRight, it.right);
|
|
21544
|
+
if (rangeOverlap(it.x, it.right, right.x0, right.x1)) rightLeft = Math.min(rightLeft, it.x);
|
|
21545
|
+
}
|
|
21546
|
+
if (leftRight > -Infinity && rightLeft < Infinity && rightLeft - leftRight > minGap) {
|
|
21547
|
+
supporting++;
|
|
21548
|
+
}
|
|
21549
|
+
}
|
|
21550
|
+
return supporting / rows.length >= COLUMN_SUPPORT_THRESHOLD;
|
|
21551
|
+
}
|
|
21484
21552
|
function coalesceMultiLineCells(rows, columns) {
|
|
21485
21553
|
if (rows.length < 2 || columns.length < 2) return rows;
|
|
21486
21554
|
const gaps = rowGaps(rows);
|
|
@@ -21557,12 +21625,15 @@ function findTableRegions(items, options = {}) {
|
|
|
21557
21625
|
let best = null;
|
|
21558
21626
|
for (let j = i + 1; j < rows.length; j++) {
|
|
21559
21627
|
const run = rows.slice(i, j + 1);
|
|
21560
|
-
const
|
|
21628
|
+
const trimmed = trimSparseBoundaryRows(run);
|
|
21629
|
+
if (trimmed.rows.length < 2) {
|
|
21630
|
+
continue;
|
|
21631
|
+
}
|
|
21632
|
+
const columns = detectColumns(trimmed.rows);
|
|
21561
21633
|
if (columns.length < 2) {
|
|
21562
|
-
if (best) break;
|
|
21563
21634
|
continue;
|
|
21564
21635
|
}
|
|
21565
|
-
const coalesced = coalesceMultiLineCells(
|
|
21636
|
+
const coalesced = coalesceMultiLineCells(trimmed.rows, columns);
|
|
21566
21637
|
if (coalesced.length < 2) {
|
|
21567
21638
|
continue;
|
|
21568
21639
|
}
|
|
@@ -21574,20 +21645,33 @@ function findTableRegions(items, options = {}) {
|
|
|
21574
21645
|
if (best) break;
|
|
21575
21646
|
else continue;
|
|
21576
21647
|
}
|
|
21577
|
-
const
|
|
21648
|
+
const rawConfidence = scoreCandidate(coalesced, columns);
|
|
21649
|
+
const bbox = computeBbox(coalesced);
|
|
21650
|
+
const ruleBonus = (() => {
|
|
21651
|
+
const hRules = options.ruleSeparators?.filter((r) => r.orientation === "h") ?? [];
|
|
21652
|
+
if (hRules.length === 0) return 0;
|
|
21653
|
+
const tol = 10;
|
|
21654
|
+
const hasTop = hRules.some(
|
|
21655
|
+
(r) => Math.abs(r.position - bbox.y1) <= tol && r.span[0] <= bbox.x0 + tol && r.span[1] >= bbox.x1 - tol
|
|
21656
|
+
);
|
|
21657
|
+
const hasBottom = hRules.some(
|
|
21658
|
+
(r) => Math.abs(r.position - bbox.y0) <= tol && r.span[0] <= bbox.x0 + tol && r.span[1] >= bbox.x1 - tol
|
|
21659
|
+
);
|
|
21660
|
+
return (hasTop ? 0.075 : 0) + (hasBottom ? 0.075 : 0);
|
|
21661
|
+
})();
|
|
21662
|
+
const confidence = Math.min(1, rawConfidence + ruleBonus);
|
|
21578
21663
|
if (confidence < MIN_CONFIDENCE_EMIT) {
|
|
21579
21664
|
if (best) break;
|
|
21580
21665
|
continue;
|
|
21581
21666
|
}
|
|
21582
|
-
const
|
|
21583
|
-
const medianH = median(run.flatMap((r) => r.items.map((it) => it.height))) || 0;
|
|
21667
|
+
const medianH = median(coalesced.flatMap((r) => r.items.map((it) => it.height))) || 0;
|
|
21584
21668
|
const mayContinue = options.pageBottomY !== void 0 && medianH > 0 && bbox.y0 - options.pageBottomY < medianH * 2;
|
|
21585
21669
|
best = {
|
|
21586
21670
|
rows: coalesced,
|
|
21587
21671
|
columns,
|
|
21588
21672
|
confidence,
|
|
21589
21673
|
bbox,
|
|
21590
|
-
rowRange: { start: i, end:
|
|
21674
|
+
rowRange: { start: i + trimmed.start, end: i + trimmed.end },
|
|
21591
21675
|
mayContinue
|
|
21592
21676
|
};
|
|
21593
21677
|
}
|
|
@@ -21887,6 +21971,142 @@ function looksLikeHeader(row) {
|
|
|
21887
21971
|
return boldish / n >= 0.5 || allCaps / n >= 0.5 || trailingColon / n >= 0.5;
|
|
21888
21972
|
}
|
|
21889
21973
|
|
|
21974
|
+
// ../../src/shared/adapters/pdf-struct-tree-adapter.ts
|
|
21975
|
+
function buildMcidMap(items) {
|
|
21976
|
+
const map = /* @__PURE__ */ new Map();
|
|
21977
|
+
let currentId = null;
|
|
21978
|
+
for (const item of items) {
|
|
21979
|
+
const itemType = "type" in item ? item.type : void 0;
|
|
21980
|
+
if (itemType === "beginMarkedContentProps") {
|
|
21981
|
+
const mc = item;
|
|
21982
|
+
if (mc.id != null) {
|
|
21983
|
+
currentId = mc.id;
|
|
21984
|
+
if (!map.has(currentId)) map.set(currentId, "");
|
|
21985
|
+
}
|
|
21986
|
+
} else if (itemType === "endMarkedContent") {
|
|
21987
|
+
currentId = null;
|
|
21988
|
+
} else if ("str" in item && currentId !== null) {
|
|
21989
|
+
const existing = map.get(currentId) ?? "";
|
|
21990
|
+
map.set(currentId, existing + item.str);
|
|
21991
|
+
}
|
|
21992
|
+
}
|
|
21993
|
+
return map;
|
|
21994
|
+
}
|
|
21995
|
+
function countNodes(node, limit) {
|
|
21996
|
+
let n = 1;
|
|
21997
|
+
if ("children" in node && node.children) {
|
|
21998
|
+
for (const child of node.children) {
|
|
21999
|
+
n += countNodes(child, limit);
|
|
22000
|
+
if (n >= limit) return n;
|
|
22001
|
+
}
|
|
22002
|
+
}
|
|
22003
|
+
return n;
|
|
22004
|
+
}
|
|
22005
|
+
function esc(str) {
|
|
22006
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
22007
|
+
}
|
|
22008
|
+
function isContent(node) {
|
|
22009
|
+
return "id" in node && !("role" in node);
|
|
22010
|
+
}
|
|
22011
|
+
function walkChildren(children, mcidMap, depth) {
|
|
22012
|
+
return children.map((c) => walkNode(c, mcidMap, depth)).join("");
|
|
22013
|
+
}
|
|
22014
|
+
function walkNode(node, mcidMap, depth = 0) {
|
|
22015
|
+
if (depth > 50) return "";
|
|
22016
|
+
if (isContent(node)) {
|
|
22017
|
+
const text = mcidMap.get(node.id) ?? "";
|
|
22018
|
+
return esc(text);
|
|
22019
|
+
}
|
|
22020
|
+
const children = node.children ?? [];
|
|
22021
|
+
const inner = walkChildren(children, mcidMap, depth + 1);
|
|
22022
|
+
switch (node.role) {
|
|
22023
|
+
case "H":
|
|
22024
|
+
return `<h1>${inner}</h1>`;
|
|
22025
|
+
case "H1":
|
|
22026
|
+
return `<h1>${inner}</h1>`;
|
|
22027
|
+
case "H2":
|
|
22028
|
+
return `<h2>${inner}</h2>`;
|
|
22029
|
+
case "H3":
|
|
22030
|
+
return `<h3>${inner}</h3>`;
|
|
22031
|
+
case "H4":
|
|
22032
|
+
return `<h4>${inner}</h4>`;
|
|
22033
|
+
case "H5":
|
|
22034
|
+
return `<h5>${inner}</h5>`;
|
|
22035
|
+
case "H6":
|
|
22036
|
+
return `<h6>${inner}</h6>`;
|
|
22037
|
+
case "P":
|
|
22038
|
+
return inner ? `<p>${inner}</p>` : "";
|
|
22039
|
+
case "L":
|
|
22040
|
+
return inner ? `<ul>${inner}</ul>` : "";
|
|
22041
|
+
case "LI":
|
|
22042
|
+
return inner ? `<li>${inner}</li>` : "";
|
|
22043
|
+
case "Lbl":
|
|
22044
|
+
return inner ? `<span>${inner}</span> ` : "";
|
|
22045
|
+
case "LBody":
|
|
22046
|
+
return inner;
|
|
22047
|
+
case "Table":
|
|
22048
|
+
return inner ? `<table>${inner}</table>` : "";
|
|
22049
|
+
case "THead":
|
|
22050
|
+
return inner ? `<thead>${inner}</thead>` : "";
|
|
22051
|
+
case "TBody":
|
|
22052
|
+
return inner ? `<tbody>${inner}</tbody>` : "";
|
|
22053
|
+
case "TFoot":
|
|
22054
|
+
return inner ? `<tfoot>${inner}</tfoot>` : "";
|
|
22055
|
+
case "TR":
|
|
22056
|
+
return inner ? `<tr>${inner}</tr>` : "";
|
|
22057
|
+
case "TH":
|
|
22058
|
+
return `<th>${inner}</th>`;
|
|
22059
|
+
case "TD":
|
|
22060
|
+
return `<td>${inner}</td>`;
|
|
22061
|
+
case "Figure": {
|
|
22062
|
+
const alt = node.alt ?? "";
|
|
22063
|
+
const caption = alt ? `<figcaption>${esc(alt)}</figcaption>` : "";
|
|
22064
|
+
return `<figure>${caption}${inner}</figure>`;
|
|
22065
|
+
}
|
|
22066
|
+
case "BlockQuote":
|
|
22067
|
+
return inner ? `<blockquote>${inner}</blockquote>` : "";
|
|
22068
|
+
case "Code":
|
|
22069
|
+
return inner ? `<code>${inner}</code>` : "";
|
|
22070
|
+
// Inline / passthrough roles
|
|
22071
|
+
case "Span":
|
|
22072
|
+
case "Link":
|
|
22073
|
+
case "Reference":
|
|
22074
|
+
case "Note":
|
|
22075
|
+
case "Annot":
|
|
22076
|
+
case "Strong":
|
|
22077
|
+
case "Em":
|
|
22078
|
+
case "Sub":
|
|
22079
|
+
case "Sup":
|
|
22080
|
+
return inner;
|
|
22081
|
+
// Grouping roles — just pass through children
|
|
22082
|
+
default:
|
|
22083
|
+
return inner;
|
|
22084
|
+
}
|
|
22085
|
+
}
|
|
22086
|
+
async function structTreeToHtml(page) {
|
|
22087
|
+
let structTree;
|
|
22088
|
+
try {
|
|
22089
|
+
structTree = await page.getStructTree();
|
|
22090
|
+
} catch {
|
|
22091
|
+
return null;
|
|
22092
|
+
}
|
|
22093
|
+
if (!structTree || !structTree.children || structTree.children.length === 0) {
|
|
22094
|
+
return null;
|
|
22095
|
+
}
|
|
22096
|
+
if (countNodes(structTree, 5) < 5) return null;
|
|
22097
|
+
let textContent;
|
|
22098
|
+
try {
|
|
22099
|
+
textContent = await page.getTextContent({ includeMarkedContent: true });
|
|
22100
|
+
} catch {
|
|
22101
|
+
return null;
|
|
22102
|
+
}
|
|
22103
|
+
const mcidMap = buildMcidMap(textContent.items);
|
|
22104
|
+
const html = walkNode(structTree, mcidMap);
|
|
22105
|
+
const textOnly = html.replace(/<[^>]+>/g, "").trim();
|
|
22106
|
+
if (!textOnly) return null;
|
|
22107
|
+
return html;
|
|
22108
|
+
}
|
|
22109
|
+
|
|
21890
22110
|
// ../../src/shared/adapters/pdf-adapter.ts
|
|
21891
22111
|
var PdfParseError = class extends Error {
|
|
21892
22112
|
constructor(message, code, originalError) {
|
|
@@ -21903,6 +22123,52 @@ var FORM_LABEL_GAP_FACTOR = 3;
|
|
|
21903
22123
|
function escapeHtml(str) {
|
|
21904
22124
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
21905
22125
|
}
|
|
22126
|
+
function computeFontSize(transform) {
|
|
22127
|
+
return Math.sqrt(transform[2] ** 2 + transform[3] ** 2);
|
|
22128
|
+
}
|
|
22129
|
+
function normalizeLigatures(text) {
|
|
22130
|
+
return text.normalize("NFC").replace(/ff/g, "ff").replace(/fi/g, "fi").replace(/fl/g, "fl").replace(/ffi/g, "ffi").replace(/ffl/g, "ffl");
|
|
22131
|
+
}
|
|
22132
|
+
function hasStructTree(page) {
|
|
22133
|
+
return typeof page.getStructTree === "function";
|
|
22134
|
+
}
|
|
22135
|
+
function isSafeUrl(url) {
|
|
22136
|
+
return /^(?:https?|mailto|ftp):/i.test(url);
|
|
22137
|
+
}
|
|
22138
|
+
function detectFontStyle(fontName) {
|
|
22139
|
+
if (!fontName) return null;
|
|
22140
|
+
const bold = /(Bold|Black|Heavy|Semibold|Demi)/i.test(fontName) && !/Blackletter/i.test(fontName);
|
|
22141
|
+
const italic = /(Italic|Oblique|Slanted)/i.test(fontName);
|
|
22142
|
+
if (bold && italic) return "bold-italic";
|
|
22143
|
+
if (bold) return "bold";
|
|
22144
|
+
if (italic) return "italic";
|
|
22145
|
+
return null;
|
|
22146
|
+
}
|
|
22147
|
+
function buildOutlineSet(outline) {
|
|
22148
|
+
const set = /* @__PURE__ */ new Set();
|
|
22149
|
+
if (!outline) return set;
|
|
22150
|
+
function walk(items) {
|
|
22151
|
+
for (const item of items) {
|
|
22152
|
+
if (item.title) {
|
|
22153
|
+
set.add(normalizeLigatures(item.title).replace(/\s+/g, " ").trim().toLowerCase());
|
|
22154
|
+
}
|
|
22155
|
+
if (item.items) walk(item.items);
|
|
22156
|
+
}
|
|
22157
|
+
}
|
|
22158
|
+
walk(outline);
|
|
22159
|
+
return set;
|
|
22160
|
+
}
|
|
22161
|
+
function fuzzyMatchOutline(text, outlineTitles) {
|
|
22162
|
+
if (outlineTitles.size === 0) return false;
|
|
22163
|
+
for (const title of outlineTitles) {
|
|
22164
|
+
if (title.length < 3) continue;
|
|
22165
|
+
if (text.includes(title) || title.includes(text)) {
|
|
22166
|
+
const ratio = Math.min(text.length, title.length) / Math.max(text.length, title.length);
|
|
22167
|
+
if (ratio >= 0.8) return true;
|
|
22168
|
+
}
|
|
22169
|
+
}
|
|
22170
|
+
return false;
|
|
22171
|
+
}
|
|
21906
22172
|
function detectRotation(transform) {
|
|
21907
22173
|
const EPS = 1e-3;
|
|
21908
22174
|
const [sx, shy, shx, sy] = transform;
|
|
@@ -21926,6 +22192,7 @@ function textItemToPositioned(item) {
|
|
|
21926
22192
|
const descent = height * DESCENDER_BUDGET_FACTOR;
|
|
21927
22193
|
const y = ty - descent;
|
|
21928
22194
|
const paddedHeight = height + descent;
|
|
22195
|
+
const fs = computeFontSize(item.transform);
|
|
21929
22196
|
return {
|
|
21930
22197
|
str: item.str,
|
|
21931
22198
|
x: tx,
|
|
@@ -21935,7 +22202,8 @@ function textItemToPositioned(item) {
|
|
|
21935
22202
|
right: tx + width,
|
|
21936
22203
|
rotation,
|
|
21937
22204
|
fontName: item.fontName,
|
|
21938
|
-
source: "text"
|
|
22205
|
+
source: "text",
|
|
22206
|
+
fontSize: fs > 0 ? fs : rawHeight > 0 ? rawHeight : void 0
|
|
21939
22207
|
};
|
|
21940
22208
|
}
|
|
21941
22209
|
function widgetToPositionedItems(ann) {
|
|
@@ -22035,9 +22303,309 @@ function itemsToLinearText(items) {
|
|
|
22035
22303
|
}
|
|
22036
22304
|
return lines.join("\n").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
22037
22305
|
}
|
|
22038
|
-
function
|
|
22306
|
+
function renderTextBlockEnhanced(items, medianPageFontSize, outlineTitles) {
|
|
22307
|
+
if (items.length === 0) return { htmlBlocks: [], textContent: "" };
|
|
22308
|
+
const sorted = [...items].sort(
|
|
22309
|
+
(a, b) => b.y + b.height - (a.y + a.height) || a.x - b.x
|
|
22310
|
+
);
|
|
22311
|
+
const rows = [];
|
|
22312
|
+
for (const it of sorted) {
|
|
22313
|
+
const last = rows[rows.length - 1];
|
|
22314
|
+
if (last && last.some(
|
|
22315
|
+
(li) => Math.max(
|
|
22316
|
+
0,
|
|
22317
|
+
Math.min(li.y + li.height, it.y + it.height) - Math.max(li.y, it.y)
|
|
22318
|
+
) / Math.max(Math.min(li.height, it.height), 1e-6) >= 0.5
|
|
22319
|
+
)) {
|
|
22320
|
+
last.push(it);
|
|
22321
|
+
} else {
|
|
22322
|
+
rows.push([it]);
|
|
22323
|
+
}
|
|
22324
|
+
}
|
|
22325
|
+
const htmlBlocks = [];
|
|
22326
|
+
const textLines = [];
|
|
22327
|
+
const col3Rows = getFeatureFlags().enablePdfMultiColumn ? detectAndReorderColumns(rows) : rows;
|
|
22328
|
+
const finalRows = repairRowHyphenation(col3Rows);
|
|
22329
|
+
let pendingListKind = null;
|
|
22330
|
+
const pendingListHtml = [];
|
|
22331
|
+
const flushPendingList = () => {
|
|
22332
|
+
if (pendingListHtml.length === 0) return;
|
|
22333
|
+
const tag = pendingListKind === "ol" ? "ol" : "ul";
|
|
22334
|
+
htmlBlocks.push(`<${tag}>${pendingListHtml.join("")}</${tag}>`);
|
|
22335
|
+
pendingListKind = null;
|
|
22336
|
+
pendingListHtml.length = 0;
|
|
22337
|
+
};
|
|
22338
|
+
for (const row of finalRows) {
|
|
22339
|
+
row.sort((a, b) => a.x - b.x);
|
|
22340
|
+
const fsSamples = row.map((it) => it.fontSize ?? 0).filter((f) => f > 0);
|
|
22341
|
+
const rowFs = fsSamples.length > 0 ? [...fsSamples].sort((a, b) => a - b)[Math.floor(fsSamples.length / 2)] : medianPageFontSize;
|
|
22342
|
+
const rowWidths = row.map((it) => it.str.length > 0 ? it.width / it.str.length : 0).filter((w) => w > 0);
|
|
22343
|
+
const rowMedianCharWidth = rowWidths.length ? [...rowWidths].sort((a, b) => a - b)[Math.floor(rowWidths.length / 2)] || 1 : 1;
|
|
22344
|
+
const rowLabelGap = rowMedianCharWidth * FORM_LABEL_GAP_FACTOR;
|
|
22345
|
+
let plainText = "";
|
|
22346
|
+
let inlineHtml = "";
|
|
22347
|
+
let hasAnyStyle = false;
|
|
22348
|
+
for (let i = 0; i < row.length; i++) {
|
|
22349
|
+
const it = row[i];
|
|
22350
|
+
let sep = "";
|
|
22351
|
+
if (i > 0) {
|
|
22352
|
+
const prev = row[i - 1];
|
|
22353
|
+
const gap = it.x - prev.right;
|
|
22354
|
+
const prevStr = prev.str.trimEnd();
|
|
22355
|
+
sep = /:$/.test(prevStr) && gap > rowLabelGap ? "\xA0\xA0" : " ";
|
|
22356
|
+
}
|
|
22357
|
+
plainText += sep + it.str;
|
|
22358
|
+
const escaped = escapeHtml(normalizeLigatures(it.str));
|
|
22359
|
+
const style = detectFontStyle(it.fontName);
|
|
22360
|
+
if (style) hasAnyStyle = true;
|
|
22361
|
+
let styledPart = style === "bold-italic" ? `<strong><em>${escaped}</em></strong>` : style === "bold" ? `<strong>${escaped}</strong>` : style === "italic" ? `<em>${escaped}</em>` : escaped;
|
|
22362
|
+
if (it.linkUrl && isSafeUrl(it.linkUrl)) {
|
|
22363
|
+
styledPart = `<a href="${escapeHtml(it.linkUrl)}">${styledPart}</a>`;
|
|
22364
|
+
}
|
|
22365
|
+
inlineHtml += (i > 0 ? escapeHtml(sep) : "") + styledPart;
|
|
22366
|
+
}
|
|
22367
|
+
plainText = plainText.replace(/[ \t]+/g, " ").trim();
|
|
22368
|
+
if (!plainText) continue;
|
|
22369
|
+
const normalizedPlain = normalizeLigatures(plainText);
|
|
22370
|
+
const normalized = normalizedPlain.replace(/\s+/g, " ").trim().toLowerCase();
|
|
22371
|
+
if (getFeatureFlags().enablePdfEnhanced) {
|
|
22372
|
+
const listMatch = detectListMarker(normalizedPlain);
|
|
22373
|
+
if (listMatch) {
|
|
22374
|
+
if (pendingListKind !== null && pendingListKind !== listMatch.kind) {
|
|
22375
|
+
flushPendingList();
|
|
22376
|
+
}
|
|
22377
|
+
pendingListKind = listMatch.kind;
|
|
22378
|
+
const escapedBody = escapeHtml(normalizeLigatures(listMatch.bodyText));
|
|
22379
|
+
pendingListHtml.push(`<li>${escapedBody}</li>`);
|
|
22380
|
+
textLines.push(normalizedPlain);
|
|
22381
|
+
continue;
|
|
22382
|
+
}
|
|
22383
|
+
}
|
|
22384
|
+
flushPendingList();
|
|
22385
|
+
const ratio = medianPageFontSize > 0 ? rowFs / medianPageFontSize : 1;
|
|
22386
|
+
let headingLevel = 0;
|
|
22387
|
+
if (ratio >= 1.5) headingLevel = 1;
|
|
22388
|
+
else if (ratio >= 1.25) headingLevel = 2;
|
|
22389
|
+
else if (ratio >= 1.1) headingLevel = 3;
|
|
22390
|
+
if (headingLevel === 0 && outlineTitles.size > 0) {
|
|
22391
|
+
if (outlineTitles.has(normalized) || fuzzyMatchOutline(normalized, outlineTitles)) {
|
|
22392
|
+
headingLevel = 2;
|
|
22393
|
+
}
|
|
22394
|
+
}
|
|
22395
|
+
textLines.push(normalizedPlain);
|
|
22396
|
+
if (headingLevel > 0) {
|
|
22397
|
+
htmlBlocks.push(`<h${headingLevel}>${inlineHtml}</h${headingLevel}>`);
|
|
22398
|
+
} else {
|
|
22399
|
+
const hasWideGap = /( {3,}|\u00A0\u00A0|\t)/.test(normalizedPlain);
|
|
22400
|
+
const isShort = normalizedPlain.length < 100 && !/[.,:;!?]$/.test(normalizedPlain) && !/\n/.test(normalizedPlain) && !hasWideGap;
|
|
22401
|
+
if (isShort && !hasAnyStyle) {
|
|
22402
|
+
htmlBlocks.push(`<p><strong>${inlineHtml}</strong></p>`);
|
|
22403
|
+
} else {
|
|
22404
|
+
htmlBlocks.push(`<p>${inlineHtml}</p>`);
|
|
22405
|
+
}
|
|
22406
|
+
}
|
|
22407
|
+
}
|
|
22408
|
+
flushPendingList();
|
|
22409
|
+
return {
|
|
22410
|
+
htmlBlocks,
|
|
22411
|
+
textContent: textLines.join("\n").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim()
|
|
22412
|
+
};
|
|
22413
|
+
}
|
|
22414
|
+
function repairRowHyphenation(rows) {
|
|
22415
|
+
if (rows.length < 2) return rows;
|
|
22416
|
+
const result = [];
|
|
22417
|
+
let i = 0;
|
|
22418
|
+
while (i < rows.length) {
|
|
22419
|
+
if (i + 1 < rows.length) {
|
|
22420
|
+
const row = rows[i];
|
|
22421
|
+
const nextRow = rows[i + 1];
|
|
22422
|
+
const lastItem = [...row].sort((a, b) => b.right - a.right)[0];
|
|
22423
|
+
const firstNext = [...nextRow].sort((a, b) => a.x - b.x)[0];
|
|
22424
|
+
if (lastItem && firstNext && /-$/.test(lastItem.str) && new RegExp("^\\p{Ll}", "u").test(firstNext.str)) {
|
|
22425
|
+
const fixed = { ...lastItem, str: lastItem.str.slice(0, -1) };
|
|
22426
|
+
result.push([...row.map((it) => it === lastItem ? fixed : it), ...nextRow]);
|
|
22427
|
+
i += 2;
|
|
22428
|
+
continue;
|
|
22429
|
+
}
|
|
22430
|
+
}
|
|
22431
|
+
result.push(rows[i]);
|
|
22432
|
+
i++;
|
|
22433
|
+
}
|
|
22434
|
+
return result;
|
|
22435
|
+
}
|
|
22436
|
+
function detectListMarker(plainText) {
|
|
22437
|
+
const bulletMatch = /^[•◦◆▪▸–\-*·]\s+(.+)/u.exec(plainText);
|
|
22438
|
+
if (bulletMatch) return { kind: "ul", bodyText: bulletMatch[1] };
|
|
22439
|
+
const numberedMatch = /^\d+[.)]\s+(.+)/.exec(plainText);
|
|
22440
|
+
if (numberedMatch) return { kind: "ol", bodyText: numberedMatch[1] };
|
|
22441
|
+
const letteredMatch = /^[a-z][.)]\s+(.+)/.exec(plainText);
|
|
22442
|
+
if (letteredMatch) return { kind: "ol", bodyText: letteredMatch[1] };
|
|
22443
|
+
return null;
|
|
22444
|
+
}
|
|
22445
|
+
function detectAndReorderColumns(rows) {
|
|
22446
|
+
if (rows.length < 4) return rows;
|
|
22447
|
+
const allItems = rows.flat();
|
|
22448
|
+
const itemWidths = allItems.filter((it) => it.width > 0 && it.str.trim().length > 0).map((it) => it.width);
|
|
22449
|
+
if (itemWidths.length === 0) return rows;
|
|
22450
|
+
const sortedWidths = [...itemWidths].sort((a, b) => a - b);
|
|
22451
|
+
const medianWidth = sortedWidths[Math.floor(sortedWidths.length / 2)];
|
|
22452
|
+
const gutterThreshold = medianWidth * 3;
|
|
22453
|
+
const xMin = allItems.reduce((m, it) => Math.min(m, it.x), Infinity);
|
|
22454
|
+
const xMax = allItems.reduce((m, it) => Math.max(m, it.right), -Infinity);
|
|
22455
|
+
const midLeft = xMin + (xMax - xMin) * 0.25;
|
|
22456
|
+
const midRight = xMin + (xMax - xMin) * 0.75;
|
|
22457
|
+
const gutterMidpoints = [];
|
|
22458
|
+
for (const row of rows) {
|
|
22459
|
+
if (row.length < 2) continue;
|
|
22460
|
+
const rowSorted = [...row].sort((a, b) => a.x - b.x);
|
|
22461
|
+
for (let i = 1; i < rowSorted.length; i++) {
|
|
22462
|
+
const prev = rowSorted[i - 1];
|
|
22463
|
+
const curr = rowSorted[i];
|
|
22464
|
+
const gap = curr.x - prev.right;
|
|
22465
|
+
if (gap < gutterThreshold) continue;
|
|
22466
|
+
const midX = (prev.right + curr.x) / 2;
|
|
22467
|
+
if (midX >= midLeft && midX <= midRight) {
|
|
22468
|
+
gutterMidpoints.push(midX);
|
|
22469
|
+
break;
|
|
22470
|
+
}
|
|
22471
|
+
}
|
|
22472
|
+
}
|
|
22473
|
+
const multiItemRowCount = rows.filter((r) => r.length >= 2).length;
|
|
22474
|
+
const minVotes = Math.max(2, Math.ceil(multiItemRowCount * 0.35));
|
|
22475
|
+
if (gutterMidpoints.length < minVotes) return rows;
|
|
22476
|
+
const sortedGutters = [...gutterMidpoints].sort((a, b) => a - b);
|
|
22477
|
+
const gutterX = sortedGutters[Math.floor(sortedGutters.length / 2)];
|
|
22478
|
+
const leftRows = [];
|
|
22479
|
+
const rightRows = [];
|
|
22480
|
+
for (const row of rows) {
|
|
22481
|
+
const leftItems = row.filter((it) => it.x + it.width / 2 <= gutterX);
|
|
22482
|
+
const rightItems = row.filter((it) => it.x + it.width / 2 > gutterX);
|
|
22483
|
+
if (leftItems.length > 0) leftRows.push(leftItems);
|
|
22484
|
+
if (rightItems.length > 0) rightRows.push(rightItems);
|
|
22485
|
+
}
|
|
22486
|
+
if (leftRows.length < 2 || rightRows.length < 2) return rows;
|
|
22487
|
+
const sortTopDown = (arr) => [...arr].sort((a, b) => {
|
|
22488
|
+
const aTop = a.reduce((m, it) => Math.max(m, it.y + it.height), -Infinity);
|
|
22489
|
+
const bTop = b.reduce((m, it) => Math.max(m, it.y + it.height), -Infinity);
|
|
22490
|
+
return bTop - aTop;
|
|
22491
|
+
});
|
|
22492
|
+
return [...sortTopDown(leftRows), ...sortTopDown(rightRows)];
|
|
22493
|
+
}
|
|
22494
|
+
function buildHeaderFooterSuppressSet(pageDataArray) {
|
|
22495
|
+
const totalPages = pageDataArray.length;
|
|
22496
|
+
if (totalPages < 3) return /* @__PURE__ */ new Set();
|
|
22497
|
+
const HEADER_THRESHOLD = 0.92;
|
|
22498
|
+
const FOOTER_THRESHOLD = 0.08;
|
|
22499
|
+
const Y_TOLERANCE = 5;
|
|
22500
|
+
const MIN_PAGE_FRACTION = 0.4;
|
|
22501
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
22502
|
+
for (const { pageNum, items, viewportHeight } of pageDataArray) {
|
|
22503
|
+
for (const it of items) {
|
|
22504
|
+
const itemTop = it.y + it.height;
|
|
22505
|
+
const inHeader = itemTop > viewportHeight * HEADER_THRESHOLD;
|
|
22506
|
+
const inFooter = it.y < viewportHeight * FOOTER_THRESHOLD;
|
|
22507
|
+
if (!inHeader && !inFooter) continue;
|
|
22508
|
+
const normalized = it.str.replace(/\s+/g, " ").trim().toLowerCase();
|
|
22509
|
+
if (normalized.length === 0) continue;
|
|
22510
|
+
const yBucket = Math.round(it.y / Y_TOLERANCE) * Y_TOLERANCE;
|
|
22511
|
+
const key = `${normalized}@${yBucket}`;
|
|
22512
|
+
if (!occurrences.has(key)) occurrences.set(key, /* @__PURE__ */ new Set());
|
|
22513
|
+
occurrences.get(key).add(pageNum);
|
|
22514
|
+
}
|
|
22515
|
+
}
|
|
22516
|
+
const suppressSet = /* @__PURE__ */ new Set();
|
|
22517
|
+
for (const [key, pageSet] of occurrences) {
|
|
22518
|
+
if (pageSet.size / totalPages >= MIN_PAGE_FRACTION) {
|
|
22519
|
+
suppressSet.add(key);
|
|
22520
|
+
}
|
|
22521
|
+
}
|
|
22522
|
+
return suppressSet;
|
|
22523
|
+
}
|
|
22524
|
+
function isHeaderFooterItem(it, suppressSet, Y_TOLERANCE = 5) {
|
|
22525
|
+
if (suppressSet.size === 0) return false;
|
|
22526
|
+
const normalized = it.str.replace(/\s+/g, " ").trim().toLowerCase();
|
|
22527
|
+
if (normalized.length === 0) return false;
|
|
22528
|
+
const yBucket = Math.round(it.y / Y_TOLERANCE) * Y_TOLERANCE;
|
|
22529
|
+
return suppressSet.has(`${normalized}@${yBucket}`);
|
|
22530
|
+
}
|
|
22531
|
+
var PDF_OP = { moveTo: 13, lineTo: 14, closePath: 18, rectangle: 92 };
|
|
22532
|
+
function extractVectorLines(fnArray, argsArray, pageWidth, pageHeight) {
|
|
22533
|
+
const MIN_H_SPAN = pageWidth * 0.3;
|
|
22534
|
+
const MIN_V_SPAN = pageHeight * 0.3;
|
|
22535
|
+
const lines = [];
|
|
22536
|
+
let curX = 0;
|
|
22537
|
+
let curY = 0;
|
|
22538
|
+
let pathStartX = 0;
|
|
22539
|
+
let pathStartY = 0;
|
|
22540
|
+
const pushIfSignificant = (x0, y0, x1, y1) => {
|
|
22541
|
+
const dx = Math.abs(x1 - x0);
|
|
22542
|
+
const dy = Math.abs(y1 - y0);
|
|
22543
|
+
if (dy < 2 && dx >= MIN_H_SPAN) {
|
|
22544
|
+
lines.push({
|
|
22545
|
+
orientation: "h",
|
|
22546
|
+
position: (y0 + y1) / 2,
|
|
22547
|
+
span: [Math.min(x0, x1), Math.max(x0, x1)]
|
|
22548
|
+
});
|
|
22549
|
+
} else if (dx < 2 && dy >= MIN_V_SPAN) {
|
|
22550
|
+
lines.push({
|
|
22551
|
+
orientation: "v",
|
|
22552
|
+
position: (x0 + x1) / 2,
|
|
22553
|
+
span: [Math.min(y0, y1), Math.max(y0, y1)]
|
|
22554
|
+
});
|
|
22555
|
+
}
|
|
22556
|
+
};
|
|
22557
|
+
for (let i = 0; i < fnArray.length; i++) {
|
|
22558
|
+
const op = fnArray[i];
|
|
22559
|
+
const args = argsArray[i] ?? [];
|
|
22560
|
+
if (op === PDF_OP.moveTo) {
|
|
22561
|
+
curX = args[0] ?? 0;
|
|
22562
|
+
curY = args[1] ?? 0;
|
|
22563
|
+
pathStartX = curX;
|
|
22564
|
+
pathStartY = curY;
|
|
22565
|
+
} else if (op === PDF_OP.lineTo) {
|
|
22566
|
+
const nx = args[0] ?? 0;
|
|
22567
|
+
const ny = args[1] ?? 0;
|
|
22568
|
+
pushIfSignificant(curX, curY, nx, ny);
|
|
22569
|
+
curX = nx;
|
|
22570
|
+
curY = ny;
|
|
22571
|
+
} else if (op === PDF_OP.rectangle) {
|
|
22572
|
+
const rx = args[0] ?? 0;
|
|
22573
|
+
const ry = args[1] ?? 0;
|
|
22574
|
+
const rw = args[2] ?? 0;
|
|
22575
|
+
const rh = args[3] ?? 0;
|
|
22576
|
+
pushIfSignificant(rx, ry, rx + rw, ry);
|
|
22577
|
+
pushIfSignificant(rx, ry + rh, rx + rw, ry + rh);
|
|
22578
|
+
pushIfSignificant(rx, ry, rx, ry + rh);
|
|
22579
|
+
pushIfSignificant(rx + rw, ry, rx + rw, ry + rh);
|
|
22580
|
+
} else if (op === PDF_OP.closePath) {
|
|
22581
|
+
pushIfSignificant(curX, curY, pathStartX, pathStartY);
|
|
22582
|
+
curX = pathStartX;
|
|
22583
|
+
curY = pathStartY;
|
|
22584
|
+
}
|
|
22585
|
+
}
|
|
22586
|
+
return lines;
|
|
22587
|
+
}
|
|
22588
|
+
function applyLinkAnnotations(items, annotations) {
|
|
22589
|
+
const linkAnns = annotations.filter(
|
|
22590
|
+
(a) => a.subtype === "Link" && a.url && a.rect && a.rect.length === 4
|
|
22591
|
+
);
|
|
22592
|
+
if (linkAnns.length === 0) return;
|
|
22593
|
+
for (const it of items) {
|
|
22594
|
+
const itArea = Math.max(it.width * it.height, 1e-6);
|
|
22595
|
+
for (const ann of linkAnns) {
|
|
22596
|
+
const [llx, lly, urx, ury] = ann.rect;
|
|
22597
|
+
const ox = Math.max(0, Math.min(it.right, urx) - Math.max(it.x, llx));
|
|
22598
|
+
const oy = Math.max(0, Math.min(it.y + it.height, ury) - Math.max(it.y, lly));
|
|
22599
|
+
if (ox * oy / itArea >= 0.2) {
|
|
22600
|
+
it.linkUrl = ann.url;
|
|
22601
|
+
break;
|
|
22602
|
+
}
|
|
22603
|
+
}
|
|
22604
|
+
}
|
|
22605
|
+
}
|
|
22606
|
+
function renderPage(items, pageNum, nextPageNum, enableTableDetection, translate, pageBottomY, enhancedOpts, ruleSeparators) {
|
|
22039
22607
|
const warnings = [];
|
|
22040
|
-
const candidates = enableTableDetection ? findTableRegions(items, { pageBottomY }) : [];
|
|
22608
|
+
const candidates = enableTableDetection ? findTableRegions(items, { pageBottomY, ruleSeparators }) : [];
|
|
22041
22609
|
const htmlBlocks = [];
|
|
22042
22610
|
const candsByY = [...candidates].sort((a, b) => b.bbox.y1 - a.bbox.y1);
|
|
22043
22611
|
const itemOverlapsCandidate = (it, c) => {
|
|
@@ -22101,36 +22669,46 @@ function renderPage(items, pageNum, nextPageNum, enableTableDetection, translate
|
|
|
22101
22669
|
}
|
|
22102
22670
|
linearTextParts.push(tableToText(block.cand));
|
|
22103
22671
|
} else {
|
|
22104
|
-
|
|
22105
|
-
|
|
22106
|
-
|
|
22107
|
-
|
|
22108
|
-
|
|
22109
|
-
|
|
22110
|
-
|
|
22111
|
-
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
|
|
22117
|
-
|
|
22118
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
22122
|
-
|
|
22123
|
-
|
|
22124
|
-
|
|
22125
|
-
|
|
22126
|
-
|
|
22127
|
-
|
|
22128
|
-
const
|
|
22129
|
-
const
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
|
|
22133
|
-
|
|
22672
|
+
if (enhancedOpts) {
|
|
22673
|
+
const { htmlBlocks: eBlocks, textContent: eText } = renderTextBlockEnhanced(
|
|
22674
|
+
block.items,
|
|
22675
|
+
enhancedOpts.medianFontSize,
|
|
22676
|
+
enhancedOpts.outlineTitles
|
|
22677
|
+
);
|
|
22678
|
+
if (eText) linearTextParts.push(eText);
|
|
22679
|
+
for (const b of eBlocks) htmlBlocks.push(b);
|
|
22680
|
+
} else {
|
|
22681
|
+
const text = itemsToLinearText(block.items);
|
|
22682
|
+
if (!text) continue;
|
|
22683
|
+
linearTextParts.push(text);
|
|
22684
|
+
const medH = (() => {
|
|
22685
|
+
const hs = block.items.map((i) => i.height).filter((h) => h > 0);
|
|
22686
|
+
if (hs.length === 0) return 10;
|
|
22687
|
+
return hs.sort((a, b) => a - b)[Math.floor(hs.length / 2)] || 10;
|
|
22688
|
+
})();
|
|
22689
|
+
const topY = block.items.reduce(
|
|
22690
|
+
(m, it) => Math.max(m, it.y + it.height),
|
|
22691
|
+
-Infinity
|
|
22692
|
+
);
|
|
22693
|
+
const topRowItems = block.items.filter(
|
|
22694
|
+
(it) => topY - (it.y + it.height) <= medH * 0.5
|
|
22695
|
+
);
|
|
22696
|
+
const topRowLooksTabular = topRowItems.length >= 4 && topRowItems.some((a) => topRowItems.some((b) => b.x - a.right > medH * 2));
|
|
22697
|
+
const paragraphs = text.split(/\n{2,}/);
|
|
22698
|
+
let paraIdx = 0;
|
|
22699
|
+
for (const para of paragraphs) {
|
|
22700
|
+
const trimmed = para.trim();
|
|
22701
|
+
paraIdx++;
|
|
22702
|
+
if (!trimmed) continue;
|
|
22703
|
+
const hasWideGap = /( {3,}|\u00A0\u00A0|\t)/.test(trimmed);
|
|
22704
|
+
const isFirstPara = paraIdx === 1;
|
|
22705
|
+
const looksTabular = hasWideGap || isFirstPara && topRowLooksTabular;
|
|
22706
|
+
const short = trimmed.length < 100 && !/[.,:;!?]$/.test(trimmed) && !/\n/.test(trimmed) && !looksTabular;
|
|
22707
|
+
if (short) {
|
|
22708
|
+
htmlBlocks.push(`<p><strong>${escapeHtml(trimmed)}</strong></p>`);
|
|
22709
|
+
} else {
|
|
22710
|
+
htmlBlocks.push(`<p>${escapeHtml(trimmed)}</p>`);
|
|
22711
|
+
}
|
|
22134
22712
|
}
|
|
22135
22713
|
}
|
|
22136
22714
|
}
|
|
@@ -22226,6 +22804,43 @@ var PdfAdapter = class {
|
|
|
22226
22804
|
}
|
|
22227
22805
|
} catch {
|
|
22228
22806
|
}
|
|
22807
|
+
let isTaggedPdf = false;
|
|
22808
|
+
let outlineTitles = /* @__PURE__ */ new Set();
|
|
22809
|
+
if (getFeatureFlags().enablePdfEnhanced) {
|
|
22810
|
+
try {
|
|
22811
|
+
const markInfo = await pdf.getMarkInfo();
|
|
22812
|
+
isTaggedPdf = markInfo?.Marked === true;
|
|
22813
|
+
} catch {
|
|
22814
|
+
}
|
|
22815
|
+
try {
|
|
22816
|
+
const outline = await pdf.getOutline();
|
|
22817
|
+
outlineTitles = buildOutlineSet(outline);
|
|
22818
|
+
} catch {
|
|
22819
|
+
}
|
|
22820
|
+
}
|
|
22821
|
+
let hfSuppressSet = /* @__PURE__ */ new Set();
|
|
22822
|
+
if (getFeatureFlags().enablePdfEnhanced && numPages >= 3 && numPages <= 200) {
|
|
22823
|
+
const hfCandidates = [];
|
|
22824
|
+
for (let pn = 1; pn <= numPages; pn++) {
|
|
22825
|
+
try {
|
|
22826
|
+
const hfPage = await pdf.getPage(pn);
|
|
22827
|
+
const hfViewport = hfPage.getViewport({ scale: 1 });
|
|
22828
|
+
const hfText = await hfPage.getTextContent();
|
|
22829
|
+
const hfAnns = await hfPage.getAnnotations().catch(() => []);
|
|
22830
|
+
const hfPositioned = [];
|
|
22831
|
+
for (const item of hfText.items) {
|
|
22832
|
+
if (!("str" in item) || typeof item.str !== "string") continue;
|
|
22833
|
+
const p = textItemToPositioned(item);
|
|
22834
|
+
if (p) hfPositioned.push(p);
|
|
22835
|
+
}
|
|
22836
|
+
const hfWidgets = hfAnns.flatMap(widgetToPositionedItems);
|
|
22837
|
+
const hfItems = hfWidgets.length > 0 ? [...dedupAgainstWidgets(hfPositioned, hfWidgets), ...hfWidgets] : hfPositioned;
|
|
22838
|
+
hfCandidates.push({ pageNum: pn, items: hfItems, viewportHeight: hfViewport.height });
|
|
22839
|
+
} catch {
|
|
22840
|
+
}
|
|
22841
|
+
}
|
|
22842
|
+
hfSuppressSet = buildHeaderFooterSuppressSet(hfCandidates);
|
|
22843
|
+
}
|
|
22229
22844
|
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
|
|
22230
22845
|
onProgress?.(pageNum / numPages);
|
|
22231
22846
|
const page = await pdf.getPage(pageNum);
|
|
@@ -22248,14 +22863,75 @@ var PdfAdapter = class {
|
|
|
22248
22863
|
}
|
|
22249
22864
|
const widgetItems = annotations.flatMap(widgetToPositionedItems);
|
|
22250
22865
|
const items = widgetItems.length > 0 ? [...dedupAgainstWidgets(positionedText, widgetItems), ...widgetItems] : positionedText;
|
|
22866
|
+
if (getFeatureFlags().enablePdfEnhanced) {
|
|
22867
|
+
applyLinkAnnotations(items, annotations);
|
|
22868
|
+
}
|
|
22869
|
+
const renderItems = getFeatureFlags().enablePdfEnhanced && hfSuppressSet.size > 0 ? items.filter((it) => !isHeaderFooterItem(it, hfSuppressSet)) : items;
|
|
22870
|
+
if (getFeatureFlags().enablePdfEnhanced && isTaggedPdf) {
|
|
22871
|
+
let structHtml = null;
|
|
22872
|
+
try {
|
|
22873
|
+
if (hasStructTree(page)) {
|
|
22874
|
+
structHtml = await structTreeToHtml(page);
|
|
22875
|
+
}
|
|
22876
|
+
} catch {
|
|
22877
|
+
}
|
|
22878
|
+
if (structHtml !== null) {
|
|
22879
|
+
const structTextLen = structHtml.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim().length;
|
|
22880
|
+
const geoTextLen = renderItems.map((it) => it.str).join(" ").length;
|
|
22881
|
+
if (geoTextLen > 0 && structTextLen / geoTextLen < 0.7) {
|
|
22882
|
+
structHtml = null;
|
|
22883
|
+
}
|
|
22884
|
+
}
|
|
22885
|
+
if (structHtml !== null) {
|
|
22886
|
+
const plainText = structHtml.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
22887
|
+
totalTextLength += plainText.length;
|
|
22888
|
+
let hasImages2 = false;
|
|
22889
|
+
try {
|
|
22890
|
+
const opList = await page.getOperatorList();
|
|
22891
|
+
hasImages2 = opList.fnArray.some((op) => op === 85 || op === 86);
|
|
22892
|
+
} catch {
|
|
22893
|
+
}
|
|
22894
|
+
pages.push({
|
|
22895
|
+
pageNumber: pageNum,
|
|
22896
|
+
textContent: plainText,
|
|
22897
|
+
hasImages: hasImages2,
|
|
22898
|
+
viewportWidth: viewport.width,
|
|
22899
|
+
htmlBlocks: [structHtml],
|
|
22900
|
+
tablesDetected: 0
|
|
22901
|
+
});
|
|
22902
|
+
continue;
|
|
22903
|
+
}
|
|
22904
|
+
}
|
|
22905
|
+
let enhancedOpts;
|
|
22906
|
+
if (getFeatureFlags().enablePdfEnhanced) {
|
|
22907
|
+
const fsSamples = positionedText.map((it) => it.fontSize ?? 0).filter((f) => f > 0);
|
|
22908
|
+
const medianFontSize = fsSamples.length > 0 ? [...fsSamples].sort((a, b) => a - b)[Math.floor(fsSamples.length / 2)] : 12;
|
|
22909
|
+
enhancedOpts = { medianFontSize, outlineTitles };
|
|
22910
|
+
}
|
|
22251
22911
|
const pageBottomY = Array.isArray(page.view) && page.view.length >= 2 ? page.view[1] : 0;
|
|
22912
|
+
let pageOpList;
|
|
22913
|
+
let ruleSeparators;
|
|
22914
|
+
try {
|
|
22915
|
+
pageOpList = await page.getOperatorList();
|
|
22916
|
+
if (getFeatureFlags().enablePdfEnhanced && tableDetectionEnabled) {
|
|
22917
|
+
ruleSeparators = extractVectorLines(
|
|
22918
|
+
pageOpList.fnArray,
|
|
22919
|
+
pageOpList.argsArray ?? [],
|
|
22920
|
+
viewport.width,
|
|
22921
|
+
viewport.height
|
|
22922
|
+
);
|
|
22923
|
+
}
|
|
22924
|
+
} catch {
|
|
22925
|
+
}
|
|
22252
22926
|
const rendered = renderPage(
|
|
22253
|
-
|
|
22927
|
+
renderItems,
|
|
22254
22928
|
pageNum,
|
|
22255
22929
|
pageNum < numPages ? pageNum + 1 : void 0,
|
|
22256
22930
|
tableDetectionEnabled,
|
|
22257
22931
|
translate,
|
|
22258
|
-
pageBottomY
|
|
22932
|
+
pageBottomY,
|
|
22933
|
+
enhancedOpts,
|
|
22934
|
+
ruleSeparators
|
|
22259
22935
|
);
|
|
22260
22936
|
for (const w of rendered.warnings) warnings.push(w);
|
|
22261
22937
|
if (!hasMultiColumnWarning && !rendered.anyTableDetected && xPositions.length > 10) {
|
|
@@ -22279,12 +22955,7 @@ var PdfAdapter = class {
|
|
|
22279
22955
|
}
|
|
22280
22956
|
}
|
|
22281
22957
|
totalTextLength += rendered.textContent.length;
|
|
22282
|
-
|
|
22283
|
-
try {
|
|
22284
|
-
const operatorList = await page.getOperatorList();
|
|
22285
|
-
hasImages = operatorList.fnArray.some((op) => op === 85 || op === 86);
|
|
22286
|
-
} catch {
|
|
22287
|
-
}
|
|
22958
|
+
const hasImages = pageOpList?.fnArray.some((op) => op === 85 || op === 86) ?? false;
|
|
22288
22959
|
pages.push({
|
|
22289
22960
|
pageNumber: pageNum,
|
|
22290
22961
|
textContent: rendered.textContent,
|
|
@@ -22747,6 +23418,621 @@ var SpreadsheetAdapter = class {
|
|
|
22747
23418
|
};
|
|
22748
23419
|
var spreadsheetAdapter = new SpreadsheetAdapter();
|
|
22749
23420
|
|
|
23421
|
+
// ../../src/shared/html-escape.ts
|
|
23422
|
+
function escapeHtml3(text) {
|
|
23423
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
23424
|
+
}
|
|
23425
|
+
|
|
23426
|
+
// ../../src/shared/adapters/data-adapter.ts
|
|
23427
|
+
var DataParseError = class extends Error {
|
|
23428
|
+
constructor(message, code, originalError) {
|
|
23429
|
+
super(message);
|
|
23430
|
+
this.code = code;
|
|
23431
|
+
this.originalError = originalError;
|
|
23432
|
+
this.name = "DataParseError";
|
|
23433
|
+
}
|
|
23434
|
+
};
|
|
23435
|
+
var XML_MAX_STRUCTURED_DEPTH = 4;
|
|
23436
|
+
var TABLE_WIDE_COLUMN_WARNING = 15;
|
|
23437
|
+
var JSON_DEPTH_GUARD = 128;
|
|
23438
|
+
var XML_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_.:-]*$/;
|
|
23439
|
+
var XML_ATTRIBUTE_REGEX = /[A-Za-z_][A-Za-z0-9_.:-]*\s*=\s*(?:"[^"]*"|'[^']*')/y;
|
|
23440
|
+
function isXmlWhitespace(ch) {
|
|
23441
|
+
return ch === " " || ch === "\n" || ch === "\r" || ch === " ";
|
|
23442
|
+
}
|
|
23443
|
+
function findTagEnd(xml, start) {
|
|
23444
|
+
let quote = null;
|
|
23445
|
+
for (let i = start; i < xml.length; i++) {
|
|
23446
|
+
const ch = xml[i];
|
|
23447
|
+
if (quote) {
|
|
23448
|
+
if (ch === quote) quote = null;
|
|
23449
|
+
continue;
|
|
23450
|
+
}
|
|
23451
|
+
if (ch === '"' || ch === "'") {
|
|
23452
|
+
quote = ch;
|
|
23453
|
+
continue;
|
|
23454
|
+
}
|
|
23455
|
+
if (ch === ">") return i;
|
|
23456
|
+
}
|
|
23457
|
+
return -1;
|
|
23458
|
+
}
|
|
23459
|
+
function findDoctypeEnd(xml, start) {
|
|
23460
|
+
let bracketDepth = 0;
|
|
23461
|
+
let quote = null;
|
|
23462
|
+
for (let i = start; i < xml.length; i++) {
|
|
23463
|
+
const ch = xml[i];
|
|
23464
|
+
if (quote) {
|
|
23465
|
+
if (ch === quote) quote = null;
|
|
23466
|
+
continue;
|
|
23467
|
+
}
|
|
23468
|
+
if (ch === '"' || ch === "'") {
|
|
23469
|
+
quote = ch;
|
|
23470
|
+
continue;
|
|
23471
|
+
}
|
|
23472
|
+
if (ch === "[") {
|
|
23473
|
+
bracketDepth++;
|
|
23474
|
+
continue;
|
|
23475
|
+
}
|
|
23476
|
+
if (ch === "]") {
|
|
23477
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
23478
|
+
continue;
|
|
23479
|
+
}
|
|
23480
|
+
if (ch === ">" && bracketDepth === 0) return i;
|
|
23481
|
+
}
|
|
23482
|
+
return -1;
|
|
23483
|
+
}
|
|
23484
|
+
function hasValidXmlAttributes(attrs) {
|
|
23485
|
+
let index = 0;
|
|
23486
|
+
while (index < attrs.length) {
|
|
23487
|
+
while (index < attrs.length && isXmlWhitespace(attrs[index])) index++;
|
|
23488
|
+
if (index >= attrs.length) return true;
|
|
23489
|
+
XML_ATTRIBUTE_REGEX.lastIndex = index;
|
|
23490
|
+
const match = XML_ATTRIBUTE_REGEX.exec(attrs);
|
|
23491
|
+
if (!match || match.index !== index) return false;
|
|
23492
|
+
index = XML_ATTRIBUTE_REGEX.lastIndex;
|
|
23493
|
+
}
|
|
23494
|
+
return true;
|
|
23495
|
+
}
|
|
23496
|
+
function isWellFormedXml(text) {
|
|
23497
|
+
const xml = text.replace(/^\uFEFF/, "");
|
|
23498
|
+
const stack = [];
|
|
23499
|
+
let seenRoot = false;
|
|
23500
|
+
let i = 0;
|
|
23501
|
+
while (i < xml.length) {
|
|
23502
|
+
const lt = xml.indexOf("<", i);
|
|
23503
|
+
if (lt === -1) {
|
|
23504
|
+
return seenRoot && stack.length === 0 && xml.slice(i).trim().length === 0;
|
|
23505
|
+
}
|
|
23506
|
+
if (xml.slice(i, lt).trim().length > 0 && stack.length === 0) {
|
|
23507
|
+
return false;
|
|
23508
|
+
}
|
|
23509
|
+
i = lt;
|
|
23510
|
+
if (xml.startsWith("<!--", i)) {
|
|
23511
|
+
const end = xml.indexOf("-->", i + 4);
|
|
23512
|
+
if (end === -1) return false;
|
|
23513
|
+
i = end + 3;
|
|
23514
|
+
continue;
|
|
23515
|
+
}
|
|
23516
|
+
if (xml.startsWith("<![CDATA[", i)) {
|
|
23517
|
+
if (stack.length === 0) return false;
|
|
23518
|
+
const end = xml.indexOf("]]>", i + 9);
|
|
23519
|
+
if (end === -1) return false;
|
|
23520
|
+
i = end + 3;
|
|
23521
|
+
continue;
|
|
23522
|
+
}
|
|
23523
|
+
if (xml.startsWith("<?", i)) {
|
|
23524
|
+
const end = xml.indexOf("?>", i + 2);
|
|
23525
|
+
if (end === -1) return false;
|
|
23526
|
+
i = end + 2;
|
|
23527
|
+
continue;
|
|
23528
|
+
}
|
|
23529
|
+
if (xml.startsWith("<!DOCTYPE", i) || xml.startsWith("<!doctype", i)) {
|
|
23530
|
+
if (seenRoot || stack.length > 0) return false;
|
|
23531
|
+
const end = findDoctypeEnd(xml, i + 9);
|
|
23532
|
+
if (end === -1) return false;
|
|
23533
|
+
i = end + 1;
|
|
23534
|
+
continue;
|
|
23535
|
+
}
|
|
23536
|
+
const gt = findTagEnd(xml, i + 1);
|
|
23537
|
+
if (gt === -1) return false;
|
|
23538
|
+
const raw = xml.slice(i + 1, gt).trim();
|
|
23539
|
+
if (raw.length === 0) return false;
|
|
23540
|
+
if (raw.startsWith("/")) {
|
|
23541
|
+
const name2 = raw.slice(1).trim();
|
|
23542
|
+
if (!XML_NAME_REGEX.test(name2)) return false;
|
|
23543
|
+
const open = stack.pop();
|
|
23544
|
+
if (open !== name2) return false;
|
|
23545
|
+
if (stack.length === 0) seenRoot = true;
|
|
23546
|
+
i = gt + 1;
|
|
23547
|
+
continue;
|
|
23548
|
+
}
|
|
23549
|
+
const selfClosing = raw.endsWith("/");
|
|
23550
|
+
const body = selfClosing ? raw.slice(0, -1).trim() : raw;
|
|
23551
|
+
if (body.length === 0) return false;
|
|
23552
|
+
const spaceIndex = body.search(/\s/);
|
|
23553
|
+
const name = spaceIndex === -1 ? body : body.slice(0, spaceIndex);
|
|
23554
|
+
const attrs = spaceIndex === -1 ? "" : body.slice(spaceIndex + 1).trim();
|
|
23555
|
+
if (!XML_NAME_REGEX.test(name)) return false;
|
|
23556
|
+
if (!hasValidXmlAttributes(attrs)) return false;
|
|
23557
|
+
if (selfClosing) {
|
|
23558
|
+
if (stack.length === 0) {
|
|
23559
|
+
if (seenRoot) return false;
|
|
23560
|
+
seenRoot = true;
|
|
23561
|
+
}
|
|
23562
|
+
} else {
|
|
23563
|
+
if (seenRoot && stack.length === 0) return false;
|
|
23564
|
+
stack.push(name);
|
|
23565
|
+
}
|
|
23566
|
+
i = gt + 1;
|
|
23567
|
+
}
|
|
23568
|
+
return seenRoot && stack.length === 0;
|
|
23569
|
+
}
|
|
23570
|
+
function jsonDepth(value, current = 0) {
|
|
23571
|
+
if (value === null || typeof value !== "object") return current;
|
|
23572
|
+
if (current >= JSON_DEPTH_GUARD) return JSON_DEPTH_GUARD + 1;
|
|
23573
|
+
const children = Object.values(value);
|
|
23574
|
+
if (children.length === 0) return current;
|
|
23575
|
+
let maxDepth = current;
|
|
23576
|
+
for (const child of children) {
|
|
23577
|
+
const depth = jsonDepth(child, current + 1);
|
|
23578
|
+
if (depth > maxDepth) {
|
|
23579
|
+
maxDepth = depth;
|
|
23580
|
+
if (maxDepth > JSON_DEPTH_GUARD) {
|
|
23581
|
+
return maxDepth;
|
|
23582
|
+
}
|
|
23583
|
+
}
|
|
23584
|
+
}
|
|
23585
|
+
return maxDepth;
|
|
23586
|
+
}
|
|
23587
|
+
function isTableArray(arr) {
|
|
23588
|
+
if (arr.length === 0) return false;
|
|
23589
|
+
const first = arr[0];
|
|
23590
|
+
if (first === null || typeof first !== "object" || Array.isArray(first)) return false;
|
|
23591
|
+
const keys = Object.keys(first);
|
|
23592
|
+
return arr.every((item) => {
|
|
23593
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) return false;
|
|
23594
|
+
const itemObj = item;
|
|
23595
|
+
const itemKeys = Object.keys(itemObj);
|
|
23596
|
+
return itemKeys.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(itemObj, key));
|
|
23597
|
+
});
|
|
23598
|
+
}
|
|
23599
|
+
function arrayToTable(arr) {
|
|
23600
|
+
const keys = Object.keys(arr[0]);
|
|
23601
|
+
const headerCells = keys.map((k) => `<th>${escapeHtml3(k)}</th>`).join("");
|
|
23602
|
+
const bodyRows = arr.map((row) => {
|
|
23603
|
+
const cells = keys.map((k) => `<td>${escapeHtml3(String(row[k] ?? ""))}</td>`).join("");
|
|
23604
|
+
return `<tr>${cells}</tr>`;
|
|
23605
|
+
}).join("\n");
|
|
23606
|
+
return `<table>
|
|
23607
|
+
<thead><tr>${headerCells}</tr></thead>
|
|
23608
|
+
<tbody>
|
|
23609
|
+
${bodyRows}
|
|
23610
|
+
</tbody>
|
|
23611
|
+
</table>`;
|
|
23612
|
+
}
|
|
23613
|
+
function objectToTable(obj) {
|
|
23614
|
+
const rows = Object.entries(obj).map(([k, v]) => `<tr><td>${escapeHtml3(k)}</td><td>${escapeHtml3(String(v ?? ""))}</td></tr>`).join("\n");
|
|
23615
|
+
return `<table>
|
|
23616
|
+
<thead><tr><th>Key</th><th>Value</th></tr></thead>
|
|
23617
|
+
<tbody>
|
|
23618
|
+
${rows}
|
|
23619
|
+
</tbody>
|
|
23620
|
+
</table>`;
|
|
23621
|
+
}
|
|
23622
|
+
function safeJsonStringify(value) {
|
|
23623
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
23624
|
+
const stack = [];
|
|
23625
|
+
function push(v, indent) {
|
|
23626
|
+
if (v === null) {
|
|
23627
|
+
stack.push("null");
|
|
23628
|
+
return;
|
|
23629
|
+
}
|
|
23630
|
+
const t = typeof v;
|
|
23631
|
+
if (t === "string") {
|
|
23632
|
+
stack.push(JSON.stringify(v));
|
|
23633
|
+
return;
|
|
23634
|
+
}
|
|
23635
|
+
if (t === "number" || t === "boolean") {
|
|
23636
|
+
stack.push(String(v));
|
|
23637
|
+
return;
|
|
23638
|
+
}
|
|
23639
|
+
if (t !== "object") {
|
|
23640
|
+
stack.push("null");
|
|
23641
|
+
return;
|
|
23642
|
+
}
|
|
23643
|
+
if (typeof v === "object" && v !== null) {
|
|
23644
|
+
if (seen.has(v)) {
|
|
23645
|
+
stack.push("null");
|
|
23646
|
+
return;
|
|
23647
|
+
}
|
|
23648
|
+
seen.add(v);
|
|
23649
|
+
}
|
|
23650
|
+
if (Array.isArray(v)) {
|
|
23651
|
+
if (v.length === 0) {
|
|
23652
|
+
stack.push("[]");
|
|
23653
|
+
return;
|
|
23654
|
+
}
|
|
23655
|
+
const inner2 = " ".repeat(indent + 1);
|
|
23656
|
+
const close2 = " ".repeat(indent);
|
|
23657
|
+
stack.push("[\n");
|
|
23658
|
+
for (let i = 0; i < v.length; i++) {
|
|
23659
|
+
stack.push(inner2);
|
|
23660
|
+
push(v[i], indent + 1);
|
|
23661
|
+
stack.push(i < v.length - 1 ? ",\n" : "\n");
|
|
23662
|
+
}
|
|
23663
|
+
stack.push(close2 + "]");
|
|
23664
|
+
return;
|
|
23665
|
+
}
|
|
23666
|
+
const keys = Object.keys(v);
|
|
23667
|
+
if (keys.length === 0) {
|
|
23668
|
+
stack.push("{}");
|
|
23669
|
+
return;
|
|
23670
|
+
}
|
|
23671
|
+
const inner = " ".repeat(indent + 1);
|
|
23672
|
+
const close = " ".repeat(indent);
|
|
23673
|
+
stack.push("{\n");
|
|
23674
|
+
for (let i = 0; i < keys.length; i++) {
|
|
23675
|
+
const k = keys[i];
|
|
23676
|
+
stack.push(inner + JSON.stringify(k) + ": ");
|
|
23677
|
+
push(v[k], indent + 1);
|
|
23678
|
+
stack.push(i < keys.length - 1 ? ",\n" : "\n");
|
|
23679
|
+
}
|
|
23680
|
+
stack.push(close + "}");
|
|
23681
|
+
}
|
|
23682
|
+
push(value, 0);
|
|
23683
|
+
return stack.join("");
|
|
23684
|
+
}
|
|
23685
|
+
function jsonToCodeBlock(value) {
|
|
23686
|
+
const json = safeJsonStringify(value);
|
|
23687
|
+
return `<pre><code class="language-json">${escapeHtml3(json)}</code></pre>`;
|
|
23688
|
+
}
|
|
23689
|
+
function jsonValueToHtml(value) {
|
|
23690
|
+
const warnings = [];
|
|
23691
|
+
if (Array.isArray(value) && isTableArray(value)) {
|
|
23692
|
+
const keys = Object.keys(value[0]);
|
|
23693
|
+
if (keys.length > TABLE_WIDE_COLUMN_WARNING) {
|
|
23694
|
+
warnings.push(
|
|
23695
|
+
`Table has ${keys.length} columns which may not render well in narrow Markdown viewers.`
|
|
23696
|
+
);
|
|
23697
|
+
}
|
|
23698
|
+
return { html: arrayToTable(value), warnings };
|
|
23699
|
+
}
|
|
23700
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && jsonDepth(value) <= 1) {
|
|
23701
|
+
return { html: objectToTable(value), warnings };
|
|
23702
|
+
}
|
|
23703
|
+
return { html: jsonToCodeBlock(value), warnings };
|
|
23704
|
+
}
|
|
23705
|
+
function serializeXmlElement(el) {
|
|
23706
|
+
const serializerCtor = globalThis.XMLSerializer;
|
|
23707
|
+
if (typeof serializerCtor === "function") {
|
|
23708
|
+
return new serializerCtor().serializeToString(el);
|
|
23709
|
+
}
|
|
23710
|
+
const outer = el.outerHTML;
|
|
23711
|
+
if (typeof outer === "string") {
|
|
23712
|
+
return outer;
|
|
23713
|
+
}
|
|
23714
|
+
return String(el);
|
|
23715
|
+
}
|
|
23716
|
+
function xmlElementToHtml(el, depth) {
|
|
23717
|
+
const tagName = el.localName || el.nodeName;
|
|
23718
|
+
const directText = Array.from(el.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => (n.textContent || "").trim()).join(" ").trim();
|
|
23719
|
+
const children = Array.from(el.children);
|
|
23720
|
+
if (children.length === 0) {
|
|
23721
|
+
if (!directText) return "";
|
|
23722
|
+
const headingLevel2 = Math.min(depth + 1, 6);
|
|
23723
|
+
return `<h${headingLevel2}>${escapeHtml3(tagName)}</h${headingLevel2}>
|
|
23724
|
+
<p>${escapeHtml3(directText)}</p>
|
|
23725
|
+
`;
|
|
23726
|
+
}
|
|
23727
|
+
if (depth >= XML_MAX_STRUCTURED_DEPTH) {
|
|
23728
|
+
return `<pre><code class="language-xml">${escapeHtml3(serializeXmlElement(el))}</code></pre>
|
|
23729
|
+
`;
|
|
23730
|
+
}
|
|
23731
|
+
const headingLevel = Math.min(depth + 1, 6);
|
|
23732
|
+
let html = `<h${headingLevel}>${escapeHtml3(tagName)}</h${headingLevel}>
|
|
23733
|
+
`;
|
|
23734
|
+
if (directText) {
|
|
23735
|
+
html += `<p>${escapeHtml3(directText)}</p>
|
|
23736
|
+
`;
|
|
23737
|
+
}
|
|
23738
|
+
for (const child of children) {
|
|
23739
|
+
html += xmlElementToHtml(child, depth + 1);
|
|
23740
|
+
}
|
|
23741
|
+
return html;
|
|
23742
|
+
}
|
|
23743
|
+
var DataAdapter = class {
|
|
23744
|
+
async parseJsonToHtml(arrayBuffer) {
|
|
23745
|
+
const warnings = [];
|
|
23746
|
+
try {
|
|
23747
|
+
if (arrayBuffer.byteLength === 0) {
|
|
23748
|
+
throw new DataParseError("The JSON file is empty.", "EMPTY_FILE");
|
|
23749
|
+
}
|
|
23750
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
|
|
23751
|
+
let parsed;
|
|
23752
|
+
try {
|
|
23753
|
+
parsed = JSON.parse(text);
|
|
23754
|
+
} catch (err) {
|
|
23755
|
+
throw new DataParseError(
|
|
23756
|
+
"The file does not contain valid JSON.",
|
|
23757
|
+
"INVALID_JSON",
|
|
23758
|
+
err instanceof Error ? err : void 0
|
|
23759
|
+
);
|
|
23760
|
+
}
|
|
23761
|
+
const { html, warnings: convWarnings } = jsonValueToHtml(parsed);
|
|
23762
|
+
warnings.push(...convWarnings);
|
|
23763
|
+
return { html, warnings };
|
|
23764
|
+
} catch (error) {
|
|
23765
|
+
if (error instanceof DataParseError) throw error;
|
|
23766
|
+
throw new DataParseError(
|
|
23767
|
+
`Failed to parse JSON file: ${error instanceof Error ? error.message : String(error)}`,
|
|
23768
|
+
"PARSE_ERROR",
|
|
23769
|
+
error instanceof Error ? error : void 0
|
|
23770
|
+
);
|
|
23771
|
+
}
|
|
23772
|
+
}
|
|
23773
|
+
async parseXmlToHtml(arrayBuffer) {
|
|
23774
|
+
const warnings = [];
|
|
23775
|
+
try {
|
|
23776
|
+
if (arrayBuffer.byteLength === 0) {
|
|
23777
|
+
throw new DataParseError("The XML file is empty.", "EMPTY_FILE");
|
|
23778
|
+
}
|
|
23779
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
|
|
23780
|
+
if (!isWellFormedXml(text)) {
|
|
23781
|
+
throw new DataParseError("The file does not contain valid XML.", "INVALID_XML");
|
|
23782
|
+
}
|
|
23783
|
+
const parserCtor = globalThis.DOMParser;
|
|
23784
|
+
if (typeof parserCtor !== "function") {
|
|
23785
|
+
throw new DataParseError("XML parsing is unavailable in this runtime.", "PARSE_ERROR");
|
|
23786
|
+
}
|
|
23787
|
+
const doc = new parserCtor().parseFromString(text, "application/xml");
|
|
23788
|
+
const parseError = doc.querySelector("parsererror");
|
|
23789
|
+
const root = doc.documentElement;
|
|
23790
|
+
if (parseError || !root || /parsererror/i.test(root.nodeName)) {
|
|
23791
|
+
throw new DataParseError("The file does not contain valid XML.", "INVALID_XML");
|
|
23792
|
+
}
|
|
23793
|
+
const html = xmlElementToHtml(root, 0);
|
|
23794
|
+
if (!html.trim()) {
|
|
23795
|
+
warnings.push("The XML file contains no readable text content.");
|
|
23796
|
+
return { html: `<p><em>No text content found in XML file.</em></p>`, warnings };
|
|
23797
|
+
}
|
|
23798
|
+
return { html, warnings };
|
|
23799
|
+
} catch (error) {
|
|
23800
|
+
if (error instanceof DataParseError) throw error;
|
|
23801
|
+
throw new DataParseError(
|
|
23802
|
+
`Failed to parse XML file: ${error instanceof Error ? error.message : String(error)}`,
|
|
23803
|
+
"PARSE_ERROR",
|
|
23804
|
+
error instanceof Error ? error : void 0
|
|
23805
|
+
);
|
|
23806
|
+
}
|
|
23807
|
+
}
|
|
23808
|
+
};
|
|
23809
|
+
var dataAdapter = new DataAdapter();
|
|
23810
|
+
|
|
23811
|
+
// ../../src/shared/adapters/pptx-adapter.ts
|
|
23812
|
+
import JSZip from "jszip";
|
|
23813
|
+
var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
23814
|
+
var NS_P = "http://schemas.openxmlformats.org/presentationml/2006/main";
|
|
23815
|
+
var PptxParseError = class extends Error {
|
|
23816
|
+
constructor(message, code, originalError) {
|
|
23817
|
+
super(message);
|
|
23818
|
+
this.code = code;
|
|
23819
|
+
this.originalError = originalError;
|
|
23820
|
+
this.name = "PptxParseError";
|
|
23821
|
+
}
|
|
23822
|
+
};
|
|
23823
|
+
function getElements(parent, ns, localName) {
|
|
23824
|
+
return Array.from(parent.getElementsByTagNameNS(ns, localName));
|
|
23825
|
+
}
|
|
23826
|
+
function isTitle(shape) {
|
|
23827
|
+
const phElements = getElements(shape, NS_P, "ph");
|
|
23828
|
+
for (const ph of phElements) {
|
|
23829
|
+
const type = ph.getAttribute("type");
|
|
23830
|
+
if (type === "title" || type === "ctrTitle") return true;
|
|
23831
|
+
const idx = ph.getAttribute("idx");
|
|
23832
|
+
if (!type && (!idx || idx === "0")) return true;
|
|
23833
|
+
}
|
|
23834
|
+
return false;
|
|
23835
|
+
}
|
|
23836
|
+
function runToHtml(run) {
|
|
23837
|
+
const tElements = getElements(run, NS_A, "t");
|
|
23838
|
+
const text = tElements.map((t) => t.textContent || "").join("");
|
|
23839
|
+
if (!text) return "";
|
|
23840
|
+
const rPr = getElements(run, NS_A, "rPr")[0];
|
|
23841
|
+
if (!rPr) return escapeHtml3(text);
|
|
23842
|
+
let result = escapeHtml3(text);
|
|
23843
|
+
if (rPr.getAttribute("b") === "1") result = `<strong>${result}</strong>`;
|
|
23844
|
+
if (rPr.getAttribute("i") === "1") result = `<em>${result}</em>`;
|
|
23845
|
+
return result;
|
|
23846
|
+
}
|
|
23847
|
+
function paragraphToHtml(para, shapeTitleFlag) {
|
|
23848
|
+
const runs = getElements(para, NS_A, "r");
|
|
23849
|
+
let content = runs.map(runToHtml).join("");
|
|
23850
|
+
const fields = getElements(para, NS_A, "fld");
|
|
23851
|
+
content += fields.map((fld) => {
|
|
23852
|
+
const t = getElements(fld, NS_A, "t")[0];
|
|
23853
|
+
return t ? escapeHtml3(t.textContent || "") : "";
|
|
23854
|
+
}).join("");
|
|
23855
|
+
if (!content.trim()) return "";
|
|
23856
|
+
if (shapeTitleFlag) {
|
|
23857
|
+
return `<h2>${content}</h2>
|
|
23858
|
+
`;
|
|
23859
|
+
}
|
|
23860
|
+
const pPr = getElements(para, NS_A, "pPr")[0];
|
|
23861
|
+
const level = pPr ? parseInt(pPr.getAttribute("lvl") || "0", 10) : 0;
|
|
23862
|
+
if (level > 0) {
|
|
23863
|
+
return `<li>${content}</li>
|
|
23864
|
+
`;
|
|
23865
|
+
}
|
|
23866
|
+
return `<p>${content}</p>
|
|
23867
|
+
`;
|
|
23868
|
+
}
|
|
23869
|
+
function tableToHtml(tbl) {
|
|
23870
|
+
const rows = getElements(tbl, NS_A, "tr");
|
|
23871
|
+
if (rows.length === 0) return "";
|
|
23872
|
+
let html = "<table>\n";
|
|
23873
|
+
rows.forEach((row, rowIndex) => {
|
|
23874
|
+
html += "<tr>";
|
|
23875
|
+
const cells = getElements(row, NS_A, "tc");
|
|
23876
|
+
cells.forEach((cell) => {
|
|
23877
|
+
const paragraphs = getElements(cell, NS_A, "p");
|
|
23878
|
+
const text = paragraphs.flatMap((p) => getElements(p, NS_A, "r")).map((r) => getElements(r, NS_A, "t").map((t) => t.textContent || "").join("")).join(" ").trim();
|
|
23879
|
+
const tag = rowIndex === 0 ? "th" : "td";
|
|
23880
|
+
html += `<${tag}>${escapeHtml3(text)}</${tag}>`;
|
|
23881
|
+
});
|
|
23882
|
+
html += "</tr>\n";
|
|
23883
|
+
});
|
|
23884
|
+
html += "</table>\n";
|
|
23885
|
+
return html;
|
|
23886
|
+
}
|
|
23887
|
+
function parseSlide(xmlString) {
|
|
23888
|
+
const warnings = [];
|
|
23889
|
+
const parser = new DOMParser();
|
|
23890
|
+
const doc = parser.parseFromString(xmlString, "application/xml");
|
|
23891
|
+
if (doc.querySelector("parsererror")) {
|
|
23892
|
+
warnings.push("A slide could not be parsed and was skipped.");
|
|
23893
|
+
return { html: "", warnings };
|
|
23894
|
+
}
|
|
23895
|
+
let html = "";
|
|
23896
|
+
const shapes = getElements(doc, NS_P, "sp");
|
|
23897
|
+
for (const shape of shapes) {
|
|
23898
|
+
const txBodies = getElements(shape, NS_P, "txBody");
|
|
23899
|
+
if (txBodies.length === 0) continue;
|
|
23900
|
+
const titleFlag = isTitle(shape);
|
|
23901
|
+
const paragraphs = getElements(txBodies[0], NS_A, "p");
|
|
23902
|
+
let inList = false;
|
|
23903
|
+
for (const para of paragraphs) {
|
|
23904
|
+
const paraHtml = paragraphToHtml(para, titleFlag);
|
|
23905
|
+
if (!paraHtml) continue;
|
|
23906
|
+
if (paraHtml.startsWith("<li>")) {
|
|
23907
|
+
if (!inList) {
|
|
23908
|
+
html += "<ul>\n";
|
|
23909
|
+
inList = true;
|
|
23910
|
+
}
|
|
23911
|
+
} else if (inList) {
|
|
23912
|
+
html += "</ul>\n";
|
|
23913
|
+
inList = false;
|
|
23914
|
+
}
|
|
23915
|
+
html += paraHtml;
|
|
23916
|
+
}
|
|
23917
|
+
if (inList) {
|
|
23918
|
+
html += "</ul>\n";
|
|
23919
|
+
}
|
|
23920
|
+
}
|
|
23921
|
+
const graphicFrames = getElements(doc, NS_P, "graphicFrame");
|
|
23922
|
+
for (const frame of graphicFrames) {
|
|
23923
|
+
const tables = getElements(frame, NS_A, "tbl");
|
|
23924
|
+
for (const tbl of tables) {
|
|
23925
|
+
html += tableToHtml(tbl);
|
|
23926
|
+
}
|
|
23927
|
+
}
|
|
23928
|
+
const pics = getElements(doc, NS_P, "pic");
|
|
23929
|
+
if (pics.length > 0 && !html.trim()) {
|
|
23930
|
+
html += `<p><em>[${pics.length} image${pics.length > 1 ? "s" : ""} on this slide]</em></p>
|
|
23931
|
+
`;
|
|
23932
|
+
} else if (pics.length > 0) {
|
|
23933
|
+
html += `<p><em>[+ ${pics.length} image${pics.length > 1 ? "s" : ""}]</em></p>
|
|
23934
|
+
`;
|
|
23935
|
+
}
|
|
23936
|
+
return { html, warnings };
|
|
23937
|
+
}
|
|
23938
|
+
function parseNotes(xmlString) {
|
|
23939
|
+
const parser = new DOMParser();
|
|
23940
|
+
const doc = parser.parseFromString(xmlString, "application/xml");
|
|
23941
|
+
if (doc.querySelector("parsererror")) return "";
|
|
23942
|
+
const shapes = getElements(doc, NS_P, "sp");
|
|
23943
|
+
const lines = [];
|
|
23944
|
+
for (const shape of shapes) {
|
|
23945
|
+
const ph = getElements(shape, NS_P, "ph")[0];
|
|
23946
|
+
if (!ph) continue;
|
|
23947
|
+
const idx = ph.getAttribute("idx");
|
|
23948
|
+
if (idx !== "1") continue;
|
|
23949
|
+
const txBodies = getElements(shape, NS_P, "txBody");
|
|
23950
|
+
if (txBodies.length === 0) continue;
|
|
23951
|
+
const paragraphs = getElements(txBodies[0], NS_A, "p");
|
|
23952
|
+
for (const para of paragraphs) {
|
|
23953
|
+
const runs = getElements(para, NS_A, "r");
|
|
23954
|
+
const text = runs.map((r) => getElements(r, NS_A, "t").map((t) => t.textContent || "").join("")).join("").trim();
|
|
23955
|
+
if (text) lines.push(text);
|
|
23956
|
+
}
|
|
23957
|
+
}
|
|
23958
|
+
if (lines.length === 0) return "";
|
|
23959
|
+
const notesContent = lines.map((l) => `<p>${escapeHtml3(l)}</p>`).join("\n");
|
|
23960
|
+
return `<blockquote>
|
|
23961
|
+
<p><strong>Speaker notes:</strong></p>
|
|
23962
|
+
${notesContent}
|
|
23963
|
+
</blockquote>
|
|
23964
|
+
`;
|
|
23965
|
+
}
|
|
23966
|
+
function sortSlides(paths2) {
|
|
23967
|
+
return paths2.slice().sort((a, b) => {
|
|
23968
|
+
const numA = parseInt(a.match(/(\d+)/)?.[1] ?? "0", 10);
|
|
23969
|
+
const numB = parseInt(b.match(/(\d+)/)?.[1] ?? "0", 10);
|
|
23970
|
+
return numA - numB;
|
|
23971
|
+
});
|
|
23972
|
+
}
|
|
23973
|
+
var PptxAdapter = class {
|
|
23974
|
+
async parseToHtml(arrayBuffer) {
|
|
23975
|
+
const warnings = [];
|
|
23976
|
+
try {
|
|
23977
|
+
let zip;
|
|
23978
|
+
try {
|
|
23979
|
+
zip = await JSZip.loadAsync(arrayBuffer);
|
|
23980
|
+
} catch (err) {
|
|
23981
|
+
throw new PptxParseError(
|
|
23982
|
+
"This file does not appear to be a valid PPTX document.",
|
|
23983
|
+
"CORRUPTED",
|
|
23984
|
+
err instanceof Error ? err : void 0
|
|
23985
|
+
);
|
|
23986
|
+
}
|
|
23987
|
+
if (!zip.files["ppt/presentation.xml"]) {
|
|
23988
|
+
throw new PptxParseError(
|
|
23989
|
+
"This file does not appear to be a valid PPTX document.",
|
|
23990
|
+
"INVALID_FORMAT"
|
|
23991
|
+
);
|
|
23992
|
+
}
|
|
23993
|
+
const slidePaths = sortSlides(
|
|
23994
|
+
Object.keys(zip.files).filter(
|
|
23995
|
+
(name) => /^ppt\/slides\/slide\d+\.xml$/.test(name)
|
|
23996
|
+
)
|
|
23997
|
+
);
|
|
23998
|
+
if (slidePaths.length === 0) {
|
|
23999
|
+
warnings.push("No slides were found in this presentation.");
|
|
24000
|
+
return { html: "<p><em>No slides found.</em></p>", slideCount: 0, warnings };
|
|
24001
|
+
}
|
|
24002
|
+
let html = "";
|
|
24003
|
+
for (let i = 0; i < slidePaths.length; i++) {
|
|
24004
|
+
const slideNumber = i + 1;
|
|
24005
|
+
const slidePath = slidePaths[i];
|
|
24006
|
+
html += `<div data-slide-marker="${slideNumber}"></div>
|
|
24007
|
+
`;
|
|
24008
|
+
const slideXml = await zip.files[slidePath].async("string");
|
|
24009
|
+
const { html: slideHtml, warnings: slideWarnings } = parseSlide(slideXml);
|
|
24010
|
+
warnings.push(...slideWarnings);
|
|
24011
|
+
html += slideHtml || `<p><em>[Empty slide]</em></p>
|
|
24012
|
+
`;
|
|
24013
|
+
const notesPath = slidePath.replace("ppt/slides/slide", "ppt/notesSlides/notesSlide");
|
|
24014
|
+
if (zip.files[notesPath]) {
|
|
24015
|
+
const notesXml = await zip.files[notesPath].async("string");
|
|
24016
|
+
const notesHtml = parseNotes(notesXml);
|
|
24017
|
+
if (notesHtml) {
|
|
24018
|
+
html += notesHtml;
|
|
24019
|
+
}
|
|
24020
|
+
}
|
|
24021
|
+
}
|
|
24022
|
+
return { html, slideCount: slidePaths.length, warnings };
|
|
24023
|
+
} catch (error) {
|
|
24024
|
+
if (error instanceof PptxParseError) throw error;
|
|
24025
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24026
|
+
throw new PptxParseError(
|
|
24027
|
+
`Failed to parse PPTX file: ${message}`,
|
|
24028
|
+
"PARSE_ERROR",
|
|
24029
|
+
error instanceof Error ? error : void 0
|
|
24030
|
+
);
|
|
24031
|
+
}
|
|
24032
|
+
}
|
|
24033
|
+
};
|
|
24034
|
+
var pptxAdapter = new PptxAdapter();
|
|
24035
|
+
|
|
22750
24036
|
// ../../src/shared/atlassian-detector.ts
|
|
22751
24037
|
var ATLASSIAN_MARKERS = [
|
|
22752
24038
|
"ac:structured-macro",
|
|
@@ -23372,13 +24658,16 @@ function detectInputType(filePath) {
|
|
|
23372
24658
|
".docx": "docx",
|
|
23373
24659
|
".pdf": "pdf",
|
|
23374
24660
|
".xlsx": "xlsx",
|
|
23375
|
-
".csv": "csv"
|
|
24661
|
+
".csv": "csv",
|
|
24662
|
+
".json": "json",
|
|
24663
|
+
".xml": "xml",
|
|
24664
|
+
".pptx": "pptx"
|
|
23376
24665
|
};
|
|
23377
24666
|
const type = map[ext];
|
|
23378
24667
|
if (!type) {
|
|
23379
24668
|
process.stderr.write(
|
|
23380
24669
|
`Unsupported file type: ${ext}
|
|
23381
|
-
Supported: .html, .htm, .docx, .pdf, .xlsx, .csv
|
|
24670
|
+
Supported: .html, .htm, .docx, .pdf, .xlsx, .csv, .json, .xml, .pptx
|
|
23382
24671
|
`
|
|
23383
24672
|
);
|
|
23384
24673
|
process.exit(1);
|
|
@@ -23459,6 +24748,39 @@ async function binaryToMarkdown(buffer, type, format) {
|
|
|
23459
24748
|
});
|
|
23460
24749
|
return converted.markdown;
|
|
23461
24750
|
}
|
|
24751
|
+
case "json": {
|
|
24752
|
+
const result = await dataAdapter.parseJsonToHtml(arrayBuffer);
|
|
24753
|
+
const sanitized = sanitizeHTML(result.html);
|
|
24754
|
+
const converted = await engine.convert(sanitized, {
|
|
24755
|
+
format,
|
|
24756
|
+
preserveTables: true,
|
|
24757
|
+
preserveLists: true,
|
|
24758
|
+
customRules: []
|
|
24759
|
+
});
|
|
24760
|
+
return converted.markdown;
|
|
24761
|
+
}
|
|
24762
|
+
case "xml": {
|
|
24763
|
+
const result = await dataAdapter.parseXmlToHtml(arrayBuffer);
|
|
24764
|
+
const sanitized = sanitizeHTML(result.html);
|
|
24765
|
+
const converted = await engine.convert(sanitized, {
|
|
24766
|
+
format,
|
|
24767
|
+
preserveTables: true,
|
|
24768
|
+
preserveLists: true,
|
|
24769
|
+
customRules: []
|
|
24770
|
+
});
|
|
24771
|
+
return converted.markdown;
|
|
24772
|
+
}
|
|
24773
|
+
case "pptx": {
|
|
24774
|
+
const result = await pptxAdapter.parseToHtml(arrayBuffer);
|
|
24775
|
+
const sanitized = sanitizeHTML(result.html);
|
|
24776
|
+
const converted = await engine.convert(sanitized, {
|
|
24777
|
+
format,
|
|
24778
|
+
preserveTables: true,
|
|
24779
|
+
preserveLists: true,
|
|
24780
|
+
customRules: []
|
|
24781
|
+
});
|
|
24782
|
+
return converted.markdown;
|
|
24783
|
+
}
|
|
23462
24784
|
}
|
|
23463
24785
|
}
|
|
23464
24786
|
var DEFAULT_PROXY_URL = "https://formatferry.vibingfun.com";
|
|
@@ -23630,7 +24952,7 @@ async function convertCommand(options) {
|
|
|
23630
24952
|
import fg from "fast-glob";
|
|
23631
24953
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
23632
24954
|
import { extname as extname2, basename, dirname as dirname2, join } from "path";
|
|
23633
|
-
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".xlsx", ".csv"]);
|
|
24955
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".xlsx", ".csv", ".json", ".xml", ".pptx"]);
|
|
23634
24956
|
function getOutputPath(filePath, outputDir) {
|
|
23635
24957
|
const ext = extname2(filePath);
|
|
23636
24958
|
const stem = basename(filePath, ext);
|
|
@@ -23653,18 +24975,36 @@ async function convertFile(filePath, format) {
|
|
|
23653
24975
|
}
|
|
23654
24976
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
23655
24977
|
if (ext === ".docx") {
|
|
23656
|
-
const
|
|
23657
|
-
const
|
|
23658
|
-
return
|
|
24978
|
+
const r = await docxAdapter.parseToHtml(arrayBuffer);
|
|
24979
|
+
const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
24980
|
+
return c.markdown;
|
|
23659
24981
|
}
|
|
23660
24982
|
if (ext === ".pdf") {
|
|
23661
|
-
const
|
|
23662
|
-
const
|
|
23663
|
-
return
|
|
24983
|
+
const r = await pdfAdapter.parseToText(arrayBuffer);
|
|
24984
|
+
const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
24985
|
+
return c.markdown;
|
|
24986
|
+
}
|
|
24987
|
+
if (ext === ".xlsx" || ext === ".csv") {
|
|
24988
|
+
const r = await spreadsheetAdapter.parseToHtml(arrayBuffer, ext.slice(1), 0);
|
|
24989
|
+
const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
24990
|
+
return c.markdown;
|
|
24991
|
+
}
|
|
24992
|
+
if (ext === ".json") {
|
|
24993
|
+
const r = await dataAdapter.parseJsonToHtml(arrayBuffer);
|
|
24994
|
+
const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
24995
|
+
return c.markdown;
|
|
24996
|
+
}
|
|
24997
|
+
if (ext === ".xml") {
|
|
24998
|
+
const r = await dataAdapter.parseXmlToHtml(arrayBuffer);
|
|
24999
|
+
const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
25000
|
+
return c.markdown;
|
|
25001
|
+
}
|
|
25002
|
+
if (ext === ".pptx") {
|
|
25003
|
+
const r = await pptxAdapter.parseToHtml(arrayBuffer);
|
|
25004
|
+
const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
25005
|
+
return c.markdown;
|
|
23664
25006
|
}
|
|
23665
|
-
|
|
23666
|
-
const c = await engine.convert(sanitizeHTML(r.html), { format, preserveTables: true, preserveLists: true, customRules: [] });
|
|
23667
|
-
return c.markdown;
|
|
25007
|
+
throw new Error(`Unsupported file type: ${ext}`);
|
|
23668
25008
|
}
|
|
23669
25009
|
async function batchCommand(options) {
|
|
23670
25010
|
const parsedTimeout = options.timeout ? parseInt(options.timeout, 10) : void 0;
|
|
@@ -23954,8 +25294,8 @@ import { readFileSync as readFileSync4 } from "fs";
|
|
|
23954
25294
|
import { resolve, dirname as dirname3 } from "path";
|
|
23955
25295
|
import { fileURLToPath } from "url";
|
|
23956
25296
|
var getVersion = () => {
|
|
23957
|
-
if ("1.0.
|
|
23958
|
-
return "1.0.
|
|
25297
|
+
if ("1.0.14") {
|
|
25298
|
+
return "1.0.14";
|
|
23959
25299
|
}
|
|
23960
25300
|
try {
|
|
23961
25301
|
const __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
@@ -23968,7 +25308,7 @@ var getVersion = () => {
|
|
|
23968
25308
|
}
|
|
23969
25309
|
};
|
|
23970
25310
|
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) => {
|
|
25311
|
+
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
25312
|
if (options.batch) {
|
|
23973
25313
|
await batchCommand(options);
|
|
23974
25314
|
} else {
|