@leemour/cli-messaging 0.178.0 → 0.180.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/attachments/container.d.ts +7 -0
  2. package/dist/attachments/container.d.ts.map +1 -0
  3. package/dist/attachments/container.js +236 -0
  4. package/dist/attachments/container.js.map +1 -0
  5. package/dist/attachments/documents.d.ts +6 -0
  6. package/dist/attachments/documents.d.ts.map +1 -0
  7. package/dist/attachments/documents.js +355 -0
  8. package/dist/attachments/documents.js.map +1 -0
  9. package/dist/attachments/encoding.d.ts +9 -0
  10. package/dist/attachments/encoding.d.ts.map +1 -0
  11. package/dist/attachments/encoding.js +108 -0
  12. package/dist/attachments/encoding.js.map +1 -0
  13. package/dist/attachments/extract.d.ts +2 -5
  14. package/dist/attachments/extract.d.ts.map +1 -1
  15. package/dist/attachments/extract.js +16 -14
  16. package/dist/attachments/extract.js.map +1 -1
  17. package/dist/attachments/limits.d.ts +3 -0
  18. package/dist/attachments/limits.d.ts.map +1 -0
  19. package/dist/attachments/limits.js +3 -0
  20. package/dist/attachments/limits.js.map +1 -0
  21. package/dist/cli/index.d.ts +1 -0
  22. package/dist/cli/index.d.ts.map +1 -1
  23. package/dist/cli/index.js +1 -0
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/messenger/account-command.d.ts.map +1 -1
  26. package/dist/cli/messenger/account-command.js +35 -29
  27. package/dist/cli/messenger/account-command.js.map +1 -1
  28. package/dist/cli/messenger/chats-command.d.ts.map +1 -1
  29. package/dist/cli/messenger/chats-command.js +3 -0
  30. package/dist/cli/messenger/chats-command.js.map +1 -1
  31. package/dist/cli/messenger/context.d.ts +4 -0
  32. package/dist/cli/messenger/context.d.ts.map +1 -1
  33. package/dist/cli/messenger/context.js.map +1 -1
  34. package/dist/cli/messenger/port.d.ts +16 -2
  35. package/dist/cli/messenger/port.d.ts.map +1 -1
  36. package/dist/cli/messenger/port.js.map +1 -1
  37. package/dist/cli/messenger/records-command.d.ts +9 -0
  38. package/dist/cli/messenger/records-command.d.ts.map +1 -0
  39. package/dist/cli/messenger/records-command.js +64 -0
  40. package/dist/cli/messenger/records-command.js.map +1 -0
  41. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  42. package/dist/domain/models.d.ts +34 -0
  43. package/dist/domain/models.d.ts.map +1 -1
  44. package/dist/domain/models.js +2 -0
  45. package/dist/domain/models.js.map +1 -1
  46. package/dist/services/attachments.d.ts.map +1 -1
  47. package/dist/services/attachments.js +9 -2
  48. package/dist/services/attachments.js.map +1 -1
  49. package/dist/store/sqlite/attachment-texts.d.ts +1 -1
  50. package/dist/store/sqlite/attachment-texts.d.ts.map +1 -1
  51. package/dist/store/sqlite/attachment-texts.js +2 -2
  52. package/dist/store/sqlite/attachment-texts.js.map +1 -1
  53. package/package.json +6 -2
  54. package/parity.json +35 -0
@@ -0,0 +1,7 @@
1
+ import { type Document, type Element, type Node } from "@xmldom/xmldom";
2
+ export declare class ReaderLimit extends Error {
3
+ }
4
+ export declare const readContainer: (bytes: Uint8Array, signal?: AbortSignal) => Promise<Map<string, Uint8Array>>;
5
+ export declare const xmlPart: (archive: Map<string, Uint8Array>, path: string) => Document;
6
+ export declare const elements: (node: Node, name: string, namespace: string) => Element[];
7
+ //# sourceMappingURL=container.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/attachments/container.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAA;AAUlF,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAqEzC,eAAO,MAAM,aAAa,UAAiB,UAAU,WAAW,WAAW,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAiD5G,CAAA;AAyCD,eAAO,MAAM,OAAO,YAAa,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,QAAQ,MAAM,KAAG,QAyBxE,CAAA;AAED,eAAO,MAAM,QAAQ,SAAU,IAAI,QAAQ,MAAM,aAAa,MAAM,KAAG,OAAO,EAS7E,CAAA"}
@@ -0,0 +1,236 @@
1
+ import { setImmediate as tick } from "node:timers/promises";
2
+ import { crc32 } from "node:zlib";
3
+ import { CliError } from "@leemour/cli-core";
4
+ import { DOMParser } from "@xmldom/xmldom";
5
+ import { Unzip, UnzipInflate } from "fflate";
6
+ import { decodeXml } from "./encoding.js";
7
+ import { MAX_FILE_BYTES as MAX_EXPANDED } from "./limits.js";
8
+ const MAX_PART = 10 * 1024 * 1024;
9
+ const MAX_ENTRIES = 1000;
10
+ const decoder = new TextDecoder("utf-8", { fatal: true });
11
+ export class ReaderLimit extends Error {
12
+ }
13
+ const cancelled = (signal) => {
14
+ if (signal?.aborted)
15
+ throw new CliError("cancelled", "document extraction cancelled");
16
+ };
17
+ const safeName = (name) => name.length > 0 &&
18
+ name.length <= 512 &&
19
+ !/^[\\/]|\\/.test(name) &&
20
+ ![...name].some((character) => character.charCodeAt(0) < 32) &&
21
+ !name.split("/").includes("..");
22
+ const directory = (bytes) => {
23
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
24
+ let end = -1;
25
+ for (let index = bytes.length - 22; index >= Math.max(0, bytes.length - 65_557); index--) {
26
+ if (view.getUint32(index, true) === 0x06054b50 && index + 22 + view.getUint16(index + 20, true) === bytes.length) {
27
+ end = index;
28
+ break;
29
+ }
30
+ }
31
+ if (end < 0 || view.getUint16(end + 4, true) || view.getUint16(end + 6, true))
32
+ throw new Error("invalid_zip");
33
+ const count = view.getUint16(end + 10, true);
34
+ if (count > MAX_ENTRIES)
35
+ throw new ReaderLimit();
36
+ if (count !== view.getUint16(end + 8, true))
37
+ throw new Error("invalid_zip");
38
+ const stop = view.getUint32(end + 16, true) + view.getUint32(end + 12, true);
39
+ if (stop > end)
40
+ throw new Error("invalid_zip");
41
+ let offset = view.getUint32(end + 16, true);
42
+ let expanded = 0;
43
+ const entries = new Map();
44
+ for (let index = 0; index < count; index++) {
45
+ if (offset + 46 > stop || view.getUint32(offset, true) !== 0x02014b50)
46
+ throw new Error("invalid_zip");
47
+ const flags = view.getUint16(offset + 8, true);
48
+ const method = view.getUint16(offset + 10, true);
49
+ const size = view.getUint32(offset + 24, true);
50
+ const compressed = view.getUint32(offset + 20, true);
51
+ const length = view.getUint16(offset + 28, true);
52
+ const next = offset + 46 + length + view.getUint16(offset + 30, true) + view.getUint16(offset + 32, true);
53
+ if (next > stop)
54
+ throw new Error("invalid_zip");
55
+ const name = decoder.decode(bytes.subarray(offset + 46, offset + 46 + length));
56
+ if (!safeName(name) || entries.has(name) || flags & 1 || (method !== 0 && method !== 8))
57
+ throw new Error("invalid_zip");
58
+ if (((view.getUint32(offset + 38, true) >>> 16) & 0xf000) === 0xa000)
59
+ throw new Error("invalid_zip");
60
+ expanded += size;
61
+ if (expanded > MAX_EXPANDED)
62
+ throw new ReaderLimit();
63
+ const local = view.getUint32(offset + 42, true);
64
+ if (local + 30 > offset || view.getUint32(local, true) !== 0x04034b50)
65
+ throw new Error("invalid_zip");
66
+ const localLength = view.getUint16(local + 26, true);
67
+ const start = local + 30 + localLength + view.getUint16(local + 28, true);
68
+ if (start + compressed > view.getUint32(end + 16, true) ||
69
+ decoder.decode(bytes.subarray(local + 30, local + 30 + localLength)) !== name ||
70
+ view.getUint16(local + 6, true) !== flags ||
71
+ view.getUint16(local + 8, true) !== method)
72
+ throw new Error("invalid_zip");
73
+ if (!(flags & 8) &&
74
+ (view.getUint32(local + 14, true) !== view.getUint32(offset + 16, true) ||
75
+ view.getUint32(local + 18, true) !== compressed ||
76
+ view.getUint32(local + 22, true) !== size))
77
+ throw new Error("invalid_zip");
78
+ entries.set(name, { size, crc: view.getUint32(offset + 16, true) });
79
+ offset = next;
80
+ }
81
+ if (offset !== stop)
82
+ throw new Error("invalid_zip");
83
+ return entries;
84
+ };
85
+ export const readContainer = async (bytes, signal) => {
86
+ cancelled(signal);
87
+ const entries = directory(bytes);
88
+ const result = new Map();
89
+ const seen = new Set();
90
+ const done = new Set();
91
+ let expanded = 0;
92
+ const archive = new Unzip((file) => {
93
+ const expected = entries.get(file.name);
94
+ if (!expected || seen.has(file.name))
95
+ throw new Error("invalid_zip");
96
+ seen.add(file.name);
97
+ const keep = /\.(xml|rels|opf|xhtml|html)$/i.test(file.name) || file.name === "mimetype";
98
+ let size = 0;
99
+ let checksum = 0;
100
+ const parts = [];
101
+ file.ondata = (error, data, final) => {
102
+ if (error)
103
+ throw error;
104
+ size += data.length;
105
+ expanded += data.length;
106
+ if (expanded > MAX_EXPANDED || (keep && size > MAX_PART))
107
+ throw new ReaderLimit();
108
+ checksum = crc32(data, checksum);
109
+ if (keep)
110
+ parts.push(data.slice());
111
+ if (final) {
112
+ if (size !== expected.size || checksum !== expected.crc)
113
+ throw new Error("invalid_zip");
114
+ done.add(file.name);
115
+ if (keep) {
116
+ const content = new Uint8Array(size);
117
+ let offset = 0;
118
+ for (const part of parts) {
119
+ content.set(part, offset);
120
+ offset += part.length;
121
+ }
122
+ result.set(file.name, content);
123
+ parts.length = 0;
124
+ }
125
+ }
126
+ };
127
+ file.start();
128
+ });
129
+ archive.register(UnzipInflate);
130
+ // Small compressed chunks bound allocation before the expanded-byte check runs.
131
+ for (let offset = 0; offset < bytes.length; offset += 1024) {
132
+ cancelled(signal);
133
+ archive.push(bytes.subarray(offset, offset + 1024), offset + 1024 >= bytes.length);
134
+ if (offset % 65_536 === 0)
135
+ await tick();
136
+ }
137
+ cancelled(signal);
138
+ if (seen.size !== entries.size || done.size !== entries.size)
139
+ throw new Error("incomplete_zip");
140
+ return result;
141
+ };
142
+ const checkXml = (text) => {
143
+ if (/<!DOCTYPE|<!ENTITY/i.test(text))
144
+ throw new Error("unsupported_xml");
145
+ let depth = 0;
146
+ let nodes = 0;
147
+ for (let index = 0; index < text.length; index++) {
148
+ if (text[index] !== "<")
149
+ continue;
150
+ const skip = text.startsWith("<!--", index)
151
+ ? "-->"
152
+ : text.startsWith("<![CDATA[", index)
153
+ ? "]]>"
154
+ : text.startsWith("<?", index)
155
+ ? "?>"
156
+ : undefined;
157
+ if (skip) {
158
+ if (++nodes > 200_000)
159
+ throw new ReaderLimit();
160
+ const end = text.indexOf(skip, index + 2);
161
+ if (end < 0)
162
+ throw new Error("invalid_xml");
163
+ index = end + skip.length - 1;
164
+ continue;
165
+ }
166
+ const start = index;
167
+ let quote = "";
168
+ for (index++; index < text.length; index++) {
169
+ const char = text[index];
170
+ if (quote) {
171
+ if (char === quote)
172
+ quote = "";
173
+ }
174
+ else if (char === '"' || char === "'")
175
+ quote = char;
176
+ else if (char === ">")
177
+ break;
178
+ }
179
+ if (index === text.length)
180
+ throw new Error("invalid_xml");
181
+ const tag = text.slice(start, index + 1);
182
+ if (tag.startsWith("</"))
183
+ depth--;
184
+ else if (!/\/\s*>$/.test(tag))
185
+ depth++;
186
+ if (++nodes > 200_000 || depth > 128)
187
+ throw new ReaderLimit();
188
+ if (depth < 0)
189
+ throw new Error("invalid_xml");
190
+ }
191
+ if (depth !== 0)
192
+ throw new Error("invalid_xml");
193
+ };
194
+ export const xmlPart = (archive, path) => {
195
+ const bytes = archive.get(path);
196
+ if (!bytes)
197
+ throw new Error("missing_part");
198
+ let text = decodeXml(bytes);
199
+ if (/\.xhtml$/i.test(path)) {
200
+ let start = 0;
201
+ while (start < text.length) {
202
+ start += /^\s*/.exec(text.slice(start))?.[0].length ?? 0;
203
+ const end = text.startsWith("<?", start) ? "?>" : text.startsWith("<!--", start) ? "-->" : undefined;
204
+ if (!end)
205
+ break;
206
+ const index = text.indexOf(end, start + 2);
207
+ if (index < 0)
208
+ throw new Error("invalid_xml");
209
+ start = index + end.length;
210
+ }
211
+ const doctype = /^<!DOCTYPE\s+html\s*>/i.exec(text.slice(start));
212
+ if (doctype)
213
+ text = text.slice(0, start) + text.slice(start + doctype[0].length);
214
+ }
215
+ checkXml(text);
216
+ const document = new DOMParser({
217
+ onError: () => {
218
+ throw new Error("invalid_xml");
219
+ },
220
+ }).parseFromString(text, "application/xml");
221
+ if (!document.documentElement)
222
+ throw new Error("invalid_xml");
223
+ return document;
224
+ };
225
+ export const elements = (node, name, namespace) => {
226
+ const found = [];
227
+ const visit = (current) => {
228
+ if (current.nodeType === 1 && current.localName === name && current.namespaceURI === namespace)
229
+ found.push(current);
230
+ for (let child = current.firstChild; child; child = child.nextSibling)
231
+ visit(child);
232
+ };
233
+ visit(node);
234
+ return found;
235
+ };
236
+ //# sourceMappingURL=container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/attachments/container.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Extraction, FileHint } from "./extract.js";
2
+ export type DocumentKind = "odt" | "ods" | "xlsx" | "pptx" | "epub";
3
+ export declare const documentKind: (hint: FileHint) => DocumentKind | undefined;
4
+ export declare const documentExtractor: (kind: DocumentKind) => string;
5
+ export declare const readDocument: (bytes: Uint8Array, kind: DocumentKind, signal?: AbortSignal) => Promise<Extraction>;
6
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/attachments/documents.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAexD,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AASnE,eAAO,MAAM,YAAY,SAAU,QAAQ,KAAG,YAAY,GAAG,SAQ5D,CAAA;AACD,eAAO,MAAM,iBAAiB,SAAU,YAAY,WAA0B,CAAA;AA2R9E,eAAO,MAAM,YAAY,UAChB,UAAU,QACX,YAAY,WACT,WAAW,KACnB,OAAO,CAAC,UAAU,CAgBpB,CAAA"}
@@ -0,0 +1,355 @@
1
+ import { posix } from "node:path";
2
+ import { CliError } from "@leemour/cli-core";
3
+ import { elements, ReaderLimit, readContainer, xmlPart } from "./container.js";
4
+ import { MAX_TEXT_CHARS as MAX_TEXT } from "./limits.js";
5
+ const OFFICE = "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
6
+ const TEXT = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
7
+ const TABLE = "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
8
+ const PACKAGE_REL = "http://schemas.openxmlformats.org/package/2006/relationships";
9
+ const REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
10
+ const STRICT_REL = "http://purl.oclc.org/ooxml/officeDocument/relationships";
11
+ const SHEET = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
12
+ const SLIDE = "http://schemas.openxmlformats.org/presentationml/2006/main";
13
+ const DRAW = "http://schemas.openxmlformats.org/drawingml/2006/main";
14
+ const OPF = "http://www.idpf.org/2007/opf";
15
+ const XHTML = "http://www.w3.org/1999/xhtml";
16
+ const TYPES = {
17
+ odt: "application/vnd.oasis.opendocument.text",
18
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
19
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
20
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
21
+ epub: "application/epub+zip",
22
+ };
23
+ export const documentKind = (hint) => {
24
+ const extension = posix
25
+ .extname(hint.name ?? hint.path)
26
+ .slice(1)
27
+ .toLowerCase();
28
+ return Object.keys(TYPES).find((kind) => kind === extension || TYPES[kind] === hint.mime?.split(";")[0]?.trim().toLowerCase());
29
+ };
30
+ export const documentExtractor = (kind) => `document:v1:${kind}`;
31
+ class Output {
32
+ parts = [];
33
+ length = 0;
34
+ real = false;
35
+ add(value, source = true) {
36
+ this.length += value.length;
37
+ if (this.length > MAX_TEXT)
38
+ throw new ReaderLimit();
39
+ this.parts.push(value);
40
+ if (source && value.trim())
41
+ this.real = true;
42
+ }
43
+ section(label) {
44
+ this.add(`\n\n[${label}]\n`, false);
45
+ }
46
+ text() {
47
+ return this.parts.join("").trim();
48
+ }
49
+ }
50
+ const attribute = (node, name, namespace) => (namespace ? node.getAttributeNS(namespace, name) : node.getAttribute(name)) ?? "";
51
+ const required = (value) => {
52
+ if (!value)
53
+ throw new Error("missing_value");
54
+ return value;
55
+ };
56
+ const count = (value) => {
57
+ if (!value)
58
+ return 1;
59
+ if (!/^[1-9]\d{0,7}$/.test(value))
60
+ throw new ReaderLimit();
61
+ return Number(value);
62
+ };
63
+ const rootIs = (node, name, namespaces) => {
64
+ const root = node.nodeType === 9 ? node.firstChild : node;
65
+ // XML declarations/comments can precede the document element.
66
+ let element = root;
67
+ while (element && element.nodeType !== 1)
68
+ element = element.nextSibling;
69
+ if (!element || element.localName !== name || !namespaces.includes(element.namespaceURI ?? ""))
70
+ throw new Error("wrong_format");
71
+ return element.namespaceURI;
72
+ };
73
+ const target = (owner, reference) => {
74
+ const decoded = decodeURIComponent(reference.split(/[?#]/)[0] ?? "");
75
+ if (/^[a-z][a-z0-9+.-]*:|^[\\/]|\\/i.test(decoded) || [...decoded].some((character) => character.charCodeAt(0) < 32))
76
+ throw new Error("external_part");
77
+ const path = posix.normalize(posix.join(posix.dirname(owner), decoded));
78
+ if (path.startsWith("../") || !path || path === ".")
79
+ throw new Error("external_part");
80
+ return path;
81
+ };
82
+ const relationships = (archive, owner) => {
83
+ const path = owner ? posix.join(posix.dirname(owner), "_rels", `${posix.basename(owner)}.rels`) : "_rels/.rels";
84
+ const document = xmlPart(archive, path);
85
+ rootIs(document, "Relationships", [PACKAGE_REL]);
86
+ const result = new Map();
87
+ for (const entry of elements(document, "Relationship", PACKAGE_REL)) {
88
+ if (attribute(entry, "TargetMode") === "External")
89
+ continue;
90
+ const id = required(attribute(entry, "Id"));
91
+ if (result.has(id))
92
+ throw new Error("duplicate_relationship");
93
+ result.set(id, {
94
+ path: target(owner, required(attribute(entry, "Target"))),
95
+ type: required(attribute(entry, "Type")),
96
+ });
97
+ }
98
+ return result;
99
+ };
100
+ const relationId = (element) => required(attribute(element, "id", REL) || attribute(element, "id", STRICT_REL));
101
+ const mainPart = (archive, contentType) => {
102
+ const types = xmlPart(archive, "[Content_Types].xml");
103
+ const namespace = "http://schemas.openxmlformats.org/package/2006/content-types";
104
+ rootIs(types, "Types", [namespace]);
105
+ const matches = [...relationships(archive, "").values()].filter((entry) => entry.type.endsWith("/officeDocument"));
106
+ if (matches.length !== 1)
107
+ throw new Error("missing_main_part");
108
+ const path = matches[0]?.path;
109
+ if (!elements(types, "Override", namespace).some((entry) => attribute(entry, "PartName") === `/${path}` && attribute(entry, "ContentType") === contentType))
110
+ throw new Error("wrong_format");
111
+ return path;
112
+ };
113
+ const odfText = (node) => {
114
+ if (node.nodeType === 3 || node.nodeType === 4)
115
+ return node.nodeValue ?? "";
116
+ if (node.nodeType === 1 && node.namespaceURI === TEXT) {
117
+ if (node.localName === "s") {
118
+ const repeated = count(attribute(node, "c", TEXT));
119
+ if (repeated > MAX_TEXT)
120
+ throw new ReaderLimit();
121
+ return " ".repeat(repeated);
122
+ }
123
+ if (node.localName === "tab")
124
+ return "\t";
125
+ if (node.localName === "line-break")
126
+ return "\n";
127
+ }
128
+ let value = "";
129
+ for (let child = node.firstChild; child; child = child.nextSibling) {
130
+ value += odfText(child);
131
+ if (value.length > MAX_TEXT)
132
+ throw new ReaderLimit();
133
+ }
134
+ return value;
135
+ };
136
+ const odfParagraphs = (node) => {
137
+ const result = [];
138
+ const visit = (current) => {
139
+ if (current.nodeType === 1 &&
140
+ current.namespaceURI === TEXT &&
141
+ (current.localName === "p" || current.localName === "h")) {
142
+ result.push(current);
143
+ return;
144
+ }
145
+ for (let child = current.firstChild; child; child = child.nextSibling)
146
+ visit(child);
147
+ };
148
+ visit(node);
149
+ return result;
150
+ };
151
+ const readOdf = (archive, kind, output) => {
152
+ if (new TextDecoder().decode(archive.get("mimetype")) !== TYPES[kind])
153
+ throw new Error("wrong_format");
154
+ const document = xmlPart(archive, "content.xml");
155
+ rootIs(document, "document-content", [OFFICE]);
156
+ if (archive.has("META-INF/manifest.xml")) {
157
+ const manifest = xmlPart(archive, "META-INF/manifest.xml");
158
+ if (elements(manifest, "encryption-data", "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0").length)
159
+ throw new Error("encrypted_document");
160
+ }
161
+ const body = elements(document, kind === "odt" ? "text" : "spreadsheet", OFFICE);
162
+ if (body.length !== 1)
163
+ throw new Error("wrong_format");
164
+ if (kind === "odt") {
165
+ for (const paragraph of odfParagraphs(body[0]))
166
+ output.add(`${odfText(paragraph)}\n`);
167
+ return;
168
+ }
169
+ for (const sheet of elements(body[0], "table", TABLE)) {
170
+ output.section(`Sheet: ${required(attribute(sheet, "name", TABLE))}`);
171
+ let rowNumber = 1;
172
+ for (const row of elements(sheet, "table-row", TABLE)) {
173
+ const repeatedRows = count(attribute(row, "number-rows-repeated", TABLE));
174
+ const values = [];
175
+ let column = 1;
176
+ for (let child = row.firstChild; child; child = child.nextSibling) {
177
+ if (child.nodeType !== 1 ||
178
+ child.namespaceURI !== TABLE ||
179
+ !["table-cell", "covered-table-cell"].includes(child.localName ?? ""))
180
+ continue;
181
+ const cell = child;
182
+ const repeats = count(attribute(cell, "number-columns-repeated", TABLE));
183
+ const paragraphs = odfParagraphs(cell).map(odfText).join("\n");
184
+ const value = paragraphs ||
185
+ attribute(cell, "string-value", OFFICE) ||
186
+ attribute(cell, "value", OFFICE) ||
187
+ attribute(cell, "date-value", OFFICE) ||
188
+ attribute(cell, "time-value", OFFICE) ||
189
+ attribute(cell, "boolean-value", OFFICE);
190
+ const formula = attribute(cell, "formula", TABLE);
191
+ if (value || formula)
192
+ values.push({ column, repeats, value: `${value}${formula ? ` [formula: ${formula}]` : ""}` });
193
+ column += repeats;
194
+ }
195
+ if (values.length) {
196
+ for (let repeat = 0; repeat < repeatedRows; repeat++) {
197
+ for (const cell of values) {
198
+ if (cell.repeats * repeatedRows * (cell.value.length + 5) > MAX_TEXT)
199
+ throw new ReaderLimit();
200
+ for (let offset = 0; offset < cell.repeats; offset++)
201
+ output.add(`R${rowNumber + repeat}C${cell.column + offset}: ${cell.value}\n`);
202
+ }
203
+ }
204
+ }
205
+ rowNumber += repeatedRows;
206
+ }
207
+ }
208
+ };
209
+ const readSheet = (archive, output) => {
210
+ const path = mainPart(archive, `${TYPES.xlsx}.main+xml`);
211
+ const document = xmlPart(archive, path);
212
+ const namespace = rootIs(document, "workbook", [SHEET, "http://purl.oclc.org/ooxml/spreadsheetml/main"]);
213
+ const rels = relationships(archive, path);
214
+ const shared = [...rels.values()].find((entry) => entry.type.endsWith("/sharedStrings"));
215
+ const strings = shared
216
+ ? elements(xmlPart(archive, shared.path), "si", namespace).map((entry) => elements(entry, "t", namespace)
217
+ .map((part) => part.textContent ?? "")
218
+ .join(""))
219
+ : [];
220
+ for (const sheet of elements(document, "sheet", namespace)) {
221
+ output.section(`Sheet: ${required(attribute(sheet, "name"))}`);
222
+ const relationship = rels.get(relationId(sheet));
223
+ if (!relationship?.type.endsWith("/worksheet"))
224
+ throw new Error("missing_sheet");
225
+ const worksheet = xmlPart(archive, relationship.path);
226
+ rootIs(worksheet, "worksheet", [namespace]);
227
+ for (const cell of elements(worksheet, "c", namespace)) {
228
+ const reference = required(attribute(cell, "r"));
229
+ if (!/^[A-Z]+[1-9]\d*$/.test(reference))
230
+ throw new Error("invalid_cell");
231
+ const type = attribute(cell, "t");
232
+ const saved = elements(cell, "v", namespace)[0]?.textContent ?? "";
233
+ let value = type === "inlineStr"
234
+ ? elements(cell, "t", namespace)
235
+ .map((part) => part.textContent ?? "")
236
+ .join("")
237
+ : saved;
238
+ if (type === "s") {
239
+ if (!/^\d+$/.test(saved) || strings[Number(saved)] === undefined)
240
+ throw new Error("missing_shared_string");
241
+ value = strings[Number(saved)];
242
+ }
243
+ const formula = elements(cell, "f", namespace)[0]?.textContent;
244
+ if (value || formula)
245
+ output.add(`${reference}: ${value}${formula ? ` [formula: ${formula}]` : ""}\n`);
246
+ }
247
+ }
248
+ };
249
+ const drawingText = (node, namespace) => {
250
+ if (node.nodeType === 1 && node.namespaceURI === namespace) {
251
+ if (node.localName === "t")
252
+ return node.textContent ?? "";
253
+ if (node.localName === "br")
254
+ return "\n";
255
+ if (node.localName === "tab")
256
+ return "\t";
257
+ }
258
+ let value = "";
259
+ for (let child = node.firstChild; child; child = child.nextSibling) {
260
+ value += drawingText(child, namespace);
261
+ if (value.length > MAX_TEXT)
262
+ throw new ReaderLimit();
263
+ }
264
+ return value;
265
+ };
266
+ const readSlides = (archive, output) => {
267
+ const path = mainPart(archive, `${TYPES.pptx}.main+xml`);
268
+ const document = xmlPart(archive, path);
269
+ const namespace = rootIs(document, "presentation", [SLIDE, "http://purl.oclc.org/ooxml/presentationml/main"]);
270
+ const rels = relationships(archive, path);
271
+ let number = 0;
272
+ for (const slide of elements(document, "sldId", namespace)) {
273
+ const relationship = rels.get(relationId(slide));
274
+ if (!relationship?.type.endsWith("/slide"))
275
+ throw new Error("missing_slide");
276
+ const content = xmlPart(archive, relationship.path);
277
+ rootIs(content, "sld", [namespace]);
278
+ output.section(`Slide ${++number}`);
279
+ const drawing = namespace === SLIDE ? DRAW : "http://purl.oclc.org/ooxml/drawingml/main";
280
+ for (const paragraph of elements(content, "p", drawing))
281
+ output.add(`${drawingText(paragraph, drawing)}\n`);
282
+ }
283
+ };
284
+ const readBook = (archive, output) => {
285
+ if (new TextDecoder().decode(archive.get("mimetype")) !== TYPES.epub || archive.has("META-INF/encryption.xml"))
286
+ throw new Error("wrong_format");
287
+ const container = xmlPart(archive, "META-INF/container.xml");
288
+ const roots = elements(container, "rootfile", "urn:oasis:names:tc:opendocument:xmlns:container");
289
+ const root = roots.find((entry) => attribute(entry, "media-type") === "application/oebps-package+xml");
290
+ const path = target("", required(root ? attribute(root, "full-path") : ""));
291
+ const book = xmlPart(archive, path);
292
+ rootIs(book, "package", [OPF]);
293
+ const manifest = new Map();
294
+ for (const item of elements(book, "item", OPF)) {
295
+ const id = required(attribute(item, "id"));
296
+ if (manifest.has(id))
297
+ throw new Error("duplicate_chapter");
298
+ manifest.set(id, { path: target(path, required(attribute(item, "href"))), type: attribute(item, "media-type") });
299
+ }
300
+ let number = 0;
301
+ const chapters = elements(book, "itemref", OPF);
302
+ if (!chapters.length)
303
+ throw new Error("missing_chapters");
304
+ for (const chapter of chapters) {
305
+ const item = manifest.get(required(attribute(chapter, "idref")));
306
+ if (item?.type !== "application/xhtml+xml")
307
+ throw new Error("unsupported_chapter");
308
+ const document = xmlPart(archive, item.path);
309
+ const bodies = elements(document, "body", XHTML);
310
+ if (bodies.length !== 1)
311
+ throw new Error("missing_chapter");
312
+ output.section(`Chapter ${++number}`);
313
+ const visit = (node) => {
314
+ if (node.nodeType === 3 || node.nodeType === 4) {
315
+ output.add(node.nodeValue ?? "");
316
+ return;
317
+ }
318
+ if (node.nodeType === 1 && ["script", "style", "head"].includes(node.localName ?? ""))
319
+ return;
320
+ for (let child = node.firstChild; child; child = child.nextSibling)
321
+ visit(child);
322
+ if (node.nodeType === 1 && ["td", "th"].includes(node.localName ?? ""))
323
+ output.add("\t", false);
324
+ if (node.nodeType === 1 && ["p", "div", "li", "h1", "h2", "h3", "br", "tr"].includes(node.localName ?? ""))
325
+ output.add("\n", false);
326
+ };
327
+ visit(bodies[0]);
328
+ }
329
+ };
330
+ export const readDocument = async (bytes, kind, signal) => {
331
+ const extractor = documentExtractor(kind);
332
+ try {
333
+ const archive = await readContainer(bytes, signal);
334
+ const output = new Output();
335
+ if (kind === "odt" || kind === "ods")
336
+ readOdf(archive, kind, output);
337
+ else if (kind === "xlsx")
338
+ readSheet(archive, output);
339
+ else if (kind === "pptx")
340
+ readSlides(archive, output);
341
+ else
342
+ readBook(archive, output);
343
+ if (signal?.aborted)
344
+ throw new CliError("cancelled", "document extraction cancelled");
345
+ return output.real ? { status: "extracted", extractor, text: output.text() } : { status: "needs-agent", extractor };
346
+ }
347
+ catch (error) {
348
+ if (error instanceof CliError)
349
+ throw error;
350
+ if (error instanceof ReaderLimit)
351
+ return { status: "too-large" };
352
+ return { status: "unreadable", extractor, error: "invalid_document" };
353
+ }
354
+ };
355
+ //# sourceMappingURL=documents.js.map