ooxml.js 4.1.1 → 4.3.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 (34) hide show
  1. package/README.md +6 -4
  2. package/dist/{codec-DxnbKt4O.d.ts → codec-BrTMlL0a.d.ts} +2 -2
  3. package/dist/{codec-DO0MNF45.d.cts → codec-DNjHjEDm.d.cts} +2 -2
  4. package/dist/codec.d.cts +1 -1
  5. package/dist/codec.d.ts +1 -1
  6. package/dist/index.cjs +2 -2
  7. package/dist/index.d.cts +3 -3
  8. package/dist/index.d.ts +3 -3
  9. package/dist/index.js +2 -2
  10. package/dist/{read-Legu2Pt-.js → read-BQoFiqsb.js} +31 -6
  11. package/dist/{read-DY3Sm5T3.d.cts → read-BoV9SZy4.d.cts} +34 -0
  12. package/dist/{read-ClIHv1ef.d.cts → read-CDX1WbiF.d.cts} +17 -0
  13. package/dist/{read-gB0H2bff.d.ts → read-CxMli3Om.d.ts} +17 -0
  14. package/dist/{read-DPAy4uu8.cjs → read-Di5UfiSh.cjs} +29 -4
  15. package/dist/{read-DcJZITV9.d.ts → read-ZWJPDvG-.d.ts} +34 -0
  16. package/dist/typed/document-package.cjs +2 -2
  17. package/dist/typed/document-package.js +2 -2
  18. package/dist/typed/docx/constructs.cjs +59 -5
  19. package/dist/typed/docx/constructs.d.cts +10 -2
  20. package/dist/typed/docx/constructs.d.ts +10 -2
  21. package/dist/typed/docx/constructs.js +59 -6
  22. package/dist/typed/docx/read.cjs +1 -1
  23. package/dist/typed/docx/read.d.cts +1 -1
  24. package/dist/typed/docx/read.d.ts +1 -1
  25. package/dist/typed/docx/read.js +1 -1
  26. package/dist/typed/docx/write.cjs +139 -6
  27. package/dist/typed/docx/write.js +140 -7
  28. package/dist/typed/embedded.cjs +1 -1
  29. package/dist/typed/embedded.js +1 -1
  30. package/dist/typed/pptx/read.cjs +1 -1
  31. package/dist/typed/pptx/read.d.cts +1 -1
  32. package/dist/typed/pptx/read.d.ts +1 -1
  33. package/dist/typed/pptx/read.js +1 -1
  34. package/package.json +3 -3
@@ -1,8 +1,12 @@
1
+ import { bytesToBase64 } from "../../util/base64.js";
2
+ import { parseXml } from "../../xml/parse.js";
3
+ import { encodePackage } from "../../codec.js";
1
4
  import { el, txt } from "../../xml/fragment.js";
2
5
  import { encodeXmlText } from "../../xml/entities.js";
3
6
  import { ptToEighthPoints, ptToEmu, ptToHalfPoints, ptToTwips } from "../shared/units.js";
4
7
  import { TABLE_OF_CONTENTS_GALLERY, isDeletedChange } from "./constructs.js";
5
- import { colorToRgbHex, findConstructMarkerImbalance } from "document-schema.js";
8
+ import { buildXlsxPackageFromContent } from "../xlsx/build.js";
9
+ import { colorToRgbHex, findConstructMarkerImbalance, findRunConstructFault } from "document-schema.js";
6
10
  //#region src/typed/docx/write.ts
7
11
  const WML_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
8
12
  const REL_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
@@ -19,14 +23,18 @@ const DRAWING_PIC_NS = "http://schemas.openxmlformats.org/drawingml/2006/picture
19
23
  const MARKUP_COMPAT_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
20
24
  const W14_NS = "http://schemas.microsoft.com/office/word/2010/wordml";
21
25
  const W15_NS = "http://schemas.microsoft.com/office/word/2012/wordml";
26
+ const VML_OFFICE_NS = "urn:schemas-microsoft-com:office:office";
22
27
  const CT_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
23
28
  const CT_CORE_PROPS = "application/vnd.openxmlformats-package.core-properties+xml";
24
29
  const CT_EXTENDED_PROPS = "application/vnd.openxmlformats-officedocument.extended-properties+xml";
30
+ const CT_EMBEDDED_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
31
+ const CT_EMBEDDED_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
25
32
  const REL_OFFICE_DOCUMENT = `${REL_NS}/officeDocument`;
26
33
  const REL_CORE_PROPS = `${PKG_RELS_NS}/metadata/core-properties`;
27
34
  const REL_EXTENDED_PROPS = `${REL_NS}/extended-properties`;
28
35
  const REL_HYPERLINK = `${REL_NS}/hyperlink`;
29
36
  const REL_IMAGE = `${REL_NS}/image`;
37
+ const REL_OLE_OBJECT = `${REL_NS}/oleObject`;
30
38
  const DOCUMENT_PART_PATH = "word/document.xml";
31
39
  function newWriteState() {
32
40
  return {
@@ -34,6 +42,8 @@ function newWriteState() {
34
42
  hyperlinkIds: /* @__PURE__ */ new Map(),
35
43
  mediaIds: /* @__PURE__ */ new Map(),
36
44
  mediaParts: /* @__PURE__ */ new Map(),
45
+ embeddingIds: /* @__PURE__ */ new Map(),
46
+ embeddingParts: /* @__PURE__ */ new Map(),
37
47
  nextDrawingId: 1,
38
48
  nextMarkerId: 1
39
49
  };
@@ -168,10 +178,49 @@ function buildParagraph(paragraph, state, pageBreakBefore, deleted, provenance)
168
178
  const changeTag = provenance === void 0 ? void 0 : TRACKED_CHANGE_TAG_BY_CHANGE[provenance.change];
169
179
  const pPr = properties === void 0 && changeTag !== void 0 ? el("w:pPr", {}, []) : properties;
170
180
  if (pPr !== void 0 && changeTag !== void 0 && provenance !== void 0) pPr.children.push(el("w:rPr", {}, [el(changeTag, trackChangeAttrs(state, provenance))]));
171
- const runs = paragraph.runs.map((run) => buildRun(run, state, deleted));
181
+ const runs = interleaveRunConstructExtents(paragraph.runs.map((run) => buildRun(run, state, deleted)), paragraph, state);
172
182
  const content = changeTag === void 0 || provenance === void 0 ? runs : [el(changeTag, trackChangeAttrs(state, provenance), runs)];
173
183
  return el("w:p", {}, [...pPr === void 0 ? [] : [pPr], ...content]);
174
184
  }
185
+ function interleaveRunConstructExtents(runElements, paragraph, state) {
186
+ if (paragraph.constructs === void 0) return [...runElements];
187
+ const fault = findRunConstructFault(paragraph);
188
+ if (fault !== void 0) throw new Error(`buildDocxPackageFromContent: run-level construct extent at index ${String(fault.index)} of a paragraph does not name real runs (${fault.kind})`);
189
+ const bookmarks = paragraph.constructs.filter((extent) => extent.descriptor.kind === "anchor" && extent.descriptor.anchorType === "bookmark");
190
+ if (bookmarks.length === 0) return [...runElements];
191
+ const closingAt = /* @__PURE__ */ new Map();
192
+ const openingAt = /* @__PURE__ */ new Map();
193
+ const pointAt = /* @__PURE__ */ new Map();
194
+ const push = (map, position, element) => {
195
+ const existing = map.get(position);
196
+ if (existing === void 0) map.set(position, [element]);
197
+ else existing.push(element);
198
+ };
199
+ for (const bookmark of bookmarks) {
200
+ const id = String(state.nextMarkerId++);
201
+ const open = el("w:bookmarkStart", {
202
+ "w:id": id,
203
+ "w:name": encodeXmlText(bookmark.descriptor.name)
204
+ });
205
+ const close = el("w:bookmarkEnd", { "w:id": id });
206
+ if (bookmark.startRun === bookmark.endRun) {
207
+ push(pointAt, bookmark.startRun, open);
208
+ push(pointAt, bookmark.startRun, close);
209
+ } else {
210
+ push(openingAt, bookmark.startRun, open);
211
+ push(closingAt, bookmark.endRun, close);
212
+ }
213
+ }
214
+ const out = [];
215
+ for (let position = 0; position <= runElements.length; position++) {
216
+ for (const close of closingAt.get(position) ?? []) out.push(close);
217
+ for (const open of openingAt.get(position) ?? []) out.push(open);
218
+ for (const half of pointAt.get(position) ?? []) out.push(half);
219
+ const run = runElements[position];
220
+ if (run !== void 0) out.push(run);
221
+ }
222
+ return out;
223
+ }
175
224
  function trailingEmptyRunElements(paragraph, element) {
176
225
  const runElements = [];
177
226
  for (const child of element.children) if (child.type === "element" && (child.tag === "w:r" || child.tag === "w:hyperlink")) runElements.push(child);
@@ -296,6 +345,43 @@ function buildDrawing(image, state) {
296
345
  el("a:graphic", { "xmlns:a": DRAWINGML_NS }, [el("a:graphicData", { uri: DRAWING_PIC_NS }, [picture])])
297
346
  ])]);
298
347
  }
348
+ function embeddedPayloadOf(document) {
349
+ switch (document.kind) {
350
+ case "wordprocessing": return {
351
+ extension: "docx",
352
+ progId: "Word.Document.12",
353
+ base64: bytesToBase64(encodePackage(buildDocxPackageFromContent(document)))
354
+ };
355
+ case "spreadsheet": return {
356
+ extension: "xlsx",
357
+ progId: "Excel.Sheet.12",
358
+ base64: bytesToBase64(encodePackage(buildXlsxPackageFromContent(document)))
359
+ };
360
+ default: throw new Error(`buildDocxPackageFromContent: an embedded object carrying a ${document.kind} document has no OOXML OLE payload this writer can produce (embedded wordprocessing and spreadsheet documents serialise through their own builders; presentation is read-only in this package, and drawing/formula are ODF/MathML spellings)`);
361
+ }
362
+ }
363
+ function embeddedObjectRelationshipId(state, payload) {
364
+ const existing = state.embeddingIds.get(payload.base64);
365
+ if (existing !== void 0) return existing;
366
+ const name = `oleObject${state.embeddingParts.size + 1}.${payload.extension}`;
367
+ state.embeddingParts.set(name, payload);
368
+ const id = addRelationship(state, REL_OLE_OBJECT, `embeddings/${name}`, false);
369
+ state.embeddingIds.set(payload.base64, id);
370
+ return id;
371
+ }
372
+ function buildObjectElement(block, state) {
373
+ const payload = embeddedPayloadOf(block.document);
374
+ const relId = embeddedObjectRelationshipId(state, payload);
375
+ return el("w:object", {
376
+ "w:dxaOrig": String(ptToTwips(block.frame.widthPt)),
377
+ "w:dyaOrig": String(ptToTwips(block.frame.heightPt))
378
+ }, [el("o:OLEObject", {
379
+ Type: "Embed",
380
+ ProgID: payload.progId,
381
+ DrawAspect: "Content",
382
+ "r:id": relId
383
+ })]);
384
+ }
299
385
  const TRACKED_CHANGE_TAG_BY_CHANGE = {
300
386
  insertion: "w:ins",
301
387
  deletion: "w:del",
@@ -356,10 +442,29 @@ function buildSdtProperties(descriptor) {
356
442
  children.push(el("w:date", descriptor.value === void 0 ? {} : { "w:fullDate": encodeXmlText(descriptor.value) }));
357
443
  return el("w:sdtPr", {}, children);
358
444
  }
445
+ const restored = restoreGalleryElement(descriptor);
446
+ if (restored !== void 0) {
447
+ children.push(restored);
448
+ return el("w:sdtPr", {}, children);
449
+ }
359
450
  const typeTag = SDT_TYPE_ELEMENT[descriptor.controlType];
360
451
  children.push(el(typeTag ?? "w:richText"));
361
452
  return el("w:sdtPr", {}, children);
362
453
  }
454
+ function restoreGalleryElement(descriptor) {
455
+ if (descriptor.controlType !== "richText") return;
456
+ const source = descriptor.source;
457
+ if (source?.format !== "docx") return;
458
+ let nodes;
459
+ try {
460
+ nodes = parseXml(source.xml);
461
+ } catch (error) {
462
+ throw new Error(`buildDocxPackageFromContent: a content control carries docx residue that does not parse as XML: ${source.xml}`, { cause: error });
463
+ }
464
+ const first = nodes[0];
465
+ if (nodes.length !== 1 || first?.type !== "element" || first.tag !== "w:docPartObj" && first.tag !== "w:docPartList") return;
466
+ return first;
467
+ }
363
468
  function parseFlow(blocks) {
364
469
  const imbalance = findConstructMarkerImbalance(blocks);
365
470
  if (imbalance !== void 0) throw new Error(`buildDocxPackageFromContent: construct markers do not balance (${imbalance.kind} at block ${String(imbalance.index)})`);
@@ -503,8 +608,25 @@ function buildFlowItems(items, state, deleted, provenance) {
503
608
  availableImageRuns = [];
504
609
  continue;
505
610
  }
506
- lastParagraph = void 0;
507
- availableImageRuns = [];
611
+ if (block.kind === "embeddedObject") {
612
+ if (pendingPageBreak) {
613
+ const breakParagraph = el("w:p", {}, [el("w:pPr", {}, [el("w:pageBreakBefore")])]);
614
+ nodes.push(breakParagraph);
615
+ lastParagraph = breakParagraph;
616
+ availableImageRuns = [];
617
+ pendingPageBreak = false;
618
+ }
619
+ const object = buildObjectElement(block, state);
620
+ const reusable = availableImageRuns.shift();
621
+ if (reusable !== void 0) reusable.children.push(object);
622
+ else if (lastParagraph !== void 0) lastParagraph.children.push(el("w:r", {}, [object]));
623
+ else {
624
+ const paragraph = el("w:p", {}, [el("w:r", {}, [object])]);
625
+ lastParagraph = paragraph;
626
+ nodes.push(paragraph);
627
+ }
628
+ continue;
629
+ }
508
630
  }
509
631
  if (pendingPageBreak) nodes.push(el("w:p", {}, [el("w:pPr", {}, [el("w:pageBreakBefore")])]));
510
632
  return nodes;
@@ -554,12 +676,14 @@ function buildDocumentPart(sections, state) {
554
676
  "xmlns:wp": DRAWING_WP_NS,
555
677
  "xmlns:pic": DRAWING_PIC_NS,
556
678
  "xmlns:mc": MARKUP_COMPAT_NS,
679
+ "xmlns:o": VML_OFFICE_NS,
557
680
  "xmlns:w14": W14_NS,
558
681
  "xmlns:w15": W15_NS,
559
682
  "mc:Ignorable": "w14 w15"
560
683
  }, [el("w:body", {}, bodyChildren)]));
561
684
  }
562
- function buildContentTypesPart(mediaFormats) {
685
+ function buildContentTypesPart(state) {
686
+ const mediaFormats = new Set([...state.mediaParts.values()].map((media) => media.format));
563
687
  const defaults = [el("Default", {
564
688
  Extension: "rels",
565
689
  ContentType: "application/vnd.openxmlformats-package.relationships+xml"
@@ -575,6 +699,10 @@ function buildContentTypesPart(mediaFormats) {
575
699
  Extension: "jpeg",
576
700
  ContentType: "image/jpeg"
577
701
  }));
702
+ const embeddingOverrides = [...state.embeddingParts].map(([name, payload]) => el("Override", {
703
+ PartName: `/word/embeddings/${name}`,
704
+ ContentType: payload.extension === "docx" ? CT_EMBEDDED_DOCX : CT_EMBEDDED_XLSX
705
+ }));
578
706
  return xmlPart(el("Types", { xmlns: CONTENT_TYPES_NS }, [
579
707
  ...defaults,
580
708
  el("Override", {
@@ -588,7 +716,8 @@ function buildContentTypesPart(mediaFormats) {
588
716
  el("Override", {
589
717
  PartName: "/docProps/app.xml",
590
718
  ContentType: CT_EXTENDED_PROPS
591
- })
719
+ }),
720
+ ...embeddingOverrides
592
721
  ]));
593
722
  }
594
723
  function buildPackageRelsPart() {
@@ -660,7 +789,7 @@ function buildDocxPackageFromContent(content) {
660
789
  }] : content.sections, state);
661
790
  const metadata = content.metadata ?? {};
662
791
  const parts = {
663
- "[Content_Types].xml": buildContentTypesPart(new Set([...state.mediaParts.values()].map((media) => media.format))),
792
+ "[Content_Types].xml": buildContentTypesPart(state),
664
793
  "_rels/.rels": buildPackageRelsPart(),
665
794
  [DOCUMENT_PART_PATH]: documentPart,
666
795
  "word/_rels/document.xml.rels": buildDocumentRelsPart(state),
@@ -671,6 +800,10 @@ function buildDocxPackageFromContent(content) {
671
800
  kind: "binary",
672
801
  base64: media.base64
673
802
  };
803
+ for (const [name, payload] of state.embeddingParts) parts[`word/embeddings/${name}`] = {
804
+ kind: "binary",
805
+ base64: payload.base64
806
+ };
674
807
  return { parts };
675
808
  }
676
809
  //#endregion
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_read = require("../read-DPAy4uu8.cjs");
2
+ const require_read = require("../read-Di5UfiSh.cjs");
3
3
  exports.readEmbeddedOoxmlPayload = require_read.readEmbeddedOoxmlPayload;
4
4
  exports.readEmbeddedPayloadPart = require_read.readEmbeddedPayloadPart;
@@ -1,2 +1,2 @@
1
- import { a as readEmbeddedOoxmlPayload, o as readEmbeddedPayloadPart } from "../read-Legu2Pt-.js";
1
+ import { a as readEmbeddedOoxmlPayload, o as readEmbeddedPayloadPart } from "../read-BQoFiqsb.js";
2
2
  export { readEmbeddedOoxmlPayload, readEmbeddedPayloadPart };
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_read = require("../../read-DPAy4uu8.cjs");
2
+ const require_read = require("../../read-Di5UfiSh.cjs");
3
3
  exports.PptxDocumentSchema = require_read.PptxDocumentSchema;
4
4
  exports.readPptxContent = require_read.readPptxContent;
@@ -1,2 +1,2 @@
1
- import { n as PptxDocumentSchema, r as readPptxContent, t as PptxDocument } from "../../read-DY3Sm5T3.cjs";
1
+ import { n as PptxDocumentSchema, r as readPptxContent, t as PptxDocument } from "../../read-BoV9SZy4.cjs";
2
2
  export { PptxDocument, PptxDocumentSchema, readPptxContent };
@@ -1,2 +1,2 @@
1
- import { n as PptxDocumentSchema, r as readPptxContent, t as PptxDocument } from "../../read-DcJZITV9.js";
1
+ import { n as PptxDocumentSchema, r as readPptxContent, t as PptxDocument } from "../../read-ZWJPDvG-.js";
2
2
  export { PptxDocument, PptxDocumentSchema, readPptxContent };
@@ -1,2 +1,2 @@
1
- import { c as readPptxContent, s as PptxDocumentSchema } from "../../read-Legu2Pt-.js";
1
+ import { c as readPptxContent, s as PptxDocumentSchema } from "../../read-BQoFiqsb.js";
2
2
  export { PptxDocumentSchema, readPptxContent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ooxml.js",
3
- "version": "4.1.1",
3
+ "version": "4.3.0",
4
4
  "description": "Type-safe, lossless round-trip conversion between OOXML packages (docx, pptx, xlsx) and JSON, built on Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -80,8 +80,8 @@
80
80
  "license": "MIT",
81
81
  "packageManager": "pnpm@11.6.0",
82
82
  "dependencies": {
83
- "archive-codec": "^1.0.2",
84
- "document-schema.js": "^4.3.7",
83
+ "archive-codec": "^1.1.0",
84
+ "document-schema.js": "^4.5.0",
85
85
  "fast-xml-parser": "^5.10.1",
86
86
  "fflate": "^0.8.3",
87
87
  "zod": "^4.4.3"