@beyondwork/docx-react-component 1.0.14 → 1.0.16
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/package.json +43 -24
- package/src/api/public-types.ts +15 -0
- package/src/compare/diff-engine.ts +84 -7
- package/src/compare/index.ts +25 -0
- package/src/compare/snapshot.ts +31 -0
- package/src/core/commands/formatting-commands.ts +225 -0
- package/src/formats/xlsx/io/serialize-shared-strings.ts +72 -0
- package/src/formats/xlsx/io/serialize-sheet.ts +333 -0
- package/src/formats/xlsx/io/serialize-styles.ts +98 -0
- package/src/formats/xlsx/io/serialize-workbook.ts +429 -0
- package/src/formats/xlsx/runtime/cell-commands.ts +567 -0
- package/src/formats/xlsx/runtime/sheet-commands.ts +206 -0
- package/src/formats/xlsx/runtime/workbook-runtime.ts +177 -0
- package/src/formats/xlsx/runtime/workbook-transaction.ts +822 -0
- package/src/io/ooxml/parse-main-document.ts +6 -6
- package/src/io/ooxml/parse-revisions.ts +18 -24
- package/src/legal/bookmarks.ts +35 -0
- package/src/legal/index.ts +32 -0
- package/src/legal/signature-blocks.ts +259 -0
- package/src/runtime/document-runtime.ts +43 -0
- package/src/runtime/numbering-prefix.ts +195 -0
- package/src/runtime/surface-projection.ts +292 -9
- package/src/ui/WordReviewEditor.tsx +107 -4
- package/src/ui-tailwind/editor-surface/pm-schema.ts +148 -13
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +15 -29
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { createExportSession } from "../../../io/export/export-session.ts";
|
|
2
|
+
import {
|
|
3
|
+
normalizePartPath,
|
|
4
|
+
resolveRelationshipTarget,
|
|
5
|
+
type OpcRelationship,
|
|
6
|
+
} from "../../../io/ooxml/part-manifest.ts";
|
|
7
|
+
import type { OpcPackage } from "../../../io/opc/package-reader.ts";
|
|
8
|
+
import { readOpcPackage } from "../../../io/opc/package-reader.ts";
|
|
9
|
+
import type { CanonicalWorkbook } from "../model/workbook.ts";
|
|
10
|
+
import { listSheets } from "../model/workbook.ts";
|
|
11
|
+
import { parseWorkbookXml } from "./parse-workbook.ts";
|
|
12
|
+
import { serializeSheetXml } from "./serialize-sheet.ts";
|
|
13
|
+
import { buildSharedStringsTable } from "./serialize-shared-strings.ts";
|
|
14
|
+
import { serializeStylesXml } from "./serialize-styles.ts";
|
|
15
|
+
|
|
16
|
+
const XLSX_MAIN_NAMESPACE = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
17
|
+
const XLSX_RELATIONSHIPS_NAMESPACE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
18
|
+
|
|
19
|
+
export const XLSX_OFFICE_DOCUMENT_REL_TYPE =
|
|
20
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
21
|
+
export const XLSX_WORKSHEET_REL_TYPE =
|
|
22
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
|
|
23
|
+
export const XLSX_SHARED_STRINGS_REL_TYPE =
|
|
24
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
|
|
25
|
+
export const XLSX_STYLES_REL_TYPE =
|
|
26
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
|
27
|
+
|
|
28
|
+
export const XLSX_WORKBOOK_CONTENT_TYPE =
|
|
29
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
|
|
30
|
+
export const XLSX_WORKSHEET_CONTENT_TYPE =
|
|
31
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
|
32
|
+
export const XLSX_SHARED_STRINGS_CONTENT_TYPE =
|
|
33
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
|
|
34
|
+
export const XLSX_STYLES_CONTENT_TYPE =
|
|
35
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
|
|
36
|
+
|
|
37
|
+
interface WorkbookSheetBinding {
|
|
38
|
+
sheetId: string;
|
|
39
|
+
name: string;
|
|
40
|
+
relationshipId: string;
|
|
41
|
+
state: "visible" | "hidden";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface WorkbookPartBinding {
|
|
45
|
+
path: string;
|
|
46
|
+
relationshipId: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface SheetExportBinding extends WorkbookPartBinding {
|
|
50
|
+
sheet: ReturnType<typeof listSheets>[number];
|
|
51
|
+
relationships: OpcRelationship[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface SourceWorkbookLayout {
|
|
55
|
+
workbookPath: string;
|
|
56
|
+
workbookDirectory: string;
|
|
57
|
+
workbookRelationships: OpcRelationship[];
|
|
58
|
+
sheetBindingsById: Map<string, WorkbookPartBinding>;
|
|
59
|
+
sheetRelationshipsByPath: Map<string, OpcRelationship[]>;
|
|
60
|
+
sharedStringsBinding?: WorkbookPartBinding;
|
|
61
|
+
stylesBinding?: WorkbookPartBinding;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function serializeWorkbookXml(
|
|
65
|
+
workbook: CanonicalWorkbook,
|
|
66
|
+
sheets: readonly WorkbookSheetBinding[],
|
|
67
|
+
): string {
|
|
68
|
+
const sheetXml = sheets.map((sheet) => {
|
|
69
|
+
const stateAttr = sheet.state === "hidden" ? ` state="hidden"` : "";
|
|
70
|
+
return ` <sheet name="${escapeXml(sheet.name)}" sheetId="${escapeXml(sheet.sheetId)}"${stateAttr} r:id="${sheet.relationshipId}"/>`;
|
|
71
|
+
});
|
|
72
|
+
const workbookPr = workbook.metadata.date1904
|
|
73
|
+
? ` <workbookPr date1904="1"/>`
|
|
74
|
+
: null;
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
78
|
+
`<workbook xmlns="${XLSX_MAIN_NAMESPACE}" xmlns:r="${XLSX_RELATIONSHIPS_NAMESPACE}">`,
|
|
79
|
+
...(workbookPr ? [workbookPr] : []),
|
|
80
|
+
` <sheets>`,
|
|
81
|
+
...sheetXml,
|
|
82
|
+
` </sheets>`,
|
|
83
|
+
`</workbook>`,
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function exportXlsxWorkbook(
|
|
88
|
+
workbook: CanonicalWorkbook,
|
|
89
|
+
source: Uint8Array | ArrayBuffer | OpcPackage,
|
|
90
|
+
): Uint8Array {
|
|
91
|
+
const sourcePackage = toOpcPackage(source);
|
|
92
|
+
const sourceLayout = resolveSourceWorkbookLayout(sourcePackage);
|
|
93
|
+
const encoder = new TextEncoder();
|
|
94
|
+
const sheets = listSheets(workbook);
|
|
95
|
+
const sharedStrings = buildSharedStringsTable(workbook);
|
|
96
|
+
const includeSharedStrings =
|
|
97
|
+
sharedStrings.strings.length > 0 || sourceLayout.sharedStringsBinding !== undefined;
|
|
98
|
+
const includeStyles =
|
|
99
|
+
workbook.styles.cellFormats.length > 0 ||
|
|
100
|
+
workbook.styles.numFormats.size > 0 ||
|
|
101
|
+
sourceLayout.stylesBinding !== undefined;
|
|
102
|
+
|
|
103
|
+
const sheetBindings = allocateSheetBindings(
|
|
104
|
+
sheets,
|
|
105
|
+
sourceLayout,
|
|
106
|
+
new Set(sourcePackage.parts.keys()),
|
|
107
|
+
);
|
|
108
|
+
const workbookSheetBindings: WorkbookSheetBinding[] = sheetBindings.map((binding) => ({
|
|
109
|
+
sheetId: binding.sheet.sheetId,
|
|
110
|
+
name: binding.sheet.name,
|
|
111
|
+
relationshipId: binding.relationshipId,
|
|
112
|
+
state: binding.sheet.hidden ? "hidden" : "visible",
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
const sharedStringsBinding = includeSharedStrings
|
|
116
|
+
? sourceLayout.sharedStringsBinding ?? createWorkbookPartBinding(
|
|
117
|
+
sourceLayout.workbookDirectory,
|
|
118
|
+
new Set(sourceLayout.workbookRelationships.map((relationship) => relationship.id)),
|
|
119
|
+
"sharedStrings.xml",
|
|
120
|
+
"rIdSharedStrings",
|
|
121
|
+
)
|
|
122
|
+
: undefined;
|
|
123
|
+
const stylesBinding = includeStyles
|
|
124
|
+
? sourceLayout.stylesBinding ?? createWorkbookPartBinding(
|
|
125
|
+
sourceLayout.workbookDirectory,
|
|
126
|
+
new Set(sourceLayout.workbookRelationships.map((relationship) => relationship.id)),
|
|
127
|
+
"styles.xml",
|
|
128
|
+
"rIdStyles",
|
|
129
|
+
)
|
|
130
|
+
: undefined;
|
|
131
|
+
|
|
132
|
+
const ownedPaths = [
|
|
133
|
+
sourceLayout.workbookPath,
|
|
134
|
+
...sheetBindings.map((binding) => binding.path),
|
|
135
|
+
...(sharedStringsBinding ? [sharedStringsBinding.path] : []),
|
|
136
|
+
...(stylesBinding ? [stylesBinding.path] : []),
|
|
137
|
+
];
|
|
138
|
+
const exportSession = createExportSession(sourcePackage, ownedPaths);
|
|
139
|
+
|
|
140
|
+
for (const binding of sheetBindings) {
|
|
141
|
+
exportSession.replaceOwnedPart({
|
|
142
|
+
path: binding.path,
|
|
143
|
+
bytes: encoder.encode(
|
|
144
|
+
serializeSheetXml(binding.sheet, {
|
|
145
|
+
sharedStringIndexByValue: includeSharedStrings
|
|
146
|
+
? sharedStrings.indexByValue
|
|
147
|
+
: undefined,
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
150
|
+
contentType: XLSX_WORKSHEET_CONTENT_TYPE,
|
|
151
|
+
relationships: binding.relationships,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (sharedStringsBinding) {
|
|
156
|
+
exportSession.replaceOwnedPart({
|
|
157
|
+
path: sharedStringsBinding.path,
|
|
158
|
+
bytes: encoder.encode(sharedStrings.xml),
|
|
159
|
+
contentType: XLSX_SHARED_STRINGS_CONTENT_TYPE,
|
|
160
|
+
relationships: sourcePackage.parts.get(sharedStringsBinding.path)?.relationships ?? [],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (stylesBinding) {
|
|
165
|
+
exportSession.replaceOwnedPart({
|
|
166
|
+
path: stylesBinding.path,
|
|
167
|
+
bytes: encoder.encode(serializeStylesXml(workbook.styles)),
|
|
168
|
+
contentType: XLSX_STYLES_CONTENT_TYPE,
|
|
169
|
+
relationships: sourcePackage.parts.get(stylesBinding.path)?.relationships ?? [],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const preservedRelationships = sourceLayout.workbookRelationships
|
|
174
|
+
.filter(
|
|
175
|
+
(relationship) => ![
|
|
176
|
+
XLSX_WORKSHEET_REL_TYPE,
|
|
177
|
+
XLSX_SHARED_STRINGS_REL_TYPE,
|
|
178
|
+
XLSX_STYLES_REL_TYPE,
|
|
179
|
+
].includes(relationship.type),
|
|
180
|
+
)
|
|
181
|
+
.map(cloneRelationship);
|
|
182
|
+
const workbookRelationships = [
|
|
183
|
+
...preservedRelationships,
|
|
184
|
+
...sheetBindings.map((binding) => ({
|
|
185
|
+
id: binding.relationshipId,
|
|
186
|
+
type: XLSX_WORKSHEET_REL_TYPE,
|
|
187
|
+
target: toRelationshipTarget(sourceLayout.workbookPath, binding.path),
|
|
188
|
+
targetMode: "internal" as const,
|
|
189
|
+
})),
|
|
190
|
+
...(sharedStringsBinding
|
|
191
|
+
? [{
|
|
192
|
+
id: sharedStringsBinding.relationshipId,
|
|
193
|
+
type: XLSX_SHARED_STRINGS_REL_TYPE,
|
|
194
|
+
target: toRelationshipTarget(sourceLayout.workbookPath, sharedStringsBinding.path),
|
|
195
|
+
targetMode: "internal" as const,
|
|
196
|
+
}]
|
|
197
|
+
: []),
|
|
198
|
+
...(stylesBinding
|
|
199
|
+
? [{
|
|
200
|
+
id: stylesBinding.relationshipId,
|
|
201
|
+
type: XLSX_STYLES_REL_TYPE,
|
|
202
|
+
target: toRelationshipTarget(sourceLayout.workbookPath, stylesBinding.path),
|
|
203
|
+
targetMode: "internal" as const,
|
|
204
|
+
}]
|
|
205
|
+
: []),
|
|
206
|
+
].sort((left, right) => left.id.localeCompare(right.id));
|
|
207
|
+
|
|
208
|
+
exportSession.replaceOwnedPart({
|
|
209
|
+
path: sourceLayout.workbookPath,
|
|
210
|
+
bytes: encoder.encode(serializeWorkbookXml(workbook, workbookSheetBindings)),
|
|
211
|
+
contentType: sourcePackage.parts.get(sourceLayout.workbookPath)?.contentType ?? XLSX_WORKBOOK_CONTENT_TYPE,
|
|
212
|
+
relationships: workbookRelationships,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return exportSession.serialize();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function allocateSheetBindings(
|
|
219
|
+
sheets: ReturnType<typeof listSheets>,
|
|
220
|
+
sourceLayout: SourceWorkbookLayout,
|
|
221
|
+
usedPathsSeed: ReadonlySet<string>,
|
|
222
|
+
): SheetExportBinding[] {
|
|
223
|
+
const usedPaths = new Set(usedPathsSeed);
|
|
224
|
+
const usedRelationshipIds = new Set(
|
|
225
|
+
sourceLayout.workbookRelationships.map((relationship) => relationship.id),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return sheets.map((sheet) => {
|
|
229
|
+
const existingBinding = sourceLayout.sheetBindingsById.get(sheet.sheetId);
|
|
230
|
+
if (existingBinding) {
|
|
231
|
+
return {
|
|
232
|
+
sheet,
|
|
233
|
+
path: existingBinding.path,
|
|
234
|
+
relationshipId: existingBinding.relationshipId,
|
|
235
|
+
relationships: cloneRelationshipsForPath(sourceLayout, existingBinding.path),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const partPath = createUniqueSheetPath(sourceLayout.workbookDirectory, usedPaths);
|
|
240
|
+
usedPaths.add(partPath);
|
|
241
|
+
const relationshipId = createUniqueRelationshipId(usedRelationshipIds, "rIdSheet");
|
|
242
|
+
usedRelationshipIds.add(relationshipId);
|
|
243
|
+
return {
|
|
244
|
+
sheet,
|
|
245
|
+
path: partPath,
|
|
246
|
+
relationshipId,
|
|
247
|
+
relationships: [],
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function resolveSourceWorkbookLayout(sourcePackage: OpcPackage): SourceWorkbookLayout {
|
|
253
|
+
const workbookRelationship = sourcePackage.manifest.packageRelationships.find(
|
|
254
|
+
(relationship) => relationship.type === XLSX_OFFICE_DOCUMENT_REL_TYPE,
|
|
255
|
+
);
|
|
256
|
+
if (!workbookRelationship) {
|
|
257
|
+
throw new Error("Cannot export xlsx workbook: source package is missing officeDocument relationship.");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const workbookPath = resolveRelationshipTarget(null, workbookRelationship);
|
|
261
|
+
const workbookPart = sourcePackage.parts.get(workbookPath);
|
|
262
|
+
if (!workbookPart) {
|
|
263
|
+
throw new Error(`Cannot export xlsx workbook: workbook part missing at ${workbookPath}.`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const workbookXml = new TextDecoder("utf-8").decode(workbookPart.bytes);
|
|
267
|
+
const parsedWorkbook = parseWorkbookXml(workbookXml);
|
|
268
|
+
const sheetBindingsById = new Map<string, WorkbookPartBinding>();
|
|
269
|
+
const sheetRelationshipsByPath = new Map<string, OpcRelationship[]>();
|
|
270
|
+
|
|
271
|
+
for (const sheet of parsedWorkbook.sheets) {
|
|
272
|
+
const relationship = workbookPart.relationships.find(
|
|
273
|
+
(candidate) =>
|
|
274
|
+
candidate.id === sheet.relationshipId &&
|
|
275
|
+
candidate.type === XLSX_WORKSHEET_REL_TYPE,
|
|
276
|
+
);
|
|
277
|
+
if (!relationship) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const sheetPath = resolveRelationshipTarget(workbookPath, relationship);
|
|
282
|
+
sheetBindingsById.set(sheet.sheetId, {
|
|
283
|
+
path: sheetPath,
|
|
284
|
+
relationshipId: relationship.id,
|
|
285
|
+
});
|
|
286
|
+
sheetRelationshipsByPath.set(
|
|
287
|
+
sheetPath,
|
|
288
|
+
sourcePackage.parts.get(sheetPath)?.relationships.map(cloneRelationship) ?? [],
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const sharedStringsRelationship = workbookPart.relationships.find(
|
|
293
|
+
(relationship) => relationship.type === XLSX_SHARED_STRINGS_REL_TYPE,
|
|
294
|
+
);
|
|
295
|
+
const stylesRelationship = workbookPart.relationships.find(
|
|
296
|
+
(relationship) => relationship.type === XLSX_STYLES_REL_TYPE,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
workbookPath,
|
|
301
|
+
workbookDirectory: getDirectoryPath(workbookPath),
|
|
302
|
+
workbookRelationships: workbookPart.relationships.map(cloneRelationship),
|
|
303
|
+
sheetBindingsById,
|
|
304
|
+
sheetRelationshipsByPath,
|
|
305
|
+
sharedStringsBinding: sharedStringsRelationship
|
|
306
|
+
? {
|
|
307
|
+
path: resolveRelationshipTarget(workbookPath, sharedStringsRelationship),
|
|
308
|
+
relationshipId: sharedStringsRelationship.id,
|
|
309
|
+
}
|
|
310
|
+
: undefined,
|
|
311
|
+
stylesBinding: stylesRelationship
|
|
312
|
+
? {
|
|
313
|
+
path: resolveRelationshipTarget(workbookPath, stylesRelationship),
|
|
314
|
+
relationshipId: stylesRelationship.id,
|
|
315
|
+
}
|
|
316
|
+
: undefined,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function cloneRelationshipsForPath(
|
|
321
|
+
sourceLayout: SourceWorkbookLayout,
|
|
322
|
+
path: string,
|
|
323
|
+
): OpcRelationship[] {
|
|
324
|
+
return sourceLayout.sheetRelationshipsByPath.get(path)?.map(cloneRelationship) ?? [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function toOpcPackage(source: Uint8Array | ArrayBuffer | OpcPackage): OpcPackage {
|
|
328
|
+
if (source instanceof Uint8Array || source instanceof ArrayBuffer) {
|
|
329
|
+
return readOpcPackage(source);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (source && typeof source === "object" && source.parts instanceof Map) {
|
|
333
|
+
return source;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
throw new Error("Unsupported xlsx export source input.");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function createWorkbookPartBinding(
|
|
340
|
+
workbookDirectory: string,
|
|
341
|
+
existingRelationshipIds: Set<string>,
|
|
342
|
+
filename: string,
|
|
343
|
+
preferredRelationshipId: string,
|
|
344
|
+
): WorkbookPartBinding {
|
|
345
|
+
const relationshipId = createUniqueRelationshipId(existingRelationshipIds, preferredRelationshipId);
|
|
346
|
+
existingRelationshipIds.add(relationshipId);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
path: normalizePartPath(`${workbookDirectory}/${filename}`),
|
|
350
|
+
relationshipId,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function createUniqueSheetPath(
|
|
355
|
+
workbookDirectory: string,
|
|
356
|
+
existingPaths: ReadonlySet<string>,
|
|
357
|
+
): string {
|
|
358
|
+
let nextIndex = 1;
|
|
359
|
+
|
|
360
|
+
while (true) {
|
|
361
|
+
const candidate = normalizePartPath(`${workbookDirectory}/worksheets/sheet${nextIndex}.xml`);
|
|
362
|
+
if (!existingPaths.has(candidate)) {
|
|
363
|
+
return candidate;
|
|
364
|
+
}
|
|
365
|
+
nextIndex += 1;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function createUniqueRelationshipId(
|
|
370
|
+
existingIds: Set<string>,
|
|
371
|
+
preferredBase: string,
|
|
372
|
+
): string {
|
|
373
|
+
if (!existingIds.has(preferredBase)) {
|
|
374
|
+
return preferredBase;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let nextIndex = 1;
|
|
378
|
+
while (existingIds.has(`${preferredBase}${nextIndex}`)) {
|
|
379
|
+
nextIndex += 1;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return `${preferredBase}${nextIndex}`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function getDirectoryPath(partPath: string): string {
|
|
386
|
+
const normalized = normalizePartPath(partPath);
|
|
387
|
+
const slashIndex = normalized.lastIndexOf("/");
|
|
388
|
+
return slashIndex <= 0 ? "/" : normalized.slice(0, slashIndex);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function toRelationshipTarget(
|
|
392
|
+
sourcePartPath: string,
|
|
393
|
+
targetPartPath: string,
|
|
394
|
+
): string {
|
|
395
|
+
const sourceDirectorySegments = getDirectoryPath(sourcePartPath)
|
|
396
|
+
.split("/")
|
|
397
|
+
.filter(Boolean);
|
|
398
|
+
const targetSegments = normalizePartPath(targetPartPath)
|
|
399
|
+
.split("/")
|
|
400
|
+
.filter(Boolean);
|
|
401
|
+
|
|
402
|
+
let sharedIndex = 0;
|
|
403
|
+
while (
|
|
404
|
+
sharedIndex < sourceDirectorySegments.length &&
|
|
405
|
+
sharedIndex < targetSegments.length &&
|
|
406
|
+
sourceDirectorySegments[sharedIndex] === targetSegments[sharedIndex]
|
|
407
|
+
) {
|
|
408
|
+
sharedIndex += 1;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const upSegments = sourceDirectorySegments
|
|
412
|
+
.slice(sharedIndex)
|
|
413
|
+
.map(() => "..");
|
|
414
|
+
const downSegments = targetSegments.slice(sharedIndex);
|
|
415
|
+
return [...upSegments, ...downSegments].join("/");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function cloneRelationship(relationship: OpcRelationship): OpcRelationship {
|
|
419
|
+
return { ...relationship };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function escapeXml(value: string): string {
|
|
423
|
+
return value
|
|
424
|
+
.replace(/&/g, "&")
|
|
425
|
+
.replace(/"/g, """)
|
|
426
|
+
.replace(/</g, "<")
|
|
427
|
+
.replace(/>/g, ">")
|
|
428
|
+
.replace(/'/g, "'");
|
|
429
|
+
}
|