@ansonlai/docx-redline-js 0.6.2 → 0.7.1

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.
@@ -9,15 +9,23 @@ import {
9
9
  normalizeTargetDescriptor,
10
10
  validateDocumentOperation
11
11
  } from './document-operation-contract.js';
12
+ import { compileExactReplacements } from './localized-replacement-compiler.js';
12
13
 
13
14
  const TEXT_WRITE_KINDS = new Set(['redline', 'restore', 'rejected-insert']);
14
15
  const FORMAT_WRITE_KINDS = new Set(['highlight', 'format', 'paragraph-format']);
15
16
 
16
17
  function normalizedError(error) {
18
+ const details = {};
19
+ if (error && typeof error === 'object') {
20
+ for (const [key, value] of Object.entries(error)) {
21
+ if (key === 'name' || key === 'stack' || key === 'message' || key === 'code') continue;
22
+ details[key] = value;
23
+ }
24
+ }
17
25
  return {
18
26
  code: typeof error?.code === 'string' && error.code ? error.code : 'OPERATION_ERROR',
19
27
  message: error?.message || String(error),
20
- ...(Array.isArray(error?.candidates) ? { candidates: error.candidates } : {})
28
+ ...details
21
29
  };
22
30
  }
23
31
 
@@ -266,6 +274,39 @@ export function compileOperationBatch(xmlDoc, operations = [], options = {}) {
266
274
  compiled._compiledResolvedBy = start.resolvedBy;
267
275
  if (binding.warnings.length > 0) compiled._compiledWarnings = binding.warnings;
268
276
 
277
+ if (Array.isArray(operation.replacements)) {
278
+ const patchCompilation = compileExactReplacements(
279
+ start.metadata.text,
280
+ operation.replacements
281
+ );
282
+ if (!patchCompilation.ok) {
283
+ throw Object.assign(new Error(patchCompilation.error.message), patchCompilation.error);
284
+ }
285
+ compiled.modified = patchCompilation.desiredText;
286
+ delete compiled.replacements;
287
+ compiled._localizedReplacementCompilation = {
288
+ sourceText: start.metadata.text,
289
+ desiredText: patchCompilation.desiredText,
290
+ replacements: patchCompilation.replacements
291
+ };
292
+ const canonicalValidation = validateDocumentOperation(compiled);
293
+ if (!canonicalValidation.valid) {
294
+ throw Object.assign(
295
+ new Error(canonicalValidation.error.message),
296
+ canonicalValidation.error
297
+ );
298
+ }
299
+ } else if (operation.operationKind === 'restore' && sourceOperation.modified === undefined) {
300
+ compiled.modified = start.metadata.text;
301
+ const canonicalValidation = validateDocumentOperation(compiled);
302
+ if (!canonicalValidation.valid) {
303
+ throw Object.assign(
304
+ new Error(canonicalValidation.error.message),
305
+ canonicalValidation.error
306
+ );
307
+ }
308
+ }
309
+
269
310
  const targetEndDescriptor = operation.targetEndDescriptor
270
311
  || (operation.targetEndRef != null
271
312
  ? normalizeTargetDescriptor(null, operation.targetEndRef, operation.targetDescriptor.revisionView)
@@ -136,11 +136,16 @@ export function preflightOperations(documentXml, operations, author, options = {
136
136
  let commentsRequired = false;
137
137
  let numberingRequired = false;
138
138
 
139
- for (let index = 0; index < sourceOperations.length; index++) {
140
- const sourceOperation = sourceOperations[index];
141
- const validation = validateDocumentOperation(sourceOperation);
142
- const fallbackOperation = normalizeDocumentOperation(sourceOperation);
143
- const operation = validation.operation || fallbackOperation;
139
+ for (let index = 0; index < sourceOperations.length; index++) {
140
+ const sourceOperation = sourceOperations[index];
141
+ const compiledBinding = batchCompilation.bindings[index];
142
+ const compiledOperation = batchCompilation.compiledOperations[index];
143
+ const operationInput = !compiledBinding?.error && compiledOperation?._localizedReplacementCompilation
144
+ ? compiledOperation
145
+ : sourceOperation;
146
+ const validation = validateDocumentOperation(operationInput);
147
+ const fallbackOperation = normalizeDocumentOperation(sourceOperation);
148
+ const operation = validation.operation || fallbackOperation;
144
149
  const authorUsed = resolveDocumentOperationAuthor(operation, author, getDefaultAuthor());
145
150
  authorsUsed.add(authorUsed);
146
151
 
@@ -187,7 +192,6 @@ export function preflightOperations(documentXml, operations, author, options = {
187
192
  continue;
188
193
  }
189
194
 
190
- const compiledBinding = batchCompilation.bindings[index];
191
195
  if (compiledBinding?.error) {
192
196
  results.push({
193
197
  index: index + 1,
@@ -31,6 +31,12 @@ export interface RejectedTextInsertionAnchor {
31
31
  offset: number;
32
32
  }
33
33
 
34
+ export interface LocalizedReplacement {
35
+ find: string;
36
+ replace: string;
37
+ occurrence?: number;
38
+ }
39
+
34
40
  export interface DocumentOperationBase {
35
41
  operationId?: string;
36
42
  captureKey?: string;
@@ -43,9 +49,7 @@ export interface DocumentOperationBase {
43
49
  insertionAffinity?: InsertionAffinity;
44
50
  }
45
51
 
46
- export interface RedlineDocumentOperation extends DocumentOperationBase {
47
- type: 'redline' | 'replace' | 'format' | 'list-change' | 'table-reconciliation' | 'insert';
48
- modified: string;
52
+ interface RedlineDocumentOperationCommon extends DocumentOperationBase {
49
53
  structuredContent?: boolean;
50
54
  targetEnd?: ParagraphTargetDescriptor;
51
55
  targetEndRef?: number | string | null;
@@ -53,13 +57,50 @@ export interface RedlineDocumentOperation extends DocumentOperationBase {
53
57
  anchor?: RejectedTextInsertionAnchor;
54
58
  }
55
59
 
56
- export interface RestoreDocumentOperation extends DocumentOperationBase {
60
+ export type RedlineDocumentOperation =
61
+ | (RedlineDocumentOperationCommon & {
62
+ type: 'redline' | 'replace';
63
+ modified: string;
64
+ replacements?: never;
65
+ })
66
+ | (RedlineDocumentOperationCommon & {
67
+ type: 'redline' | 'replace';
68
+ modified?: never;
69
+ /** Exact, case-sensitive replacements in one batch-start accepted-view paragraph. */
70
+ replacements: LocalizedReplacement[];
71
+ })
72
+ | (RedlineDocumentOperationCommon & {
73
+ type: 'format' | 'list-change' | 'table-reconciliation' | 'insert';
74
+ modified: string;
75
+ replacements?: never;
76
+ });
77
+
78
+ interface RestoreDocumentOperationCommon extends DocumentOperationBase {
57
79
  type: 'restore';
58
- modified: string | string[];
59
- targetEnd?: ParagraphTargetDescriptor;
60
- targetEndRef?: number | string | null;
61
80
  }
62
81
 
82
+ export type RestoreDocumentOperation =
83
+ | (RestoreDocumentOperationCommon & {
84
+ modified: string | string[];
85
+ replacements?: never;
86
+ targetEnd?: ParagraphTargetDescriptor;
87
+ targetEndRef?: number | string | null;
88
+ })
89
+ | (RestoreDocumentOperationCommon & {
90
+ modified?: never;
91
+ /** Exact, case-sensitive replacements in one strong rejected-view paragraph. */
92
+ replacements: LocalizedReplacement[];
93
+ targetEnd?: never;
94
+ targetEndRef?: never;
95
+ })
96
+ | (RestoreDocumentOperationCommon & {
97
+ /** Omit modified to restore one strong rejected-view paragraph verbatim. */
98
+ modified?: never;
99
+ replacements?: never;
100
+ targetEnd?: never;
101
+ targetEndRef?: never;
102
+ });
103
+
63
104
  export interface DeleteDocumentOperation extends DocumentOperationBase {
64
105
  type: 'delete';
65
106
  modified?: '';
@@ -133,6 +174,7 @@ export interface ResolvedDocumentTarget {
133
174
  paragraphId: string | null;
134
175
  text: string;
135
176
  fingerprint?: string;
177
+ revisionView?: 'accepted' | 'rejected';
136
178
  inTable?: boolean;
137
179
  targetTextMatch?: {
138
180
  mode: 'exact' | 'space_equivalent' | 'normalized';
@@ -190,6 +232,34 @@ export interface MutationReceipt {
190
232
  warnings: string[];
191
233
  }
192
234
 
235
+ export interface LocalizedReplacementChange {
236
+ kind: 'localized_replacement';
237
+ committed: boolean;
238
+ finalDisposition: 'applied' | 'rolled_back';
239
+ target: {
240
+ paragraphId?: string;
241
+ index?: number;
242
+ fingerprint?: string;
243
+ humanReference?: string;
244
+ };
245
+ context?: {
246
+ search?: string;
247
+ range?: string;
248
+ anchorMatchCount?: number;
249
+ humanReference?: string;
250
+ };
251
+ replacements: Array<{
252
+ find: string;
253
+ replace: string;
254
+ occurrence?: number;
255
+ beforeExcerpt: string;
256
+ afterExcerpt: string;
257
+ }>;
258
+ verification: {
259
+ acceptedViewMatchesCompiledText: boolean;
260
+ };
261
+ }
262
+
193
263
  export interface DocumentOperationResult {
194
264
  documentXml: string;
195
265
  hasChanges: boolean;
@@ -207,6 +277,7 @@ export interface DocumentOperationResult {
207
277
  resolvedTarget?: ResolvedDocumentTarget;
208
278
  resolvedAnchor?: ResolvedCommentAnchor;
209
279
  receipt?: MutationReceipt;
280
+ change?: LocalizedReplacementChange;
210
281
  }
211
282
 
212
283
  export interface BatchOperationItemResult {
@@ -221,6 +292,7 @@ export interface BatchOperationItemResult {
221
292
  warnings?: string[];
222
293
  error?: RedlineError;
223
294
  receipt?: MutationReceipt;
295
+ change?: LocalizedReplacementChange;
224
296
  }
225
297
 
226
298
  export interface DocumentOperationBatchResult {