@usejunior/odf-core 0.9.1

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/.tsbuildinfo +1 -0
  2. package/dist/comments.d.ts +55 -0
  3. package/dist/comments.d.ts.map +1 -0
  4. package/dist/comments.js +230 -0
  5. package/dist/comments.js.map +1 -0
  6. package/dist/compare/diff.d.ts +51 -0
  7. package/dist/compare/diff.d.ts.map +1 -0
  8. package/dist/compare/diff.js +189 -0
  9. package/dist/compare/diff.js.map +1 -0
  10. package/dist/compare/emit.d.ts +64 -0
  11. package/dist/compare/emit.d.ts.map +1 -0
  12. package/dist/compare/emit.js +404 -0
  13. package/dist/compare/emit.js.map +1 -0
  14. package/dist/compare/index.d.ts +49 -0
  15. package/dist/compare/index.d.ts.map +1 -0
  16. package/dist/compare/index.js +59 -0
  17. package/dist/compare/index.js.map +1 -0
  18. package/dist/compare/inline_diff.d.ts +32 -0
  19. package/dist/compare/inline_diff.d.ts.map +1 -0
  20. package/dist/compare/inline_diff.js +102 -0
  21. package/dist/compare/inline_diff.js.map +1 -0
  22. package/dist/compare/inline_map.d.ts +40 -0
  23. package/dist/compare/inline_map.d.ts.map +1 -0
  24. package/dist/compare/inline_map.js +153 -0
  25. package/dist/compare/inline_map.js.map +1 -0
  26. package/dist/document.d.ts +98 -0
  27. package/dist/document.d.ts.map +1 -0
  28. package/dist/document.js +172 -0
  29. package/dist/document.js.map +1 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +6 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/odf_archive_safety.d.ts +18 -0
  35. package/dist/odf_archive_safety.d.ts.map +1 -0
  36. package/dist/odf_archive_safety.js +72 -0
  37. package/dist/odf_archive_safety.js.map +1 -0
  38. package/dist/shared/odf/OdfArchive.d.ts +50 -0
  39. package/dist/shared/odf/OdfArchive.d.ts.map +1 -0
  40. package/dist/shared/odf/OdfArchive.js +118 -0
  41. package/dist/shared/odf/OdfArchive.js.map +1 -0
  42. package/dist/shared/odf/blocks.d.ts +15 -0
  43. package/dist/shared/odf/blocks.d.ts.map +1 -0
  44. package/dist/shared/odf/blocks.js +36 -0
  45. package/dist/shared/odf/blocks.js.map +1 -0
  46. package/dist/shared/odf/namespaces.d.ts +27 -0
  47. package/dist/shared/odf/namespaces.d.ts.map +1 -0
  48. package/dist/shared/odf/namespaces.js +29 -0
  49. package/dist/shared/odf/namespaces.js.map +1 -0
  50. package/dist/shared/odf/text_segments.d.ts +63 -0
  51. package/dist/shared/odf/text_segments.d.ts.map +1 -0
  52. package/dist/shared/odf/text_segments.js +90 -0
  53. package/dist/shared/odf/text_segments.js.map +1 -0
  54. package/package.json +54 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ODF archive-safety guard.
3
+ *
4
+ * Reuses docx-core's format-agnostic `inspectZipEntries` for the zip-bomb / entry
5
+ * limits (entry count, single-entry size, total uncompressed size, compression
6
+ * ratio), and additionally asserts the package declares the OpenDocument text
7
+ * mimetype. Returns a plain result; the MCP layer maps failures to tool errors.
8
+ */
9
+ export type OdfArchiveSafetyResult = {
10
+ ok: true;
11
+ } | {
12
+ ok: false;
13
+ code: string;
14
+ message: string;
15
+ hint: string;
16
+ };
17
+ export declare function validateOdfArchiveSafety(buffer: Buffer): Promise<OdfArchiveSafetyResult>;
18
+ //# sourceMappingURL=odf_archive_safety.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"odf_archive_safety.d.ts","sourceRoot":"","sources":["../src/odf_archive_safety.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAmB/D,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAwE9F"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * ODF archive-safety guard.
3
+ *
4
+ * Reuses docx-core's format-agnostic `inspectZipEntries` for the zip-bomb / entry
5
+ * limits (entry count, single-entry size, total uncompressed size, compression
6
+ * ratio), and additionally asserts the package declares the OpenDocument text
7
+ * mimetype. Returns a plain result; the MCP layer maps failures to tool errors.
8
+ */
9
+ import { inspectZipEntries } from '@usejunior/docx-core';
10
+ import JSZip from 'jszip';
11
+ import { ODF_PATHS, ODT_MIMETYPE } from './shared/odf/namespaces.js';
12
+ function readIntEnv(name, fallback) {
13
+ const raw = process.env[name];
14
+ if (!raw)
15
+ return fallback;
16
+ const parsed = Number.parseInt(raw, 10);
17
+ if (!Number.isFinite(parsed) || parsed <= 0)
18
+ return fallback;
19
+ return parsed;
20
+ }
21
+ const MAX_ARCHIVE_ENTRIES = () => readIntEnv('ODF_MAX_ARCHIVE_ENTRIES', 2000);
22
+ const MAX_TOTAL_UNCOMPRESSED_BYTES = () => readIntEnv('ODF_MAX_UNCOMPRESSED_BYTES', 200 * 1024 * 1024);
23
+ const MAX_SINGLE_ENTRY_UNCOMPRESSED_BYTES = () => readIntEnv('ODF_MAX_ENTRY_UNCOMPRESSED_BYTES', 50 * 1024 * 1024);
24
+ const MAX_COMPRESSION_RATIO = () => readIntEnv('ODF_MAX_COMPRESSION_RATIO', 200);
25
+ function fail(code, message, hint) {
26
+ return { ok: false, code, message, hint };
27
+ }
28
+ export async function validateOdfArchiveSafety(buffer) {
29
+ let entries;
30
+ try {
31
+ entries = await inspectZipEntries(buffer);
32
+ }
33
+ catch (e) {
34
+ return fail('INVALID_ODF_ARCHIVE', `Unable to parse .odt archive: ${e instanceof Error ? e.message : String(e)}`, 'Ensure the input file is a valid .odt (OpenDocument text) package.');
35
+ }
36
+ const files = entries.filter((entry) => !entry.isDirectory);
37
+ if (files.length > MAX_ARCHIVE_ENTRIES()) {
38
+ return fail('ODF_ARCHIVE_TOO_MANY_ENTRIES', `Archive contains ${files.length} entries (max ${MAX_ARCHIVE_ENTRIES()}).`, 'Use a simpler .odt package or raise ODF_MAX_ARCHIVE_ENTRIES intentionally.');
39
+ }
40
+ let totalUncompressed = 0;
41
+ for (const file of files) {
42
+ const { compressedSize, uncompressedSize } = file;
43
+ totalUncompressed += uncompressedSize;
44
+ if (uncompressedSize > MAX_SINGLE_ENTRY_UNCOMPRESSED_BYTES()) {
45
+ return fail('ODF_ARCHIVE_ENTRY_TOO_LARGE', `Archive entry '${file.name}' is ${uncompressedSize} bytes uncompressed (max ${MAX_SINGLE_ENTRY_UNCOMPRESSED_BYTES()}).`, 'Reduce embedded object sizes or raise ODF_MAX_ENTRY_UNCOMPRESSED_BYTES intentionally.');
46
+ }
47
+ if (totalUncompressed > MAX_TOTAL_UNCOMPRESSED_BYTES()) {
48
+ return fail('ODF_ARCHIVE_UNCOMPRESSED_TOO_LARGE', `Archive expands to ${totalUncompressed} bytes (max ${MAX_TOTAL_UNCOMPRESSED_BYTES()}).`, 'Reduce archive complexity or raise ODF_MAX_UNCOMPRESSED_BYTES intentionally.');
49
+ }
50
+ if (uncompressedSize > 0) {
51
+ const ratio = compressedSize > 0 ? uncompressedSize / compressedSize : Number.POSITIVE_INFINITY;
52
+ if (ratio > MAX_COMPRESSION_RATIO()) {
53
+ return fail('ODF_ARCHIVE_COMPRESSION_RATIO_TOO_HIGH', `Archive entry '${file.name}' has compression ratio ${ratio.toFixed(2)} (max ${MAX_COMPRESSION_RATIO()}).`, 'This may indicate a highly compressed or hostile archive. Adjust ODF_MAX_COMPRESSION_RATIO only if trusted.');
54
+ }
55
+ }
56
+ }
57
+ // ODF identity: the package must declare the OpenDocument text mimetype.
58
+ let mimetype = null;
59
+ try {
60
+ const zip = await JSZip.loadAsync(buffer);
61
+ const mimeFile = zip.file(ODF_PATHS.MIMETYPE);
62
+ mimetype = mimeFile ? (await mimeFile.async('string')).trim() : null;
63
+ }
64
+ catch {
65
+ mimetype = null;
66
+ }
67
+ if (mimetype !== ODT_MIMETYPE) {
68
+ return fail('INVALID_ODF_ARCHIVE', `Not an OpenDocument text package (mimetype: ${mimetype ?? 'absent'}).`, `Provide a .odt whose 'mimetype' entry is '${ODT_MIMETYPE}'.`);
69
+ }
70
+ return { ok: true };
71
+ }
72
+ //# sourceMappingURL=odf_archive_safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"odf_archive_safety.js","sourceRoot":"","sources":["../src/odf_archive_safety.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * ODF package handler (parallel to docx-core's `DocxArchive`).
3
+ *
4
+ * ODF is a ZIP-of-XML like DOCX, but with one sharp packaging rule that DOCX does
5
+ * not have: the `mimetype` entry MUST be the FIRST entry in the archive and stored
6
+ * UNCOMPRESSED (STORE, not DEFLATE). Strict ODF readers reject a package that
7
+ * violates this.
8
+ *
9
+ * CRITICAL (verified against JSZip 3.10.1): a fresh JSZip honors mimetype-first +
10
+ * `{ compression: 'STORE' }`, but if you LOAD an existing `.odt` and re-`generateAsync`
11
+ * the loaded handle, JSZip re-emits the existing `mimetype` entry as DEFLATE
12
+ * (method 8) — producing an invalid `.odt`. The ONLY reliable fix is to REBUILD a
13
+ * fresh JSZip on save: write `mimetype` first with STORE, then copy every other
14
+ * entry's decompressed content. `save()` below does exactly that. Do not "optimize"
15
+ * it back into re-saving the loaded handle.
16
+ */
17
+ export declare class OdfArchive {
18
+ private zip;
19
+ private modified;
20
+ private constructor();
21
+ /**
22
+ * Load an ODF package from a Buffer. Rejects a buffer that is missing the
23
+ * required `content.xml` or `META-INF/manifest.xml` parts.
24
+ */
25
+ static load(buffer: Buffer): Promise<OdfArchive>;
26
+ /** Get `content.xml` as a string. */
27
+ getContentXml(): Promise<string>;
28
+ /** Replace `content.xml`. */
29
+ setContentXml(xml: string): void;
30
+ /** Get an arbitrary part as a string, or null if absent. */
31
+ getFile(path: string): Promise<string | null>;
32
+ /** Set an arbitrary part. */
33
+ setFile(path: string, content: string): void;
34
+ /** Whether a part exists. */
35
+ hasFile(path: string): boolean;
36
+ /** List all non-directory entry names in the archive. */
37
+ listFiles(): string[];
38
+ /** Paths modified since load. */
39
+ getModifiedPaths(): string[];
40
+ /**
41
+ * Save the archive to a Buffer.
42
+ *
43
+ * Rebuilds a fresh JSZip so the `mimetype` entry is guaranteed first + STORE
44
+ * across a load→modify→save round trip (see the class-level note). Untouched
45
+ * entries keep byte-identical DECOMPRESSED content; the compressed container
46
+ * bytes may differ (re-deflation), which matches the DOCX side's guarantee.
47
+ */
48
+ save(): Promise<Buffer>;
49
+ }
50
+ //# sourceMappingURL=OdfArchive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OdfArchive.d.ts","sourceRoot":"","sources":["../../../src/shared/odf/OdfArchive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,qBAAa,UAAU;IACrB,OAAO,CAAC,GAAG,CAAQ;IACnB,OAAO,CAAC,QAAQ,CAA0B;IAE1C,OAAO;IAIP;;;OAGG;WACU,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAWtD,qCAAqC;IAC/B,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAQtC,6BAA6B;IAC7B,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAKhC,4DAA4D;IACtD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAMnD,6BAA6B;IAC7B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAK5C,6BAA6B;IAC7B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI9B,yDAAyD;IACzD,SAAS,IAAI,MAAM,EAAE;IAQrB,iCAAiC;IACjC,gBAAgB,IAAI,MAAM,EAAE;IAI5B;;;;;;;OAOG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CA6B9B"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * ODF package handler (parallel to docx-core's `DocxArchive`).
3
+ *
4
+ * ODF is a ZIP-of-XML like DOCX, but with one sharp packaging rule that DOCX does
5
+ * not have: the `mimetype` entry MUST be the FIRST entry in the archive and stored
6
+ * UNCOMPRESSED (STORE, not DEFLATE). Strict ODF readers reject a package that
7
+ * violates this.
8
+ *
9
+ * CRITICAL (verified against JSZip 3.10.1): a fresh JSZip honors mimetype-first +
10
+ * `{ compression: 'STORE' }`, but if you LOAD an existing `.odt` and re-`generateAsync`
11
+ * the loaded handle, JSZip re-emits the existing `mimetype` entry as DEFLATE
12
+ * (method 8) — producing an invalid `.odt`. The ONLY reliable fix is to REBUILD a
13
+ * fresh JSZip on save: write `mimetype` first with STORE, then copy every other
14
+ * entry's decompressed content. `save()` below does exactly that. Do not "optimize"
15
+ * it back into re-saving the loaded handle.
16
+ */
17
+ import JSZip from 'jszip';
18
+ import { ODF_PATHS, ODT_MIMETYPE } from './namespaces.js';
19
+ export class OdfArchive {
20
+ zip;
21
+ modified = new Set();
22
+ constructor(zip) {
23
+ this.zip = zip;
24
+ }
25
+ /**
26
+ * Load an ODF package from a Buffer. Rejects a buffer that is missing the
27
+ * required `content.xml` or `META-INF/manifest.xml` parts.
28
+ */
29
+ static async load(buffer) {
30
+ const zip = await JSZip.loadAsync(buffer);
31
+ if (!zip.file(ODF_PATHS.CONTENT)) {
32
+ throw new Error('Invalid ODF: missing content.xml');
33
+ }
34
+ if (!zip.file(ODF_PATHS.MANIFEST)) {
35
+ throw new Error('Invalid ODF: missing META-INF/manifest.xml');
36
+ }
37
+ return new OdfArchive(zip);
38
+ }
39
+ /** Get `content.xml` as a string. */
40
+ async getContentXml() {
41
+ const file = this.zip.file(ODF_PATHS.CONTENT);
42
+ if (!file) {
43
+ throw new Error('content.xml not found');
44
+ }
45
+ return file.async('string');
46
+ }
47
+ /** Replace `content.xml`. */
48
+ setContentXml(xml) {
49
+ this.zip.file(ODF_PATHS.CONTENT, xml);
50
+ this.modified.add(ODF_PATHS.CONTENT);
51
+ }
52
+ /** Get an arbitrary part as a string, or null if absent. */
53
+ async getFile(path) {
54
+ const file = this.zip.file(path);
55
+ if (!file)
56
+ return null;
57
+ return file.async('string');
58
+ }
59
+ /** Set an arbitrary part. */
60
+ setFile(path, content) {
61
+ this.zip.file(path, content);
62
+ this.modified.add(path);
63
+ }
64
+ /** Whether a part exists. */
65
+ hasFile(path) {
66
+ return this.zip.file(path) !== null;
67
+ }
68
+ /** List all non-directory entry names in the archive. */
69
+ listFiles() {
70
+ const files = [];
71
+ this.zip.forEach((relativePath, file) => {
72
+ if (!file.dir)
73
+ files.push(relativePath);
74
+ });
75
+ return files;
76
+ }
77
+ /** Paths modified since load. */
78
+ getModifiedPaths() {
79
+ return Array.from(this.modified);
80
+ }
81
+ /**
82
+ * Save the archive to a Buffer.
83
+ *
84
+ * Rebuilds a fresh JSZip so the `mimetype` entry is guaranteed first + STORE
85
+ * across a load→modify→save round trip (see the class-level note). Untouched
86
+ * entries keep byte-identical DECOMPRESSED content; the compressed container
87
+ * bytes may differ (re-deflation), which matches the DOCX side's guarantee.
88
+ */
89
+ async save() {
90
+ const out = new JSZip();
91
+ // mimetype FIRST, uncompressed. Use the original value if present (it should be),
92
+ // otherwise fall back to the ODT mimetype.
93
+ const mimeFile = this.zip.file(ODF_PATHS.MIMETYPE);
94
+ const mimeValue = mimeFile ? await mimeFile.async('string') : ODT_MIMETYPE;
95
+ out.file(ODF_PATHS.MIMETYPE, mimeValue, { compression: 'STORE' });
96
+ // Copy every other entry's decompressed content, in the loaded order.
97
+ const names = [];
98
+ this.zip.forEach((relativePath, file) => {
99
+ if (relativePath === ODF_PATHS.MIMETYPE || file.dir)
100
+ return;
101
+ names.push(relativePath);
102
+ });
103
+ for (const name of names) {
104
+ const file = this.zip.file(name);
105
+ if (!file)
106
+ continue;
107
+ const content = await file.async('nodebuffer');
108
+ out.file(name, content);
109
+ }
110
+ const buffer = await out.generateAsync({
111
+ type: 'nodebuffer',
112
+ compression: 'DEFLATE',
113
+ compressionOptions: { level: 6 },
114
+ });
115
+ return buffer;
116
+ }
117
+ }
118
+ //# sourceMappingURL=OdfArchive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OdfArchive.js","sourceRoot":"","sources":["../../../src/shared/odf/OdfArchive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared block-level paragraph enumeration for ODF `content.xml`.
3
+ *
4
+ * Extracted from `document.ts` so both the document view and the comparison engine
5
+ * (`compare/index.ts`) enumerate body paragraphs identically without an import cycle.
6
+ *
7
+ * A "block" is a `text:p` / `text:h` carrying visible content, in document order — including
8
+ * those nested in `table:table-cell`. Two subtrees are skipped because their `text:p`s are not
9
+ * body content: `office:annotation` / `office:annotation-end` (comment bodies) and
10
+ * `text:tracked-changes` (deleted content stored out-of-line). All matching is by
11
+ * `namespaceURI` + `localName` (ODF prefixes are not guaranteed).
12
+ */
13
+ /** Depth-first, document-order collection of `text:p` / `text:h` blocks into `out`. */
14
+ export declare function collectBlocks(node: Node | null, out: Element[]): void;
15
+ //# sourceMappingURL=blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../src/shared/odf/blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,CAgBrE"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared block-level paragraph enumeration for ODF `content.xml`.
3
+ *
4
+ * Extracted from `document.ts` so both the document view and the comparison engine
5
+ * (`compare/index.ts`) enumerate body paragraphs identically without an import cycle.
6
+ *
7
+ * A "block" is a `text:p` / `text:h` carrying visible content, in document order — including
8
+ * those nested in `table:table-cell`. Two subtrees are skipped because their `text:p`s are not
9
+ * body content: `office:annotation` / `office:annotation-end` (comment bodies) and
10
+ * `text:tracked-changes` (deleted content stored out-of-line). All matching is by
11
+ * `namespaceURI` + `localName` (ODF prefixes are not guaranteed).
12
+ */
13
+ import { ELEMENT_NODE, isAnnotationSubtree, isTextBlock, isTrackedChangesSubtree, } from './text_segments.js';
14
+ /** Depth-first, document-order collection of `text:p` / `text:h` blocks into `out`. */
15
+ export function collectBlocks(node, out) {
16
+ if (!node)
17
+ return;
18
+ for (let child = node.firstChild; child; child = child.nextSibling) {
19
+ if (child.nodeType !== ELEMENT_NODE)
20
+ continue;
21
+ const el = child;
22
+ // An annotation carries its own `text:p` comment body; never enumerate it as a block.
23
+ if (isAnnotationSubtree(el))
24
+ continue;
25
+ // `text:tracked-changes` stores deleted paragraphs out-of-line; they are not body blocks.
26
+ if (isTrackedChangesSubtree(el))
27
+ continue;
28
+ if (isTextBlock(el)) {
29
+ out.push(el);
30
+ // Block-level text elements are not nested inside one another in ODF, but
31
+ // continue traversal in case of unusual structures (cost is negligible).
32
+ }
33
+ collectBlocks(el, out);
34
+ }
35
+ }
36
+ //# sourceMappingURL=blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../src/shared/odf/blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,WAAW,EACX,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAE5B,uFAAuF;AACvF,MAAM,UAAU,aAAa,CAAC,IAAiB,EAAE,GAAc;IAC7D,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,KAAK,IAAI,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QACnE,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY;YAAE,SAAS;QAC9C,MAAM,EAAE,GAAG,KAAgB,CAAC;QAC5B,sFAAsF;QACtF,IAAI,mBAAmB,CAAC,EAAE,CAAC;YAAE,SAAS;QACtC,0FAA0F;QAC1F,IAAI,uBAAuB,CAAC,EAAE,CAAC;YAAE,SAAS;QAC1C,IAAI,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACb,0EAA0E;YAC1E,yEAAyE;QAC3E,CAAC;QACD,aAAa,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACzB,CAAC;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * OpenDocument Format (ODF) XML namespace URIs and the OpenDocument text mimetype.
3
+ *
4
+ * ODF content is heavily namespaced; the document view MUST resolve elements via
5
+ * `getElementsByTagNameNS` / `localName` rather than prefixed tag names, because
6
+ * prefixes are not guaranteed by the spec.
7
+ */
8
+ export declare const ODF_NS: {
9
+ readonly OFFICE: "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
10
+ readonly TEXT: "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
11
+ readonly STYLE: "urn:oasis:names:tc:opendocument:xmlns:style:1.0";
12
+ readonly TABLE: "urn:oasis:names:tc:opendocument:xmlns:table:1.0";
13
+ readonly MANIFEST: "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0";
14
+ readonly DC: "http://purl.org/dc/elements/1.1/";
15
+ readonly XML: "http://www.w3.org/XML/1998/namespace";
16
+ };
17
+ /** The mimetype value an OpenDocument text package declares. */
18
+ export declare const ODT_MIMETYPE = "application/vnd.oasis.opendocument.text";
19
+ /** Standard part paths within an ODF package. */
20
+ export declare const ODF_PATHS: {
21
+ readonly MIMETYPE: "mimetype";
22
+ readonly CONTENT: "content.xml";
23
+ readonly STYLES: "styles.xml";
24
+ readonly META: "meta.xml";
25
+ readonly MANIFEST: "META-INF/manifest.xml";
26
+ };
27
+ //# sourceMappingURL=namespaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespaces.d.ts","sourceRoot":"","sources":["../../../src/shared/odf/namespaces.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,MAAM;;;;;;;;CAUT,CAAC;AAEX,gEAAgE;AAChE,eAAO,MAAM,YAAY,4CAA4C,CAAC;AAEtE,iDAAiD;AACjD,eAAO,MAAM,SAAS;;;;;;CAMZ,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * OpenDocument Format (ODF) XML namespace URIs and the OpenDocument text mimetype.
3
+ *
4
+ * ODF content is heavily namespaced; the document view MUST resolve elements via
5
+ * `getElementsByTagNameNS` / `localName` rather than prefixed tag names, because
6
+ * prefixes are not guaranteed by the spec.
7
+ */
8
+ export const ODF_NS = {
9
+ OFFICE: 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
10
+ TEXT: 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
11
+ STYLE: 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
12
+ TABLE: 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
13
+ MANIFEST: 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
14
+ // Dublin Core — carries annotation/change author (`dc:creator`) and date (`dc:date`).
15
+ DC: 'http://purl.org/dc/elements/1.1/',
16
+ // W3C XML namespace — carries `xml:id` on `text:changed-region` for tracked changes.
17
+ XML: 'http://www.w3.org/XML/1998/namespace',
18
+ };
19
+ /** The mimetype value an OpenDocument text package declares. */
20
+ export const ODT_MIMETYPE = 'application/vnd.oasis.opendocument.text';
21
+ /** Standard part paths within an ODF package. */
22
+ export const ODF_PATHS = {
23
+ MIMETYPE: 'mimetype',
24
+ CONTENT: 'content.xml',
25
+ STYLES: 'styles.xml',
26
+ META: 'meta.xml',
27
+ MANIFEST: 'META-INF/manifest.xml',
28
+ };
29
+ //# sourceMappingURL=namespaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespaces.js","sourceRoot":"","sources":["../../../src/shared/odf/namespaces.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,MAAM,EAAE,kDAAkD;IAC1D,IAAI,EAAE,gDAAgD;IACtD,KAAK,EAAE,iDAAiD;IACxD,KAAK,EAAE,iDAAiD;IACxD,QAAQ,EAAE,oDAAoD;IAC9D,sFAAsF;IACtF,EAAE,EAAE,kCAAkC;IACtC,qFAAqF;IACrF,GAAG,EAAE,sCAAsC;CACnC,CAAC;AAEX,gEAAgE;AAChE,MAAM,CAAC,MAAM,YAAY,GAAG,yCAAyC,CAAC;AAEtE,iDAAiD;AACjD,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,YAAY;IACpB,IAAI,EAAE,UAAU;IAChB,QAAQ,EAAE,uBAAuB;CACzB,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared visible-text ↔ DOM-node mapping for ODF block-level text elements.
3
+ *
4
+ * Extracted from `document.ts` so both the document view and the comments module use one
5
+ * segmentation of a `text:p`/`text:h`'s visible text without an import cycle.
6
+ *
7
+ * All element matching is by `namespaceURI` + `localName` (ODF prefixes are not guaranteed).
8
+ */
9
+ export declare const TEXT_NODE = 3;
10
+ export declare const ELEMENT_NODE = 1;
11
+ /** True for the block-level text elements that carry a paragraph's visible content. */
12
+ export declare function isTextBlock(el: {
13
+ namespaceURI?: string | null;
14
+ localName?: string | null;
15
+ }): boolean;
16
+ /**
17
+ * True for an `office:annotation` / `office:annotation-end` element. Annotation subtrees carry
18
+ * their own `text:p` comment body, which must NOT be walked as part of the host paragraph's
19
+ * visible text nor enumerated as a paragraph block — callers skip these subtrees.
20
+ */
21
+ export declare function isAnnotationSubtree(el: {
22
+ namespaceURI?: string | null;
23
+ localName?: string | null;
24
+ }): boolean;
25
+ /**
26
+ * True for a `text:tracked-changes` container. It holds change DEFINITIONS — including deleted
27
+ * paragraphs stored out-of-line inside `text:deletion` — which are NOT body content: they must
28
+ * not be walked as a host paragraph's visible text nor enumerated as paragraph blocks. Callers
29
+ * skip this subtree (the deletion-storage analogue of `isAnnotationSubtree`).
30
+ */
31
+ export declare function isTrackedChangesSubtree(el: {
32
+ namespaceURI?: string | null;
33
+ localName?: string | null;
34
+ }): boolean;
35
+ /** A contiguous slice of a paragraph's visible text and where it came from. */
36
+ export type Segment = {
37
+ kind: 'text';
38
+ node: {
39
+ data: string;
40
+ };
41
+ visStart: number;
42
+ length: number;
43
+ } | {
44
+ kind: 'virtual';
45
+ node: Element;
46
+ virtual: 'space' | 'tab' | 'line-break';
47
+ visStart: number;
48
+ length: number;
49
+ };
50
+ /**
51
+ * Build the ordered segment list and concatenated visible string for a block.
52
+ * `text:s` (count via `text:c`) expands to spaces, `text:tab` to a tab, and
53
+ * `text:line-break` to a newline — each a "virtual" segment whose visible text has no host
54
+ * `#text` node (so a match landing on one cannot be edited in place via `replaceTextById`);
55
+ * the generating element itself is carried as `node` so offset-mapping consumers (the compare
56
+ * emitter) can split or copy it. `office:annotation` / `office:annotation-end` subtrees are
57
+ * skipped entirely.
58
+ */
59
+ export declare function buildSegments(block: Element): {
60
+ segments: Segment[];
61
+ visible: string;
62
+ };
63
+ //# sourceMappingURL=text_segments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text_segments.d.ts","sourceRoot":"","sources":["../../../src/shared/odf/text_segments.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,eAAO,MAAM,SAAS,IAAI,CAAC;AAC3B,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,EAAE,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAEpG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAE5G;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAEhH;AAED,+EAA+E;AAC/E,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1E;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElH;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA6CtF"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Shared visible-text ↔ DOM-node mapping for ODF block-level text elements.
3
+ *
4
+ * Extracted from `document.ts` so both the document view and the comments module use one
5
+ * segmentation of a `text:p`/`text:h`'s visible text without an import cycle.
6
+ *
7
+ * All element matching is by `namespaceURI` + `localName` (ODF prefixes are not guaranteed).
8
+ */
9
+ import { ODF_NS } from './namespaces.js';
10
+ export const TEXT_NODE = 3;
11
+ export const ELEMENT_NODE = 1;
12
+ /** True for the block-level text elements that carry a paragraph's visible content. */
13
+ export function isTextBlock(el) {
14
+ return el.namespaceURI === ODF_NS.TEXT && (el.localName === 'p' || el.localName === 'h');
15
+ }
16
+ /**
17
+ * True for an `office:annotation` / `office:annotation-end` element. Annotation subtrees carry
18
+ * their own `text:p` comment body, which must NOT be walked as part of the host paragraph's
19
+ * visible text nor enumerated as a paragraph block — callers skip these subtrees.
20
+ */
21
+ export function isAnnotationSubtree(el) {
22
+ return el.namespaceURI === ODF_NS.OFFICE && (el.localName === 'annotation' || el.localName === 'annotation-end');
23
+ }
24
+ /**
25
+ * True for a `text:tracked-changes` container. It holds change DEFINITIONS — including deleted
26
+ * paragraphs stored out-of-line inside `text:deletion` — which are NOT body content: they must
27
+ * not be walked as a host paragraph's visible text nor enumerated as paragraph blocks. Callers
28
+ * skip this subtree (the deletion-storage analogue of `isAnnotationSubtree`).
29
+ */
30
+ export function isTrackedChangesSubtree(el) {
31
+ return el.namespaceURI === ODF_NS.TEXT && el.localName === 'tracked-changes';
32
+ }
33
+ /**
34
+ * Build the ordered segment list and concatenated visible string for a block.
35
+ * `text:s` (count via `text:c`) expands to spaces, `text:tab` to a tab, and
36
+ * `text:line-break` to a newline — each a "virtual" segment whose visible text has no host
37
+ * `#text` node (so a match landing on one cannot be edited in place via `replaceTextById`);
38
+ * the generating element itself is carried as `node` so offset-mapping consumers (the compare
39
+ * emitter) can split or copy it. `office:annotation` / `office:annotation-end` subtrees are
40
+ * skipped entirely.
41
+ */
42
+ export function buildSegments(block) {
43
+ const segments = [];
44
+ let visible = '';
45
+ const walk = (node) => {
46
+ for (let child = node.firstChild; child; child = child.nextSibling) {
47
+ if (child.nodeType === TEXT_NODE) {
48
+ const data = child.data ?? '';
49
+ if (data.length === 0)
50
+ continue;
51
+ segments.push({ kind: 'text', node: child, visStart: visible.length, length: data.length });
52
+ visible += data;
53
+ continue;
54
+ }
55
+ if (child.nodeType !== ELEMENT_NODE)
56
+ continue;
57
+ const el = child;
58
+ // Skip annotation subtrees: their body text is a comment, not the host paragraph's content.
59
+ if (isAnnotationSubtree(el))
60
+ continue;
61
+ // Skip tracked-changes storage: deleted paragraphs live out-of-line here, not in the body.
62
+ if (isTrackedChangesSubtree(el))
63
+ continue;
64
+ if (el.namespaceURI === ODF_NS.TEXT && el.localName === 's') {
65
+ const countRaw = el.getAttributeNS(ODF_NS.TEXT, 'c') ?? el.getAttribute('text:c');
66
+ const count = Math.max(1, Number.parseInt(countRaw ?? '1', 10) || 1);
67
+ const spaces = ' '.repeat(count);
68
+ segments.push({ kind: 'virtual', node: el, virtual: 'space', visStart: visible.length, length: spaces.length });
69
+ visible += spaces;
70
+ continue;
71
+ }
72
+ if (el.namespaceURI === ODF_NS.TEXT && el.localName === 'tab') {
73
+ segments.push({ kind: 'virtual', node: el, virtual: 'tab', visStart: visible.length, length: 1 });
74
+ visible += '\t';
75
+ continue;
76
+ }
77
+ if (el.namespaceURI === ODF_NS.TEXT && el.localName === 'line-break') {
78
+ segments.push({ kind: 'virtual', node: el, virtual: 'line-break', visStart: visible.length, length: 1 });
79
+ visible += '\n';
80
+ continue;
81
+ }
82
+ // Other elements (text:span, hyperlink, etc.): recurse so their inner
83
+ // #text nodes are recorded as separate segments.
84
+ walk(el);
85
+ }
86
+ };
87
+ walk(block);
88
+ return { segments, visible };
89
+ }
90
+ //# sourceMappingURL=text_segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text_segments.js","sourceRoot":"","sources":["../../../src/shared/odf/text_segments.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAC3B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B,uFAAuF;AACvF,MAAM,UAAU,WAAW,CAAC,EAA+D;IACzF,OAAO,EAAE,CAAC,YAAY,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,CAAC,SAAS,KAAK,GAAG,CAAC,CAAC;AAC3F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAA+D;IACjG,OAAO,EAAE,CAAC,YAAY,KAAK,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS,KAAK,YAAY,IAAI,EAAE,CAAC,SAAS,KAAK,gBAAgB,CAAC,CAAC;AACnH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,EAA+D;IACrG,OAAO,EAAE,CAAC,YAAY,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,SAAS,KAAK,iBAAiB,CAAC;AAC/E,CAAC;AAOD;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,MAAM,IAAI,GAAG,CAAC,IAAU,EAAQ,EAAE;QAChC,KAAK,IAAI,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;YACnE,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAI,KAAqC,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC/D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS;gBAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAoC,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC3H,OAAO,IAAI,IAAI,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY;gBAAE,SAAS;YAC9C,MAAM,EAAE,GAAG,KAAgB,CAAC;YAC5B,4FAA4F;YAC5F,IAAI,mBAAmB,CAAC,EAAE,CAAC;gBAAE,SAAS;YACtC,2FAA2F;YAC3F,IAAI,uBAAuB,CAAC,EAAE,CAAC;gBAAE,SAAS;YAC1C,IAAI,EAAE,CAAC,YAAY,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,SAAS,KAAK,GAAG,EAAE,CAAC;gBAC5D,MAAM,QAAQ,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;gBAClF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,IAAI,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBAChH,OAAO,IAAI,MAAM,CAAC;gBAClB,SAAS;YACX,CAAC;YACD,IAAI,EAAE,CAAC,YAAY,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;gBAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;gBAClG,OAAO,IAAI,IAAI,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,IAAI,EAAE,CAAC,YAAY,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,SAAS,KAAK,YAAY,EAAE,CAAC;gBACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;gBACzG,OAAO,IAAI,IAAI,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,sEAAsE;YACtE,iDAAiD;YACjD,IAAI,CAAC,EAAE,CAAC,CAAC;QACX,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC,KAAK,CAAC,CAAC;IACZ,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@usejunior/odf-core",
3
+ "version": "0.9.1",
4
+ "description": "OpenDocument Format (.odt) core library: archive packaging, document view, surgical text replacement, and tracked-changes comparison",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "clean:dist": "node -e \"const { rmSync } = require('node:fs'); rmSync('dist', { recursive: true, force: true });\"",
10
+ "build": "npm run clean:dist && tsc -p tsconfig.build.json",
11
+ "dev": "tsc -p tsconfig.build.json --watch",
12
+ "test": "node ../../node_modules/vitest/vitest.mjs",
13
+ "test:run": "node ../../node_modules/vitest/vitest.mjs run",
14
+ "lint": "tsc --noEmit"
15
+ },
16
+ "dependencies": {
17
+ "@usejunior/docx-core": "^0.9.1",
18
+ "@xmldom/xmldom": "^0.9.10",
19
+ "jszip": "^3.10.1"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.10.0",
23
+ "typescript": "^5.7.2",
24
+ "vitest": "^4.1.8"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/UseJunior/safe-docx"
32
+ },
33
+ "homepage": "https://github.com/UseJunior/safe-docx/tree/main/packages/odf-core",
34
+ "bugs": {
35
+ "url": "https://github.com/UseJunior/safe-docx/issues"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "keywords": [
44
+ "odf",
45
+ "opendocument",
46
+ "odt",
47
+ "libreoffice",
48
+ "track-changes",
49
+ "comparison",
50
+ "document-editing",
51
+ "mcp"
52
+ ],
53
+ "license": "MIT"
54
+ }