@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.
- package/dist/ai-edits/headless.js +3 -3
- package/dist/compare/reproducible-package.d.ts +5 -0
- package/dist/compare/reproducible-package.js +10 -23
- package/dist/docx/archiveInflation.d.ts +8 -103
- package/dist/docx/archiveInflation.js +8 -145
- package/dist/docx/canonicalStoryRepack.js +2 -2
- package/dist/docx/ensureParaIds.js +7 -22
- package/dist/docx/metadataPrivacy.js +38 -48
- package/dist/docx/packageParts.d.ts +4 -4
- package/dist/docx/packageParts.js +17 -24
- package/dist/docx/parser.js +4 -5
- package/dist/docx/removeHeaderFooterParts.d.ts +2 -2
- package/dist/docx/removeHeaderFooterParts.js +19 -36
- package/dist/docx/rezip.d.ts +7 -7
- package/dist/docx/rezip.js +143 -531
- package/dist/docx/selectiveSave.js +37 -41
- package/dist/docx/selectiveSaveFlags.d.ts +1 -1
- package/dist/docx/selectiveSaveTripwire.js +5 -11
- package/dist/docx/server/applyDocxXmlPatchProposal.js +20 -41
- package/dist/docx/server/boundedArchive.d.ts +1 -2
- package/dist/docx/server/boundedArchive.js +77 -98
- package/dist/docx/server/inspectDocxPackage.d.ts +1 -1
- package/dist/docx/server/inspectDocxPackage.js +1 -1
- package/dist/docx/unzip.d.ts +2 -3
- package/dist/docx/unzip.js +126 -154
- package/package.json +3 -4
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { decodeText
|
|
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
|
|
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
|
|
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
|
|
125
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
259
|
-
|
|
260
|
-
|
|
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"];
|
package/dist/docx/parser.js
CHANGED
|
@@ -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 {
|
|
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.
|
|
126
|
+
const file = context.raw.originalZip.entry(partPath);
|
|
127
127
|
if (!file) return;
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
3
|
+
import { ZipArchive } from "@stll/docx-core/zip";
|
|
4
4
|
type RemoveResolvedHeaderFooterPartsOptions = {
|
|
5
5
|
document: document_d_exports.Document;
|
|
6
|
-
zip:
|
|
6
|
+
zip: ZipArchive;
|
|
7
7
|
removedReferences: readonly RemovedSectionReference[];
|
|
8
8
|
compressionLevel: number;
|
|
9
9
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { decodeText
|
|
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.
|
|
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.
|
|
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
|
|
42
|
-
if (
|
|
43
|
-
const otherRelationships = parseRelationships(await file.
|
|
44
|
-
for (const relationship of otherRelationships.values()) if (relationship.targetMode !== "External") candidates.delete(resolveRelativePath(file.
|
|
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.
|
|
49
|
+
const partRels = zip.entry(relsPath);
|
|
51
50
|
if (!partRels) continue;
|
|
52
|
-
for (const relationship of parseRelationships(await partRels.
|
|
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
|
-
|
|
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.
|
|
64
|
+
zip.delete(path);
|
|
74
65
|
const slash = path.lastIndexOf("/");
|
|
75
|
-
zip.
|
|
66
|
+
zip.delete(`${path.slice(0, slash + 1)}_rels/${path.slice(slash + 1)}.rels`);
|
|
76
67
|
}
|
|
77
|
-
for (const file of
|
|
78
|
-
if (
|
|
79
|
-
for (const relationship of parseRelationships(await file.
|
|
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.
|
|
73
|
+
zip.delete(path);
|
|
83
74
|
candidates.add(path);
|
|
84
75
|
}
|
|
85
|
-
const contentTypes = zip.
|
|
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.
|
|
88
|
-
|
|
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 };
|
package/dist/docx/rezip.d.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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. */
|