@juspay/neurolink 13.4.1 → 13.4.3
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/CHANGELOG.md +2 -2
- package/README.md +33 -8
- package/dist/auth/providers/BaseAuthProvider.d.ts +1 -1
- package/dist/browser/neurolink.min.js +397 -397
- package/dist/processors/document/PptxProcessor.d.ts +16 -5
- package/dist/processors/document/PptxProcessor.js +145 -17
- package/docs-site/static/search-index.json +53 -64
- package/package.json +2 -2
|
@@ -9,15 +9,18 @@
|
|
|
9
9
|
* - ppt/slideMasters/ — master slide templates
|
|
10
10
|
* - ppt/slideLayouts/ — slide layout definitions
|
|
11
11
|
*
|
|
12
|
-
* Text is extracted from `<a:t>` elements in the slide XML files
|
|
13
|
-
*
|
|
12
|
+
* Text is extracted from `<a:t>` elements in the slide XML files, with XML
|
|
13
|
+
* character references decoded (`&` becomes `&`). Text inside a table is
|
|
14
|
+
* grouped by cell and row, so a table row comes out as one line
|
|
15
|
+
* (`Region | Owner | Volume`) instead of one line per run. Slides are sorted
|
|
16
|
+
* by number and presented in reading order, each followed by its speaker notes.
|
|
14
17
|
*
|
|
15
18
|
* Uses `adm-zip` (already a project dependency) for ZIP extraction.
|
|
16
19
|
*
|
|
17
20
|
* **XML-entity attacks are not applicable here.** Unlike `WordProcessor`
|
|
18
21
|
* (mammoth/@xmldom/xmldom) and `ExcelProcessor` (exceljs/saxes), this
|
|
19
22
|
* processor never hands slide XML to a DOM or SAX parser at all — extraction
|
|
20
|
-
* is a single regex (`
|
|
23
|
+
* is a single regex (`SLIDE_TOKEN_REGEX`, below) run over the raw decoded
|
|
21
24
|
* bytes. There is no DOCTYPE/ENTITY resolution path for a payload to reach,
|
|
22
25
|
* so there is nothing for a `.pptx`-specific XXE or billion-laughs test to
|
|
23
26
|
* falsify; one is deliberately not written (see
|
|
@@ -67,7 +70,14 @@ export declare class PptxProcessor {
|
|
|
67
70
|
private static extractNotesForSlide;
|
|
68
71
|
/**
|
|
69
72
|
* Extract text strings from a slide XML document.
|
|
70
|
-
*
|
|
73
|
+
*
|
|
74
|
+
* Finds every `<a:t>` element and returns its decoded text, one string per
|
|
75
|
+
* run, except inside a table: there the runs of a cell are joined (runs of
|
|
76
|
+
* one paragraph directly, paragraphs and line breaks with a space) and the
|
|
77
|
+
* cells of a row come back as one `a | b | c` string. A row with no text in
|
|
78
|
+
* any cell is dropped. An empty cell keeps its separator, so the number of
|
|
79
|
+
* cells in a row can be counted from the line (`a | |`, `| b`), but the
|
|
80
|
+
* line itself is trimmed and a literal `|` in cell text is not escaped.
|
|
71
81
|
*
|
|
72
82
|
* @param xml - Raw XML string from a slide file
|
|
73
83
|
* @returns Array of text strings found in the slide
|
|
@@ -76,7 +86,8 @@ export declare class PptxProcessor {
|
|
|
76
86
|
/**
|
|
77
87
|
* Extract text from specific slides in a PPTX file.
|
|
78
88
|
*
|
|
79
|
-
* Called by the `extract_file_content` tool for targeted slide access.
|
|
89
|
+
* Called by the `extract_file_content` tool for targeted slide access. Each
|
|
90
|
+
* slide carries its speaker notes, as in {@link PptxProcessor.extractText}.
|
|
80
91
|
*
|
|
81
92
|
* @param content - Raw PPTX file buffer
|
|
82
93
|
* @param slideNumbers - Array of 1-indexed slide numbers to extract
|
|
@@ -9,15 +9,18 @@
|
|
|
9
9
|
* - ppt/slideMasters/ — master slide templates
|
|
10
10
|
* - ppt/slideLayouts/ — slide layout definitions
|
|
11
11
|
*
|
|
12
|
-
* Text is extracted from `<a:t>` elements in the slide XML files
|
|
13
|
-
*
|
|
12
|
+
* Text is extracted from `<a:t>` elements in the slide XML files, with XML
|
|
13
|
+
* character references decoded (`&` becomes `&`). Text inside a table is
|
|
14
|
+
* grouped by cell and row, so a table row comes out as one line
|
|
15
|
+
* (`Region | Owner | Volume`) instead of one line per run. Slides are sorted
|
|
16
|
+
* by number and presented in reading order, each followed by its speaker notes.
|
|
14
17
|
*
|
|
15
18
|
* Uses `adm-zip` (already a project dependency) for ZIP extraction.
|
|
16
19
|
*
|
|
17
20
|
* **XML-entity attacks are not applicable here.** Unlike `WordProcessor`
|
|
18
21
|
* (mammoth/@xmldom/xmldom) and `ExcelProcessor` (exceljs/saxes), this
|
|
19
22
|
* processor never hands slide XML to a DOM or SAX parser at all — extraction
|
|
20
|
-
* is a single regex (`
|
|
23
|
+
* is a single regex (`SLIDE_TOKEN_REGEX`, below) run over the raw decoded
|
|
21
24
|
* bytes. There is no DOCTYPE/ENTITY resolution path for a payload to reach,
|
|
22
25
|
* so there is nothing for a `.pptx`-specific XXE or billion-laughs test to
|
|
23
26
|
* falsify; one is deliberately not written (see
|
|
@@ -78,10 +81,75 @@ function createBoundedEntryReader() {
|
|
|
78
81
|
};
|
|
79
82
|
}
|
|
80
83
|
/**
|
|
81
|
-
* Regex
|
|
82
|
-
*
|
|
84
|
+
* Regex that tokenises the parts of slide XML the extractor reads, in document
|
|
85
|
+
* order:
|
|
86
|
+
*
|
|
87
|
+
* - `text`: an `<a:t>` element, which holds the visible text of one run.
|
|
88
|
+
* - `closing` and `tag`: the start or end tag of a paragraph (`a:p`), table
|
|
89
|
+
* cell (`a:tc`) or table row (`a:tr`), which decide how runs are grouped.
|
|
90
|
+
* - a `<a:br>` line break (matched with no group).
|
|
91
|
+
* - `end`: the end of the input, so the last open row or cell is flushed by the
|
|
92
|
+
* same code that handles every other boundary.
|
|
93
|
+
*
|
|
94
|
+
* Each tag name is followed by whitespace or `>`, never by more name
|
|
95
|
+
* characters. The old pattern `<a:t[^>]*>` also matched `<a:tbl>`, `<a:tc>`,
|
|
96
|
+
* `<a:tr>`, `<a:tab>` and `<a:tailEnd>`, so a table cell was read as a run
|
|
97
|
+
* whose "text" ran from the table markup up to the next `</a:t>`.
|
|
98
|
+
*
|
|
99
|
+
* A self-closing tag (`<a:t/>`, `<a:p/>`) is not a start tag, so it is
|
|
100
|
+
* excluded by the lookbehind.
|
|
101
|
+
*
|
|
102
|
+
* Neither run text nor a tag's attributes may contain `<`: well-formed XML
|
|
103
|
+
* escapes it in text and forbids it in an attribute value. Run text is
|
|
104
|
+
* `[^<]*` rather than a lazy `[\s\S]*?`, and attributes are `[^<>]*` rather
|
|
105
|
+
* than `[^>]*`, so a match attempt stops at the next `<` instead of scanning to
|
|
106
|
+
* the end of the slide. Without that, input made of unclosed `<a:t>` elements
|
|
107
|
+
* or unterminated start tags (`<a:t `, `<a:p `, `<a:br `) rescans the rest of
|
|
108
|
+
* the slide from every start position, which is quadratic time.
|
|
109
|
+
*/
|
|
110
|
+
const SLIDE_TOKEN_REGEX = /<a:t(?:\s[^<>]*)?(?<!\/)>(?<text>[^<]*)<\/a:t>|<(?<closing>\/?)a:(?<tag>tr|tc|p)(?:\s[^<>]*)?(?<!\/)>|<a:br(?:\s[^<>]*|\/)?>|(?<end>$)/g;
|
|
111
|
+
/** The XML predefined entities, the only named references XML defines. */
|
|
112
|
+
const XML_NAMED_ENTITIES = {
|
|
113
|
+
amp: "&",
|
|
114
|
+
lt: "<",
|
|
115
|
+
gt: ">",
|
|
116
|
+
quot: '"',
|
|
117
|
+
apos: "'",
|
|
118
|
+
};
|
|
119
|
+
/** A named, decimal (`©`) or hexadecimal (`©`) character reference. */
|
|
120
|
+
const XML_ENTITY_REGEX = /&(?:#(\d+)|#x([0-9a-fA-F]+)|(amp|lt|gt|quot|apos));/g;
|
|
121
|
+
/** Whether a code point is a character XML 1.0 allows in a document. */
|
|
122
|
+
function isXmlChar(codePoint) {
|
|
123
|
+
return (codePoint === 0x9 ||
|
|
124
|
+
codePoint === 0xa ||
|
|
125
|
+
codePoint === 0xd ||
|
|
126
|
+
(codePoint >= 0x20 && codePoint <= 0xd7ff) ||
|
|
127
|
+
(codePoint >= 0xe000 && codePoint <= 0xfffd) ||
|
|
128
|
+
(codePoint >= 0x10000 && codePoint <= 0x10ffff));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Decode XML character references in run text.
|
|
132
|
+
*
|
|
133
|
+
* One pass over the original string, so `&lt;` becomes the literal text
|
|
134
|
+
* `<` and never `<`. A reference to a character XML forbids, or one outside
|
|
135
|
+
* the Unicode range, is left as written rather than turned into a control
|
|
136
|
+
* character. The slide XML is never handed to a parser, so there are no
|
|
137
|
+
* user-defined entities to resolve (see the file header).
|
|
83
138
|
*/
|
|
84
|
-
|
|
139
|
+
function decodeXmlEntities(text) {
|
|
140
|
+
if (!text.includes("&")) {
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
return text.replace(XML_ENTITY_REGEX, (reference, decimal, hex, named) => {
|
|
144
|
+
if (named !== undefined) {
|
|
145
|
+
return XML_NAMED_ENTITIES[named];
|
|
146
|
+
}
|
|
147
|
+
const codePoint = decimal !== undefined
|
|
148
|
+
? Number.parseInt(decimal, 10)
|
|
149
|
+
: Number.parseInt(hex ?? "", 16);
|
|
150
|
+
return isXmlChar(codePoint) ? String.fromCodePoint(codePoint) : reference;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
85
153
|
/**
|
|
86
154
|
* Regex to match slide filenames and extract slide number.
|
|
87
155
|
* Matches entries like "ppt/slides/slide1.xml", "ppt/slides/slide12.xml".
|
|
@@ -195,22 +263,77 @@ export class PptxProcessor {
|
|
|
195
263
|
}
|
|
196
264
|
/**
|
|
197
265
|
* Extract text strings from a slide XML document.
|
|
198
|
-
*
|
|
266
|
+
*
|
|
267
|
+
* Finds every `<a:t>` element and returns its decoded text, one string per
|
|
268
|
+
* run, except inside a table: there the runs of a cell are joined (runs of
|
|
269
|
+
* one paragraph directly, paragraphs and line breaks with a space) and the
|
|
270
|
+
* cells of a row come back as one `a | b | c` string. A row with no text in
|
|
271
|
+
* any cell is dropped. An empty cell keeps its separator, so the number of
|
|
272
|
+
* cells in a row can be counted from the line (`a | |`, `| b`), but the
|
|
273
|
+
* line itself is trimmed and a literal `|` in cell text is not escaped.
|
|
199
274
|
*
|
|
200
275
|
* @param xml - Raw XML string from a slide file
|
|
201
276
|
* @returns Array of text strings found in the slide
|
|
202
277
|
*/
|
|
203
278
|
static extractTextFromXml(xml) {
|
|
204
|
-
const
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
279
|
+
const lines = [];
|
|
280
|
+
// DrawingML tables do not nest, so one open row, cell and paragraph is
|
|
281
|
+
// enough. A start tag implicitly closes whatever the same or a deeper level
|
|
282
|
+
// left open, which keeps a slide with a missing end tag readable.
|
|
283
|
+
let row = null;
|
|
284
|
+
let cell = null;
|
|
285
|
+
let paragraph = "";
|
|
286
|
+
for (const match of xml.matchAll(SLIDE_TOKEN_REGEX)) {
|
|
287
|
+
const { text, closing, tag, end } = match.groups ?? {};
|
|
288
|
+
if (text !== undefined) {
|
|
289
|
+
const decoded = decodeXmlEntities(text);
|
|
290
|
+
if (cell) {
|
|
291
|
+
paragraph += decoded;
|
|
292
|
+
}
|
|
293
|
+
else if (decoded.trim()) {
|
|
294
|
+
lines.push(decoded.trim());
|
|
295
|
+
}
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (tag === undefined && end === undefined) {
|
|
299
|
+
// `<a:br>`: a line break inside a cell is a space, since the row is
|
|
300
|
+
// one line. Outside a table each run is already its own line.
|
|
301
|
+
if (cell) {
|
|
302
|
+
paragraph += " ";
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
// 1 = paragraph, 2 = cell, 3 = row (also the end of the input).
|
|
307
|
+
const level = tag === "p" ? 1 : tag === "tc" ? 2 : 3;
|
|
308
|
+
if (cell && level >= 1) {
|
|
309
|
+
if (paragraph.trim()) {
|
|
310
|
+
cell.push(paragraph.trim());
|
|
311
|
+
}
|
|
312
|
+
paragraph = "";
|
|
313
|
+
}
|
|
314
|
+
if (cell && level >= 2) {
|
|
315
|
+
if (!row) {
|
|
316
|
+
row = [];
|
|
317
|
+
}
|
|
318
|
+
row.push(cell.join(" "));
|
|
319
|
+
cell = null;
|
|
320
|
+
}
|
|
321
|
+
if (row && level >= 3) {
|
|
322
|
+
if (row.some((cellText) => cellText)) {
|
|
323
|
+
lines.push(row.join(" | ").trim());
|
|
324
|
+
}
|
|
325
|
+
row = null;
|
|
326
|
+
}
|
|
327
|
+
if (closing === "") {
|
|
328
|
+
if (tag === "tr") {
|
|
329
|
+
row = [];
|
|
330
|
+
}
|
|
331
|
+
else if (tag === "tc") {
|
|
332
|
+
cell = [];
|
|
333
|
+
}
|
|
211
334
|
}
|
|
212
335
|
}
|
|
213
|
-
return
|
|
336
|
+
return lines;
|
|
214
337
|
}
|
|
215
338
|
// ===========================================================================
|
|
216
339
|
// TARGETED EXTRACTION API
|
|
@@ -218,7 +341,8 @@ export class PptxProcessor {
|
|
|
218
341
|
/**
|
|
219
342
|
* Extract text from specific slides in a PPTX file.
|
|
220
343
|
*
|
|
221
|
-
* Called by the `extract_file_content` tool for targeted slide access.
|
|
344
|
+
* Called by the `extract_file_content` tool for targeted slide access. Each
|
|
345
|
+
* slide carries its speaker notes, as in {@link PptxProcessor.extractText}.
|
|
222
346
|
*
|
|
223
347
|
* @param content - Raw PPTX file buffer
|
|
224
348
|
* @param slideNumbers - Array of 1-indexed slide numbers to extract
|
|
@@ -256,11 +380,15 @@ export class PptxProcessor {
|
|
|
256
380
|
const parts = [];
|
|
257
381
|
for (const slide of slides) {
|
|
258
382
|
const texts = PptxProcessor.extractTextFromXml(slide.xml);
|
|
383
|
+
const notes = PptxProcessor.extractNotesForSlide(zip, slide.slideNumber, readEntry);
|
|
259
384
|
parts.push(`### Slide ${slide.slideNumber}`);
|
|
260
385
|
if (texts.length > 0) {
|
|
261
386
|
parts.push(texts.join("\n"));
|
|
262
387
|
}
|
|
263
|
-
|
|
388
|
+
if (notes) {
|
|
389
|
+
parts.push(`**Speaker notes:** ${notes}`);
|
|
390
|
+
}
|
|
391
|
+
if (texts.length === 0 && !notes) {
|
|
264
392
|
parts.push("(No text content on this slide)");
|
|
265
393
|
}
|
|
266
394
|
parts.push("");
|