@stll/folio-core 0.64.1 → 0.65.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.
@@ -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,7 +37,7 @@ 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
43
  import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
@@ -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 };
@@ -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. */