@veryfront/ext-document-kreuzberg 0.1.994 → 0.1.996

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.
@@ -23,6 +23,12 @@ function decodeXmlText(value) {
23
23
  function extractPptxSlideText(xml) {
24
24
  return Array.from(xml.matchAll(/<a:t(?:\s[^>]*)?>([\s\S]*?)<\/a:t>/g)).map((match) => decodeXmlText(match[1] ?? "").trim()).filter(Boolean).join(" ").trim();
25
25
  }
26
+ function normalizePptxText(value) {
27
+ return value.replace(/\s+/g, " ").trim();
28
+ }
29
+ function cleanExtractedMarkdown(value) {
30
+ return value.replace(/!\[[^\]\n]*\]\(\s*\)/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
31
+ }
26
32
  function slideNumber(path) {
27
33
  return Number(path.match(/\/slide(\d+)\.xml$/)?.[1] ?? 0);
28
34
  }
@@ -30,6 +36,72 @@ function getXmlAttribute(tag, name) {
30
36
  const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31
37
  return tag.match(new RegExp(`\\s${escapedName}=(["'])(.*?)\\1`))?.[2];
32
38
  }
39
+ function pptxShapeRole(shapeXml) {
40
+ const placeholderTag = shapeXml.match(/<(?:\w+:)?ph\b[^>]*>/)?.[0] ?? "";
41
+ const placeholderType = getXmlAttribute(placeholderTag, "type");
42
+ if (placeholderType === "title" || placeholderType === "ctrTitle") return "title";
43
+ if (placeholderType === "subTitle") return "subtitle";
44
+ const propertyTag = shapeXml.match(/<(?:\w+:)?cNvPr\b[^>]*>/)?.[0] ?? "";
45
+ const name = getXmlAttribute(propertyTag, "name")?.toLowerCase() ?? "";
46
+ if (name.includes("subtitle")) return "subtitle";
47
+ if (name.includes("title")) return "title";
48
+ return "body";
49
+ }
50
+ function pptxTextShapes(xml) {
51
+ const shapes = Array.from(xml.matchAll(/<p:sp\b[\s\S]*?<\/p:sp>/g)).map((match) => {
52
+ const shapeXml = match[0];
53
+ return {
54
+ role: pptxShapeRole(shapeXml),
55
+ text: extractPptxSlideText(shapeXml)
56
+ };
57
+ }).filter((shape) => shape.text.length > 0);
58
+ const hasExplicitHeading = shapes.some(
59
+ (shape) => shape.role === "title" || shape.role === "subtitle"
60
+ );
61
+ if (!hasExplicitHeading && shapes.length === 1) {
62
+ return [{ ...shapes[0], role: "title" }];
63
+ }
64
+ return shapes;
65
+ }
66
+ function pptxShapeRoleQueues(xml) {
67
+ const roles = /* @__PURE__ */ new Map();
68
+ for (const shape of pptxTextShapes(xml)) {
69
+ const key = normalizePptxText(shape.text);
70
+ if (!key) continue;
71
+ const queue = roles.get(key) ?? [];
72
+ queue.push(shape.role);
73
+ roles.set(key, queue);
74
+ }
75
+ return roles;
76
+ }
77
+ function normalizePptxMarkdownHeadings(markdown, xml) {
78
+ const roles = pptxShapeRoleQueues(xml);
79
+ if (roles.size === 0) return markdown;
80
+ return cleanExtractedMarkdown(
81
+ markdown.split("\n").map((line) => {
82
+ const heading = line.match(/^#{1,6}\s+(.+?)\s*$/);
83
+ if (!heading) return line;
84
+ const text = heading[1] ?? "";
85
+ const queue = roles.get(normalizePptxText(text));
86
+ const role = queue?.shift();
87
+ if (!role) return line;
88
+ if (role === "title") return `# ${text}`;
89
+ if (role === "subtitle") return `## ${text}`;
90
+ return text;
91
+ }).join("\n")
92
+ );
93
+ }
94
+ function formatPptxShapeText(shape) {
95
+ if (shape.role === "title") return `# ${shape.text}`;
96
+ if (shape.role === "subtitle") return `## ${shape.text}`;
97
+ return shape.text;
98
+ }
99
+ function formatPptxSlideText(xml) {
100
+ const shapes = pptxTextShapes(xml);
101
+ const nonShapeText = extractPptxSlideText(xml.replace(/<p:sp\b[\s\S]*?<\/p:sp>/g, ""));
102
+ if (shapes.length === 0) return nonShapeText || extractPptxSlideText(xml);
103
+ return [...shapes.map(formatPptxShapeText), nonShapeText].filter(Boolean).join("\n\n").trim();
104
+ }
33
105
  function normalizeZipPath(path) {
34
106
  const parts = [];
35
107
  for (const part of path.split("/")) {
@@ -89,6 +161,42 @@ async function pptxSlidePaths(zip) {
89
161
  ...fallback.filter((path) => !ordered.includes(path))
90
162
  ];
91
163
  }
164
+ async function buildSingleSlidePptx(slideXml) {
165
+ const zip = new JSZip();
166
+ zip.file(
167
+ "[Content_Types].xml",
168
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
169
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
170
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
171
+ <Default Extension="xml" ContentType="application/xml"/>
172
+ <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
173
+ <Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
174
+ </Types>`
175
+ );
176
+ zip.file(
177
+ "_rels/.rels",
178
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
179
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
180
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
181
+ </Relationships>`
182
+ );
183
+ zip.file(
184
+ "ppt/presentation.xml",
185
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
186
+ <p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
187
+ <p:sldIdLst><p:sldId id="256" r:id="rId1"/></p:sldIdLst>
188
+ </p:presentation>`
189
+ );
190
+ zip.file(
191
+ "ppt/_rels/presentation.xml.rels",
192
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
193
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
194
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
195
+ </Relationships>`
196
+ );
197
+ zip.file("ppt/slides/slide1.xml", slideXml);
198
+ return await zip.generateAsync({ type: "uint8array" });
199
+ }
92
200
  async function extractPdfByPage(buffer) {
93
201
  const { extractBytes } = await loadKreuzbergNative();
94
202
  const source = await PDFDocument.load(buffer, { ignoreEncryption: true });
@@ -119,21 +227,37 @@ async function extractPdfByPage(buffer) {
119
227
  async function extractPptxBySlide(buffer, mimeType) {
120
228
  const zip = await JSZip.loadAsync(buffer);
121
229
  const slidePaths = await pptxSlidePaths(zip);
230
+ const config = extractionConfigForMimeType(mimeType);
231
+ let nativeExtractor;
232
+ try {
233
+ nativeExtractor = await loadKreuzbergNative();
234
+ } catch (error) {
235
+ if (!slidePaths.length) throw error;
236
+ }
122
237
  if (!slidePaths.length) {
123
- const { extractBytes } = await loadKreuzbergNative();
124
- const result = await extractBytes(
238
+ const result = await nativeExtractor.extractBytes(
125
239
  new Uint8Array(buffer),
126
240
  mimeType,
127
- extractionConfigForMimeType(mimeType)
241
+ config
128
242
  );
129
- postProgress({ unit: "file", current: 1, total: 1, characters: result.content.length });
130
- return result.content;
243
+ const content = cleanExtractedMarkdown(result.content);
244
+ postProgress({ unit: "file", current: 1, total: 1, characters: content.length });
245
+ return content;
131
246
  }
132
247
  const slides = [];
133
248
  for (const [index, path] of slidePaths.entries()) {
134
249
  const file = zip.file(path);
135
250
  const xml = file ? await file.async("text") : "";
136
- const content = extractPptxSlideText(xml);
251
+ let content;
252
+ if (nativeExtractor) {
253
+ try {
254
+ const slideBytes = await buildSingleSlidePptx(xml);
255
+ const result = await nativeExtractor.extractBytes(slideBytes, mimeType, config);
256
+ content = normalizePptxMarkdownHeadings(cleanExtractedMarkdown(result.content), xml);
257
+ } catch {
258
+ }
259
+ }
260
+ content ||= formatPptxSlideText(xml);
137
261
  slides.push(content);
138
262
  postProgress({
139
263
  unit: "slide",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veryfront/ext-document-kreuzberg",
3
- "version": "0.1.994",
3
+ "version": "0.1.996",
4
4
  "description": "Veryfront first-party extension package for ext-document-kreuzberg",
5
5
  "keywords": [
6
6
  "veryfront",
@@ -55,7 +55,7 @@
55
55
  "pdf-lib": "1.17.1"
56
56
  },
57
57
  "peerDependencies": {
58
- "veryfront": "^0.1.994"
58
+ "veryfront": "^0.1.996"
59
59
  },
60
60
  "type": "module",
61
61
  "types": "./esm/extensions/ext-document-kreuzberg/src/index.d.ts",