@stll/folio-core 0.64.1 → 0.66.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 (62) hide show
  1. package/dist/ai-edits/apply.js +31 -9
  2. package/dist/ai-edits/headless.js +3 -3
  3. package/dist/compare/reproducible-package.d.ts +5 -0
  4. package/dist/compare/reproducible-package.js +10 -23
  5. package/dist/docx/archiveInflation.d.ts +8 -103
  6. package/dist/docx/archiveInflation.js +8 -145
  7. package/dist/docx/attributeRemainder.js +1 -1
  8. package/dist/docx/canonicalStoryRepack.js +2 -2
  9. package/dist/docx/documentSectionFacts.js +1 -1
  10. package/dist/docx/ensureParaIds.js +9 -24
  11. package/dist/docx/importedIdCollisions.js +1 -1
  12. package/dist/docx/mathToMathml.js +1 -1
  13. package/dist/docx/metadataPrivacy.js +38 -48
  14. package/dist/docx/numericIdNormalization.js +2 -2
  15. package/dist/docx/packageParts.d.ts +4 -4
  16. package/dist/docx/packageParts.js +17 -24
  17. package/dist/docx/parser.js +5 -6
  18. package/dist/docx/removeHeaderFooterParts.d.ts +2 -2
  19. package/dist/docx/removeHeaderFooterParts.js +19 -36
  20. package/dist/docx/revisionIdNormalization.js +2 -2
  21. package/dist/docx/rezip.d.ts +7 -7
  22. package/dist/docx/rezip.js +144 -532
  23. package/dist/docx/sdtProperties.js +1 -2
  24. package/dist/docx/selectedXmlIdentityScan.d.ts +1 -1
  25. package/dist/docx/selectedXmlIdentityScan.js +1 -1
  26. package/dist/docx/selectiveSave.js +37 -41
  27. package/dist/docx/selectiveSaveFlags.d.ts +1 -1
  28. package/dist/docx/selectiveSaveTripwire.js +5 -11
  29. package/dist/docx/selectiveXmlPatch.js +2 -2
  30. package/dist/docx/server/applyDocxXmlPatchProposal.js +20 -41
  31. package/dist/docx/server/boundedArchive.d.ts +2 -3
  32. package/dist/docx/server/boundedArchive.js +78 -99
  33. package/dist/docx/server/inspectDocxPackage.d.ts +1 -1
  34. package/dist/docx/server/inspectDocxPackage.js +1 -1
  35. package/dist/docx/server/validateDocxConformance.js +1 -1
  36. package/dist/docx/shapeAlternateContent.js +1 -1
  37. package/dist/docx/storyBlockReplay.js +1 -1
  38. package/dist/docx/unzip.d.ts +2 -3
  39. package/dist/docx/unzip.js +127 -155
  40. package/dist/docx/verbatimCapture.js +1 -1
  41. package/dist/docx/xmlParser.d.ts +5 -36
  42. package/dist/docx/xmlParser.js +14 -78
  43. package/dist/docx/xmlSafety.js +5 -5
  44. package/dist/layout-painter/renderPage.js +4 -2
  45. package/dist/markdown/countParagraphList.d.ts +18 -0
  46. package/dist/markdown/countParagraphList.js +90 -0
  47. package/dist/markdown/renderParagraph.js +2 -62
  48. package/dist/markdown/renderRuns.js +4 -1
  49. package/dist/prosemirror/conversion/fromProseDoc.js +5 -3
  50. package/dist/prosemirror/conversion/toProseDoc.js +4 -1
  51. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -1
  52. package/dist/prosemirror/replacedAnnotations.d.ts +6 -0
  53. package/dist/prosemirror/replacedAnnotations.js +30 -8
  54. package/dist/prosemirror/schema/nodes.d.ts +7 -0
  55. package/dist/prosemirror/textBoxAnchorAttrs.js +9 -1
  56. package/package.json +3 -5
  57. package/dist/docx/streamingXmlParser.d.ts +0 -60
  58. package/dist/docx/streamingXmlParser.js +0 -406
  59. package/dist/docx/xmlNamespaceContext.d.ts +0 -6
  60. package/dist/docx/xmlNamespaceContext.js +0 -53
  61. package/dist/docx/xmlResourceLimits.d.ts +0 -107
  62. package/dist/docx/xmlResourceLimits.js +0 -194
@@ -1,9 +1,8 @@
1
- import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
2
- import { countCentralDirectoryRecords, createInflationBudget, inflateEntryWithinLimits } from "./archiveInflation.js";
1
+ import { decodeText } from "../utils/textEncoding.js";
2
+ import "./archiveInflation.js";
3
3
  import { elementToXml, getLocalName, parseXmlDocument } from "./xmlParser.js";
4
- import { TaggedError } from "better-result";
5
- import JSZip from "jszip";
6
- import { writeZipPart } from "@stll/docx-core/zip";
4
+ import { Result, TaggedError } from "better-result";
5
+ import { ZipArchive, ZipError } from "@stll/docx-core/zip";
7
6
  // src/docx/metadataPrivacy.ts
8
7
  const FOLIO_DOCUMENT_METADATA_PROPERTIES = Object.freeze([
9
8
  "title",
@@ -57,40 +56,34 @@ const CORE_PROPERTIES_PATH_LOWER = "docprops/core.xml";
57
56
  * found — otherwise the privacy rewrite silently no-ops and the unscrubbed
58
57
  * buffer round-trips untouched.
59
58
  */
60
- const findZipEntryCaseInsensitive = (zip, lowerPath) => {
61
- const direct = zip.file(lowerPath);
62
- if (direct) return direct;
63
- for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.toLowerCase() === lowerPath) return file;
64
- return null;
65
- };
59
+ const findZipEntryCaseInsensitive = (zip, lowerPath) => zip.entry(lowerPath) ?? zip.entries().find(({ path }) => path.toLowerCase() === lowerPath) ?? null;
66
60
  const loadPrivacyArchive = async (buffer) => {
67
61
  if (buffer.byteLength > MAX_INPUT_BYTES) throw new FolioDocumentPrivacyArchiveError({
68
62
  message: "Document privacy input exceeded the compressed-size limit",
69
63
  reason: "input-too-large"
70
64
  });
71
- if (countCentralDirectoryRecords(new Uint8Array(buffer), MAX_ARCHIVE_ENTRIES) > MAX_ARCHIVE_ENTRIES) throw new FolioDocumentPrivacyArchiveError({
72
- message: "Document privacy input exceeded the package-entry limit",
73
- reason: "too-many-entries"
65
+ const read = await Result.tryPromise({
66
+ try: async () => await ZipArchive.read(buffer, { limits: {
67
+ maxEntries: MAX_ARCHIVE_ENTRIES,
68
+ maxCompressionRatio: 200
69
+ } }),
70
+ catch: (cause) => cause
74
71
  });
75
- let zip;
76
- try {
77
- zip = await JSZip.loadAsync(buffer);
78
- } catch (cause) {
72
+ if (Result.isError(read)) {
73
+ const cause = read.error;
74
+ if (cause instanceof ZipError && cause.reason === "too-many-entries") throw new FolioDocumentPrivacyArchiveError({
75
+ message: "Document privacy input exceeded the package-entry limit",
76
+ reason: "too-many-entries"
77
+ });
79
78
  throw new FolioDocumentPrivacyArchiveError({
80
79
  message: "Document privacy input is not a readable package",
81
80
  reason: "load-failed",
82
81
  cause
83
82
  });
84
83
  }
85
- if (Object.values(zip.files).length > MAX_ARCHIVE_ENTRIES) throw new FolioDocumentPrivacyArchiveError({
86
- message: "Document privacy input exceeded the package-entry limit",
87
- reason: "too-many-entries"
88
- });
84
+ const zip = read.value;
89
85
  const coreProperties = findZipEntryCaseInsensitive(zip, CORE_PROPERTIES_PATH_LOWER);
90
- if (!coreProperties) return zip;
91
- const data = "_data" in coreProperties ? coreProperties._data : void 0;
92
- const declaredBytes = typeof data === "object" && data !== null && "uncompressedSize" in data ? data.uncompressedSize : void 0;
93
- if (typeof declaredBytes !== "number" || declaredBytes > MAX_CORE_PROPERTIES_BYTES) throw new FolioDocumentPrivacyArchiveError({
86
+ if (coreProperties && coreProperties.size > MAX_CORE_PROPERTIES_BYTES) throw new FolioDocumentPrivacyArchiveError({
94
87
  message: "Document privacy core properties exceeded the part-size limit",
95
88
  reason: "core-properties-too-large"
96
89
  });
@@ -119,22 +112,26 @@ const rewriteCorePropertiesPrivacy = (xml, transforms) => {
119
112
  };
120
113
  };
121
114
  /**
122
- * Read the core-properties part within its size limit. The declared size was
123
- * checked when the archive loaded, but only the inflation itself can show that
124
- * the part is no larger than it claims.
115
+ * Read the core-properties part. Its declared size was checked when the
116
+ * archive loaded, and inflation stops at that size.
125
117
  */
126
118
  const readCoreProperties = async (entry) => {
127
- const result = await inflateEntryWithinLimits({
128
- entry,
129
- maxEntryBytes: MAX_CORE_PROPERTIES_BYTES,
130
- maxCompressionRatio: 200,
131
- budget: createInflationBudget(MAX_CORE_PROPERTIES_BYTES)
132
- });
133
- if (!result.ok) throw new FolioDocumentPrivacyArchiveError({
134
- message: "Document privacy core properties exceeded the part-size limit",
135
- reason: "core-properties-too-large"
119
+ const read = await Result.tryPromise({
120
+ try: async () => await entry.bytes(),
121
+ catch: (cause) => cause
136
122
  });
137
- return decodeText(result.bytes, { ignoreBOM: true });
123
+ if (Result.isError(read)) {
124
+ const cause = read.error;
125
+ throw new FolioDocumentPrivacyArchiveError(cause instanceof ZipError && cause.reason === "declared-size-exceeded" ? {
126
+ message: "Document privacy core properties exceeded the part-size limit",
127
+ reason: "core-properties-too-large"
128
+ } : {
129
+ message: "Document privacy input is not a readable package",
130
+ reason: "load-failed",
131
+ cause
132
+ });
133
+ }
134
+ return decodeText(read.value, { ignoreBOM: true });
138
135
  };
139
136
  /** Rewrite selected package metadata fields without changing other package parts. */
140
137
  const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
@@ -156,16 +153,9 @@ const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
156
153
  removedMetadataProperties: []
157
154
  }
158
155
  };
159
- writeZipPart({
160
- zip,
161
- path: coreProperties.name,
162
- data: encodeUtf8(rewritten.xml)
163
- });
156
+ zip.set(coreProperties.path, rewritten.xml);
164
157
  return {
165
- buffer: await zip.generateAsync({
166
- type: "arraybuffer",
167
- compression: "DEFLATE"
168
- }),
158
+ buffer: (await zip.generate()).buffer,
169
159
  privacyReport: {
170
160
  appliedTransforms,
171
161
  removedMetadataProperties: rewritten.removedMetadataProperties
@@ -1,10 +1,10 @@
1
1
  import { REVISION_ELEMENT_NAMES } from "./revisionIdNormalization.js";
2
- import { parseStreamingXmlWithIdentityVisitor, scanStreamingXmlNumericIdAttributes } from "./streamingXmlParser.js";
3
2
  import { replaceRawDocxXmlParts } from "./unzip.js";
4
3
  import { getLocalName, getNamespaceUri, resolveAttributeNamespaceUri } from "./xmlParser.js";
5
- import { XmlResourceLimitError } from "./xmlResourceLimits.js";
6
4
  import { panic } from "better-result";
7
5
  import { assertValidOoxmlNumericId, isOoxmlNumericIdAttributeName, isValidOoxmlNumericId, mayContainInvalidOoxmlNumericIds, mayContainOoxmlNumericIds, ooxmlNumericIdDomain } from "@stll/docx-core";
6
+ import { parseStreamingXmlWithIdentityVisitor, scanStreamingXmlNumericIdAttributes } from "@stll/docx-core/xml";
7
+ import { XmlResourceLimitError } from "@stll/docx-core/xml/limits";
8
8
  // src/docx/numericIdNormalization.ts
9
9
  const identityKey = (value) => {
10
10
  const spelling = String(value).trim();
@@ -1,4 +1,4 @@
1
- import JSZip from "jszip";
1
+ import { ZipArchive } from "@stll/docx-core/zip";
2
2
  /** Resolve an internal URI reference once, before comparing it with ZIP entries. */
3
3
  export declare const resolvePackageRelationshipTarget: (target: string, relsPath: string) => string | undefined;
4
4
  /**
@@ -13,7 +13,7 @@ export declare const resolvePackageRelationshipTarget: (target: string, relsPath
13
13
  */
14
14
  export declare const isUnsafePackagePath: (path: string) => boolean;
15
15
  /** Drop the entries whose path a save refuses. Every other part survives. */
16
- export declare const removeUnsafeEntries: (zip: JSZip) => void;
16
+ export declare const removeUnsafeEntries: (zip: ZipArchive) => void;
17
17
  /** What reconciliation removed to make the package internally consistent. */
18
18
  export type PackageReferenceRepair = {
19
19
  /** Parts a relationship named that the package does not hold. */
@@ -41,7 +41,7 @@ export type PackageReferenceRepair = {
41
41
  * already-reconciled package therefore reports nothing and changes nothing,
42
42
  * which is what the repack tests assert about their own output.
43
43
  */
44
- export declare const reconcilePackageReferences: (zip: JSZip, compressionLevel: number) => Promise<PackageReferenceRepair>;
44
+ export declare const reconcilePackageReferences: (zip: ZipArchive, compressionLevel: number) => Promise<PackageReferenceRepair>;
45
45
  /**
46
46
  * What {@link checkPackageIntegrity} found wrong with a package. Both fields
47
47
  * are empty for a package where every internal relationship resolves and
@@ -75,4 +75,4 @@ export type PackageIntegrityReport = {
75
75
  * or a corpus-gate invariant: one pass over the `.rels` parts building an id
76
76
  * census, one pass over the XML parts checking references against it.
77
77
  */
78
- export declare const checkPackageIntegrity: (zip: JSZip) => Promise<PackageIntegrityReport>;
78
+ export declare const checkPackageIntegrity: (zip: ZipArchive) => Promise<PackageIntegrityReport>;
@@ -1,6 +1,5 @@
1
- import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
1
+ import { decodeText } from "../utils/textEncoding.js";
2
2
  import { Result } from "better-result";
3
- import { writeZipPart } from "@stll/docx-core/zip";
4
3
  // src/docx/packageParts.ts
5
4
  /**
6
5
  * What a repack carries out of the source package, and the guarantee that
@@ -99,11 +98,11 @@ const isUnsafePackagePath = (path) => {
99
98
  };
100
99
  /** Drop the entries whose path a save refuses. Every other part survives. */
101
100
  const removeUnsafeEntries = (zip) => {
102
- for (const [path, file] of Object.entries(zip.files)) if (!file.dir && isUnsafePackagePath(path)) zip.remove(path);
101
+ for (const path of zip.paths()) if (isUnsafePackagePath(path)) zip.delete(path);
103
102
  };
104
103
  const presentPartPaths = (zip) => {
105
104
  const present = /* @__PURE__ */ new Set();
106
- for (const [path, file] of Object.entries(zip.files)) if (!file.dir) present.add(normalizePartPath(path));
105
+ for (const path of zip.paths()) present.add(normalizePartPath(path));
107
106
  return present;
108
107
  };
109
108
  const isPresent = (present, partPath) => present.has(normalizePartPath(partPath)) || present.has(normalizePartPath(decodePartName(partPath) ?? ""));
@@ -121,25 +120,18 @@ const relationshipTarget = (element, relsPath) => {
121
120
  */
122
121
  const readRelationshipParts = async (zip) => {
123
122
  const parts = [];
124
- for (const [path, file] of Object.entries(zip.files)) {
125
- if (file.dir || !normalizePartPath(path).endsWith(".rels")) continue;
123
+ for (const file of zip.entries()) {
124
+ const { path } = file;
125
+ if (!normalizePartPath(path).endsWith(".rels")) continue;
126
126
  parts.push({
127
127
  path,
128
- xml: await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))
128
+ xml: await file.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }))
129
129
  });
130
130
  }
131
131
  return parts;
132
132
  };
133
133
  const writePart = ({ zip, path, xml, compressionLevel }) => {
134
- writeZipPart({
135
- zip,
136
- path,
137
- data: encodeUtf8(xml),
138
- options: {
139
- compression: "DEFLATE",
140
- compressionOptions: { level: compressionLevel }
141
- }
142
- });
134
+ zip.set(path, xml, { level: compressionLevel });
143
135
  };
144
136
  /**
145
137
  * Make the package internally consistent before it is written: every internal
@@ -183,11 +175,11 @@ const reconcilePackageReferences = async (zip, compressionLevel) => {
183
175
  });
184
176
  }
185
177
  for (const [partPath, ids] of removedIdsByOwningPart) {
186
- const file = zip.file(partPath);
178
+ const file = zip.entry(partPath);
187
179
  if (!file) continue;
188
180
  const idPattern = [...ids].map((id) => escapeRegExp(id)).join("|");
189
181
  const referencePattern = new RegExp(`\\s+r:[A-Za-z]+\\s*=\\s*(?<quote>["'])(?<id>${idPattern})\\k<quote>`, "gu");
190
- const xml = await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
182
+ const xml = await file.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }));
191
183
  const removedIds = /* @__PURE__ */ new Set();
192
184
  const scrubbed = xml.replaceAll(referencePattern, (...args) => {
193
185
  const groups = args.at(-1);
@@ -204,10 +196,10 @@ const reconcilePackageReferences = async (zip, compressionLevel) => {
204
196
  for (const id of removedIds) repair.orphanedIdReferences.push(`${partPath}|${id}`);
205
197
  }
206
198
  }
207
- const contentTypes = zip.file(CONTENT_TYPES_PATTERN)[0];
199
+ const contentTypes = zip.entries().find(({ path }) => CONTENT_TYPES_PATTERN.test(path));
208
200
  if (!contentTypes) return repair;
209
201
  let droppedOverride = false;
210
- const prunedContentTypes = (await contentTypes.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))).replaceAll(CONTENT_TYPE_ELEMENT, (element) => {
202
+ const prunedContentTypes = (await contentTypes.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }))).replaceAll(CONTENT_TYPE_ELEMENT, (element) => {
211
203
  const partName = PART_NAME_ATTRIBUTE.exec(element)?.groups?.["value"];
212
204
  if (partName === void 0 || isPresent(present, partName)) return element;
213
205
  repair.danglingOverrides.push(normalizePartPath(decodePartName(partName) ?? partName));
@@ -216,7 +208,7 @@ const reconcilePackageReferences = async (zip, compressionLevel) => {
216
208
  });
217
209
  if (droppedOverride) writePart({
218
210
  zip,
219
- path: contentTypes.name,
211
+ path: contentTypes.path,
220
212
  xml: prunedContentTypes,
221
213
  compressionLevel
222
214
  });
@@ -255,9 +247,10 @@ const checkPackageIntegrity = async (zip) => {
255
247
  }
256
248
  if (owningPart !== void 0) idsByOwningPart.set(owningPart, ids);
257
249
  }
258
- for (const [path, file] of Object.entries(zip.files)) {
259
- if (file.dir || !normalizePartPath(path).endsWith(".xml")) continue;
260
- const xml = await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
250
+ for (const file of zip.entries()) {
251
+ const { path } = file;
252
+ if (!normalizePartPath(path).endsWith(".xml")) continue;
253
+ const xml = await file.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }));
261
254
  const ids = idsByOwningPart.get(path);
262
255
  for (const match of xml.matchAll(REFERENCE_ATTRIBUTE)) {
263
256
  const id = match.groups?.["id"];
@@ -37,13 +37,13 @@ import { parseSettings } from "./settingsParser.js";
37
37
  import { parseStylesPackage } from "./styleParser.js";
38
38
  import { applyThemeFontLang, parseTheme } from "./themeParser.js";
39
39
  import { UNBALANCED_MOVE_RANGE_WARNING, normalizeTrackedMoveRanges } from "./trackedMoveRangeNormalization.js";
40
- import { getEntryUncompressedSize, getMediaMimeType, mediaToDataUrl, replaceRawDocxXmlParts, unzipDocx } from "./unzip.js";
40
+ import { getMediaMimeType, mediaToDataUrl, replaceRawDocxXmlParts, unzipDocx } from "./unzip.js";
41
41
  import { renderWmfSvg } from "./wmfSvg.js";
42
42
  import { getAttribute, getChildElements, getLocalName, parseXml, parseXmlDocument } from "./xmlParser.js";
43
- import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
44
43
  import { TaggedError, panic } from "better-result";
45
44
  import { PARSE_WARNING_CODES, setDocumentBodyComments } from "@stll/docx-core/model";
46
45
  import { isCommentExhausted } from "@stll/docx-core/ops";
46
+ import { FOLIO_XML_RESOURCE_LIMITS } from "@stll/docx-core/xml/limits";
47
47
  // src/docx/parser.ts
48
48
  /**
49
49
  * Main Parser Orchestrator - Unified parseDocx function
@@ -123,11 +123,10 @@ const extractAltChunkPlainText = async (xml, context) => {
123
123
  if (!relationship || relationship.targetMode === "External" || relationship.type !== ALT_CHUNK_RELATIONSHIP_TYPE && relationship.type !== ALT_CHUNK_STRICT_RELATIONSHIP_TYPE) return;
124
124
  const partPath = resolveRelativePath(context.relationshipsPath, relationship.target);
125
125
  if (contentTypeOf(context.raw.contentTypesXml, partPath)?.split(";")[0]?.trim().toLowerCase() !== "text/plain") return;
126
- const file = context.raw.originalZip.file(partPath);
126
+ const file = context.raw.originalZip.entry(partPath);
127
127
  if (!file) return;
128
- const declaredSize = getEntryUncompressedSize(file);
129
- if (declaredSize === null || declaredSize > context.budget.remainingBytes) return void 0;
130
- const bytes = await file.async("uint8array");
128
+ if (file.size > context.budget.remainingBytes) return void 0;
129
+ const bytes = await file.bytes();
131
130
  if (bytes.byteLength > context.budget.remainingBytes) return;
132
131
  context.budget.remainingBytes -= bytes.byteLength;
133
132
  try {
@@ -1,9 +1,9 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { RemovedSectionReference } from "../internal/sectionEndpointResolution.js";
3
- import JSZip from "jszip";
3
+ import { ZipArchive } from "@stll/docx-core/zip";
4
4
  type RemoveResolvedHeaderFooterPartsOptions = {
5
5
  document: document_d_exports.Document;
6
- zip: JSZip;
6
+ zip: ZipArchive;
7
7
  removedReferences: readonly RemovedSectionReference[];
8
8
  compressionLevel: number;
9
9
  };
@@ -1,11 +1,10 @@
1
- import { decodeText, encodeUtf8 } from "../utils/textEncoding.js";
1
+ import { decodeText } from "../utils/textEncoding.js";
2
2
  import { isUnsafePackagePath } from "./packageParts.js";
3
3
  import { parseRelationships, resolveRelativePath } from "./relsParser.js";
4
4
  import { captureVerbatimXml } from "./verbatimCapture.js";
5
5
  import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, cloneElement, getAttribute, getLocalName, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
6
6
  import { panic } from "better-result";
7
7
  import { OOXML_NS } from "@stll/docx-utils";
8
- import { writeZipPart } from "@stll/docx-core/zip";
9
8
  // src/docx/removeHeaderFooterParts.ts
10
9
  const DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
11
10
  const officeRelationshipHasType = (type, kind) => [...OFFICE_RELATIONSHIP_NAMESPACE_URIS].some((namespace) => type === `${namespace}/${kind}`);
@@ -17,9 +16,9 @@ const withoutChildren = (xml, remove) => {
17
16
  /** Remove only parts whose last selection was explicitly resolved out of the document. */
18
17
  const removeResolvedHeaderFooterParts = async ({ document, zip, removedReferences, compressionLevel }) => {
19
18
  if (removedReferences.length === 0) return;
20
- const relsFile = zip.file(DOCUMENT_RELS_PATH);
19
+ const relsFile = zip.entry(DOCUMENT_RELS_PATH);
21
20
  if (!relsFile) return panic("A resolved header/footer part has no document relationships");
22
- const relsXml = await relsFile.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
21
+ const relsXml = await relsFile.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }));
23
22
  const relationships = parseRelationships(relsXml);
24
23
  const removedIds = /* @__PURE__ */ new Set();
25
24
  const candidates = /* @__PURE__ */ new Set();
@@ -38,61 +37,45 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
38
37
  if (removedIds.has(relationship.id) || relationship.targetMode === "External") continue;
39
38
  candidates.delete(resolveRelativePath(DOCUMENT_RELS_PATH, relationship.target));
40
39
  }
41
- for (const file of Object.values(zip.files)) {
42
- if (file.dir || !file.name.endsWith(".rels") || file.name === DOCUMENT_RELS_PATH) continue;
43
- const otherRelationships = parseRelationships(await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true })));
44
- for (const relationship of otherRelationships.values()) if (relationship.targetMode !== "External") candidates.delete(resolveRelativePath(file.name, relationship.target));
40
+ for (const file of zip.entries()) {
41
+ if (!file.path.endsWith(".rels") || file.path === DOCUMENT_RELS_PATH) continue;
42
+ const otherRelationships = parseRelationships(await file.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true })));
43
+ for (const relationship of otherRelationships.values()) if (relationship.targetMode !== "External") candidates.delete(resolveRelativePath(file.path, relationship.target));
45
44
  }
46
45
  const retiredMedia = /* @__PURE__ */ new Set();
47
46
  for (const path of candidates) {
48
47
  const slash = path.lastIndexOf("/");
49
48
  const relsPath = `${path.slice(0, slash + 1)}_rels/${path.slice(slash + 1)}.rels`;
50
- const partRels = zip.file(relsPath);
49
+ const partRels = zip.entry(relsPath);
51
50
  if (!partRels) continue;
52
- for (const relationship of parseRelationships(await partRels.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))).values()) {
51
+ for (const relationship of parseRelationships(await partRels.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }))).values()) {
53
52
  if (!officeRelationshipHasType(relationship.type, "image") || relationship.targetMode === "External") continue;
54
53
  const mediaPath = resolveRelativePath(relsPath, relationship.target);
55
54
  if (!isUnsafePackagePath(mediaPath) && mediaPath.startsWith("word/media/")) retiredMedia.add(mediaPath);
56
55
  }
57
56
  }
58
57
  const compressionOptions = { level: compressionLevel };
59
- writeZipPart({
60
- zip,
61
- path: DOCUMENT_RELS_PATH,
62
- data: encodeUtf8(withoutChildren(relsXml, (child) => getNamespaceUri(child) === OOXML_NS.pr && getLocalName(child.name) === "Relationship" && removedIds.has(getAttribute(child, null, "Id") ?? ""))),
63
- options: {
64
- compression: "DEFLATE",
65
- compressionOptions
66
- }
67
- });
58
+ zip.set(DOCUMENT_RELS_PATH, withoutChildren(relsXml, (child) => getNamespaceUri(child) === OOXML_NS.pr && getLocalName(child.name) === "Relationship" && removedIds.has(getAttribute(child, null, "Id") ?? "")), compressionOptions);
68
59
  if (document.package.relationships) {
69
60
  document.package.relationships = new Map(document.package.relationships);
70
61
  for (const id of removedIds) document.package.relationships.delete(id);
71
62
  }
72
63
  for (const path of candidates) {
73
- zip.remove(path);
64
+ zip.delete(path);
74
65
  const slash = path.lastIndexOf("/");
75
- zip.remove(`${path.slice(0, slash + 1)}_rels/${path.slice(slash + 1)}.rels`);
66
+ zip.delete(`${path.slice(0, slash + 1)}_rels/${path.slice(slash + 1)}.rels`);
76
67
  }
77
- for (const file of Object.values(zip.files)) {
78
- if (file.dir || !file.name.endsWith(".rels")) continue;
79
- for (const relationship of parseRelationships(await file.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }))).values()) if (relationship.targetMode !== "External") retiredMedia.delete(resolveRelativePath(file.name, relationship.target));
68
+ for (const file of zip.entries()) {
69
+ if (!file.path.endsWith(".rels")) continue;
70
+ for (const relationship of parseRelationships(await file.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }))).values()) if (relationship.targetMode !== "External") retiredMedia.delete(resolveRelativePath(file.path, relationship.target));
80
71
  }
81
72
  for (const path of retiredMedia) {
82
- zip.remove(path);
73
+ zip.delete(path);
83
74
  candidates.add(path);
84
75
  }
85
- const contentTypes = zip.file("[Content_Types].xml");
76
+ const contentTypes = zip.entry("[Content_Types].xml");
86
77
  if (!contentTypes) return panic("The package has no content types");
87
- const contentTypesXml = await contentTypes.async("uint8array").then((bytes) => decodeText(bytes, { ignoreBOM: true }));
88
- writeZipPart({
89
- zip,
90
- path: "[Content_Types].xml",
91
- data: encodeUtf8(withoutChildren(contentTypesXml, (child) => getNamespaceUri(child) === OOXML_NS.ct && getLocalName(child.name) === "Override" && candidates.has((getAttribute(child, null, "PartName") ?? "").replace(/^\//u, "")))),
92
- options: {
93
- compression: "DEFLATE",
94
- compressionOptions
95
- }
96
- });
78
+ const contentTypesXml = await contentTypes.bytes().then((bytes) => decodeText(bytes, { ignoreBOM: true }));
79
+ zip.set("[Content_Types].xml", withoutChildren(contentTypesXml, (child) => getNamespaceUri(child) === OOXML_NS.ct && getLocalName(child.name) === "Override" && candidates.has((getAttribute(child, null, "PartName") ?? "").replace(/^\//u, ""))), compressionOptions);
97
80
  };
98
81
  export { removeResolvedHeaderFooterParts };
@@ -1,7 +1,7 @@
1
- import { rewriteStreamingXmlDecimalAttributes } from "./streamingXmlParser.js";
2
1
  import { WORDPROCESSINGML_NAMESPACE_URIS, findAttributeByNamespaceUri, getLocalName, getNamespaceUri } from "./xmlParser.js";
3
- import { XmlResourceLimitError, assertXmlResourceLimits } from "./xmlResourceLimits.js";
4
2
  import { TaggedError } from "better-result";
3
+ import { rewriteStreamingXmlDecimalAttributes } from "@stll/docx-core/xml";
4
+ import { XmlResourceLimitError, assertXmlResourceLimits } from "@stll/docx-core/xml/limits";
5
5
  // src/docx/revisionIdNormalization.ts
6
6
  const RevisionIdCollisionErrorBase = TaggedError("RevisionIdCollisionError");
7
7
  /**
@@ -5,7 +5,7 @@ import { SaveDiagnosticOptions } from "./saveDiagnostics.js";
5
5
  import { DocumentBodyAuthorityOptions } from "./serializer/documentSerializer.js";
6
6
  import { RawDocxContent } from "./unzip.js";
7
7
  import { TaggedErrorClass } from "better-result";
8
- import JSZip from "jszip";
8
+ import { ZipArchive } from "@stll/docx-core/zip";
9
9
  declare const DocxPackageFidelityErrorBase: TaggedErrorClass<"DocxPackageFidelityError">;
10
10
  export declare class DocxPackageFidelityError extends DocxPackageFidelityErrorBase<{
11
11
  message: string;
@@ -65,7 +65,7 @@ export declare class CanonicalImageRelationshipRefusalError extends CanonicalIma
65
65
  }> {}
66
66
  type PublishCanonicalImageResourcesOptions = {
67
67
  document: document_d_exports.Document;
68
- zip: JSZip;
68
+ zip: ZipArchive;
69
69
  compressionLevel: number;
70
70
  };
71
71
  /** Publish allocated pictures into each owning story without rebinding canonical ids. */
@@ -75,7 +75,7 @@ export declare function collectHyperlinksWithoutRId(blocks: BlockContent[]): Hyp
75
75
  /** The selective save boundary must use the same resource census as full repack. */
76
76
  export declare const hasUnmaterializedInlineResources: (blocks: BlockContent[]) => boolean;
77
77
  /** Compare against the already loaded source ZIP, without mutating model or package. */
78
- export declare const hasUnmaterializedHyperlinkBindings: (document: document_d_exports.Document, zip: JSZip) => Promise<boolean>;
78
+ export declare const hasUnmaterializedHyperlinkBindings: (document: document_d_exports.Document, zip: ZipArchive) => Promise<boolean>;
79
79
  /**
80
80
  * Options for repacking DOCX
81
81
  */
@@ -156,7 +156,7 @@ export declare function updateXmlFile(originalBuffer: ArrayBuffer, path: string,
156
156
  */
157
157
  export declare function updateMultipleFiles(originalBuffer: ArrayBuffer, updates: Map<string, string | ArrayBuffer>, options?: RepackOptions): Promise<ArrayBuffer>;
158
158
  /**
159
- * Apply file updates to an already-loaded JSZip instance and generate the output.
159
+ * Apply file updates to an already-loaded ZipArchive instance and generate the output.
160
160
  * Use this when the zip is already loaded to avoid a redundant decompression pass.
161
161
  *
162
162
  * This is the selective save's exit, so it owes the package the same passes
@@ -165,7 +165,7 @@ export declare function updateMultipleFiles(originalBuffer: ArrayBuffer, updates
165
165
  * revision ids are free and because an out-of-range paragraph id, or a
166
166
  * malformed application version, can sit in a part it never touched.
167
167
  */
168
- export declare function applyUpdatesToZip(zip: JSZip, updates: Map<string, string | ArrayBuffer>, options?: RepackOptions): Promise<ArrayBuffer>;
168
+ export declare function applyUpdatesToZip(zip: ZipArchive, updates: Map<string, string | ArrayBuffer>, options?: RepackOptions): Promise<ArrayBuffer>;
169
169
  /**
170
170
  * Add a new relationship to document.xml.rels
171
171
  *
@@ -219,7 +219,7 @@ export declare function hasModelDrivenPictureWatermark(doc: document_d_exports.D
219
219
  * root can keep any prefix binding only the source document declared.
220
220
  */
221
221
  type CollectHeaderFooterUpdatesOptions = SaveDiagnosticOptions & {
222
- sourceZip: JSZip;
222
+ sourceZip: ZipArchive;
223
223
  };
224
224
  export declare function collectHeaderFooterUpdates(doc: document_d_exports.Document, { sourceZip, onDiagnostic }: CollectHeaderFooterUpdatesOptions): Promise<Map<string, string>>;
225
225
  type AddedStylesPlan = {
@@ -233,7 +233,7 @@ type AddedStylesPlan = {
233
233
  xml: string;
234
234
  };
235
235
  /** One append-only style owner for full repack and selective overlays. */
236
- export declare function planAddedStyles(doc: document_d_exports.Document, originalZip: JSZip): Promise<AddedStylesPlan>;
236
+ export declare function planAddedStyles(doc: document_d_exports.Document, originalZip: ZipArchive): Promise<AddedStylesPlan>;
237
237
  /** `word/Footnotes.xml` -> `word/_rels/Footnotes.xml.rels` (casing preserved). */
238
238
  export declare function notePartRelsPath(partPath: string): string;
239
239
  /** Update existing core-property values without synthesizing absent metadata. */