@ansonlai/docx-redline-js 0.7.2 → 0.8.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.
- package/AGENTS.md +15 -10
- package/ARCHITECTURE.md +30 -10
- package/CHANGELOG.md +539 -431
- package/README.md +331 -309
- package/adapters/xml-adapter.js +84 -56
- package/core/sha256.js +120 -0
- package/dist/docx-redline-js.esm.js +8982 -693
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +102 -89
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +28305 -0
- package/dist/docx-redline.bundle.cjs.map +7 -0
- package/dist/docx-redline.bundle.js +28296 -0
- package/dist/docx-redline.bundle.js.map +7 -0
- package/docs/AGENT_FAST_START.md +2 -1
- package/docs/AGENT_KNOWLEDGE_BASE.md +480 -393
- package/docs/SKILL_AUTHORING.md +5 -2
- package/docs/TESTING.md +2 -1
- package/docs/schemas/document-operations.schema.json +29 -0
- package/document/docx-document.d.ts +39 -0
- package/document/docx-document.js +1010 -0
- package/document/zip-archive.js +258 -0
- package/engine/reconstruction-writer.js +18 -0
- package/index.d.ts +598 -531
- package/index.js +349 -340
- package/node/cli-help.js +5 -5
- package/node/cli.js +760 -754
- package/node/docx-document.js +11 -355
- package/node/index.d.ts +1 -1
- package/node/zip-archive.js +28 -38
- package/package.json +14 -11
- package/scripts/build.mjs +40 -5
- package/scripts/generate-word-comment-edge-case-fixture.ps1 +58 -0
- package/scripts/generate-word-comment-thread-fixture.ps1 +36 -0
- package/scripts/generate-word-header-footer-fixture.ps1 +32 -0
- package/scripts/generate-word-multi-section-fixture.ps1 +27 -0
- package/scripts/generate-word-package-integrity-cases.mjs +141 -0
- package/scripts/generate-word-thread-state-fixtures.ps1 +50 -0
- package/scripts/word-com-package-integrity.ps1 +98 -0
- package/services/batch-operation-orchestrator.js +262 -259
- package/services/comment-replies.js +96 -95
- package/services/comment-thread-parts.js +280 -0
- package/services/document-inspection.js +366 -365
- package/services/document-operation-applier.js +230 -202
- package/services/document-operation-contract.js +17 -2
- package/services/error-recovery.js +6 -0
- package/services/headers-footers.js +206 -0
- package/services/operation-batch-compiler.js +1 -0
- package/services/operation-preflight.js +231 -218
- package/services/package-parts.js +80 -0
- package/services/revision-token.js +11 -7
- package/services/standalone-docx-plumbing.js +631 -569
- package/services/standalone-operation-runner.d.ts +19 -3
package/index.js
CHANGED
|
@@ -1,340 +1,349 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standalone reconciliation entrypoint (no Word JS API dependencies).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// Adapters
|
|
6
|
-
export { configureXmlProvider, parseOoxmlSafe } from './adapters/xml-adapter.js';
|
|
7
|
-
export { configureLogger } from './adapters/logger.js';
|
|
8
|
-
export { setDefaultAuthor, getDefaultAuthor, setPlatform, getPlatform } from './adapters/config.js';
|
|
9
|
-
|
|
10
|
-
// Engine
|
|
11
|
-
import {
|
|
12
|
-
applyRedlineToOxml as applyRedlineToOxmlEngine,
|
|
13
|
-
sanitizeAiResponse,
|
|
14
|
-
parseOoxml,
|
|
15
|
-
serializeOoxml
|
|
16
|
-
} from './engine/oxml-engine.js';
|
|
17
|
-
import { parseTable as parseMarkdownTable } from './pipeline/pipeline.js';
|
|
18
|
-
import { wrapInDocumentFragment as wrapInDocumentFragmentShared } from './pipeline/serialization.js';
|
|
19
|
-
import {
|
|
20
|
-
buildSingleLineListStructuralFallbackPlan,
|
|
21
|
-
executeSingleLineListStructuralFallback
|
|
22
|
-
} from './orchestration/list-structural-fallback.js';
|
|
23
|
-
import { withOoxmlSourceType } from './core/word-xml.js';
|
|
24
|
-
export { containsTrackedChanges, getTrackedChangeAuthors } from './core/word-xml.js';
|
|
25
|
-
export { validateRedlineOoxml } from './core/redline-validation.js';
|
|
26
|
-
export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/structured-content.js';
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Standalone-safe redline wrapper.
|
|
30
|
-
*
|
|
31
|
-
* In non-Word runtimes, the engine can return `{ useNativeApi: true, hasChanges: true }`
|
|
32
|
-
* without an OOXML payload for some format-only operations. Standalone callers cannot
|
|
33
|
-
* complete that native fallback path, so normalize to a no-op with warnings.
|
|
34
|
-
*/
|
|
35
|
-
export async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
|
|
36
|
-
const result = await applyRedlineToOxmlEngine(oxml, originalText, modifiedText, options);
|
|
37
|
-
if (result?.useNativeApi && typeof result?.oxml !== 'string') {
|
|
38
|
-
const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
39
|
-
return withOoxmlSourceType({
|
|
40
|
-
...result,
|
|
41
|
-
oxml,
|
|
42
|
-
hasChanges: false,
|
|
43
|
-
warnings: [
|
|
44
|
-
...existingWarnings,
|
|
45
|
-
'Standalone mode cannot execute native Word API fallback for this operation.'
|
|
46
|
-
]
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Reconciles a Markdown table against an OOXML scope.
|
|
54
|
-
*
|
|
55
|
-
* This centralizes table-specific validation + reconciliation so Word add-in
|
|
56
|
-
* and browser modules can share the same entrypoint.
|
|
57
|
-
*
|
|
58
|
-
* @param {string} oxml - OOXML scope to reconcile (paragraph/range/table package)
|
|
59
|
-
* @param {string} originalText - Original visible text in that scope
|
|
60
|
-
* @param {string} markdownTable - Markdown table text
|
|
61
|
-
* @param {Object} [options={}] - Reconciliation options forwarded to applyRedlineToOxml
|
|
62
|
-
* @returns {Promise<{ oxml: string, hasChanges: boolean, warnings?: string[], isMarkdownTable: boolean, tableData?: Object }>}
|
|
63
|
-
*/
|
|
64
|
-
export async function reconcileMarkdownTableOoxml(oxml, originalText, markdownTable, options = {}) {
|
|
65
|
-
const sourceOoxml = typeof oxml === 'string' ? oxml : '';
|
|
66
|
-
const tableText = typeof markdownTable === 'string' ? markdownTable : String(markdownTable || '');
|
|
67
|
-
let tableData;
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
tableData = parseMarkdownTable(tableText);
|
|
71
|
-
} catch {
|
|
72
|
-
tableData = { headers: [], rows: [] };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const hasTableData = (tableData?.headers?.length || 0) > 0 || (tableData?.rows?.length || 0) > 0;
|
|
76
|
-
if (!hasTableData) {
|
|
77
|
-
return {
|
|
78
|
-
oxml: sourceOoxml,
|
|
79
|
-
hasChanges: false,
|
|
80
|
-
isMarkdownTable: false,
|
|
81
|
-
warnings: ['Could not parse Markdown table from input.']
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const result = await applyRedlineToOxml(
|
|
86
|
-
sourceOoxml,
|
|
87
|
-
originalText || '',
|
|
88
|
-
tableText,
|
|
89
|
-
options
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
...result,
|
|
94
|
-
isMarkdownTable: true,
|
|
95
|
-
tableData
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export { resolveParagraphRangeByRefs } from './core/paragraph-targeting.js';
|
|
100
|
-
export { inferTableReplacementParagraphBlock, isLikelyStructuredTableSourceParagraph } from './core/table-targeting.js';
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Applies redline reconciliation, then forces single-line structural list
|
|
104
|
-
* conversion when the redline is a no-op on marker-prefixed list text.
|
|
105
|
-
*
|
|
106
|
-
* This is useful for inputs like `1. HEADER` where text diff is unchanged but
|
|
107
|
-
* OOXML should convert plain text markers into real Word list structure.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} oxml - Original OOXML
|
|
110
|
-
* @param {string} originalText - Original visible text
|
|
111
|
-
* @param {string} modifiedText - Proposed modified text
|
|
112
|
-
* @param {Object} [options={}] - Reconciliation options
|
|
113
|
-
* @param {boolean} [options.listFallbackAllowExistingList=true] - Allow fallback even when paragraph is already list-bound
|
|
114
|
-
* @returns {Promise<{ oxml: string, hasChanges: boolean } & Record<string, any>>}
|
|
115
|
-
*/
|
|
116
|
-
export async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedText, options = {}) {
|
|
117
|
-
const allowExistingListForFallback = options.listFallbackAllowExistingList !== false;
|
|
118
|
-
const fallbackModifiedText = options.sanitizeInput === true ? sanitizeAiResponse(modifiedText) : modifiedText;
|
|
119
|
-
const sanitizationWarnings = fallbackModifiedText !== modifiedText
|
|
120
|
-
? ['Input was sanitized; pass sanitizeInput: false to disable.']
|
|
121
|
-
: [];
|
|
122
|
-
const plan = buildSingleLineListStructuralFallbackPlan({
|
|
123
|
-
oxml,
|
|
124
|
-
originalText,
|
|
125
|
-
modifiedText: fallbackModifiedText,
|
|
126
|
-
allowExistingList: allowExistingListForFallback
|
|
127
|
-
});
|
|
128
|
-
const preferListFallback = options.preferListStructuralFallback !== false;
|
|
129
|
-
let preflightFallbackWarnings = [];
|
|
130
|
-
|
|
131
|
-
if (plan && preferListFallback) {
|
|
132
|
-
const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
|
|
133
|
-
author: options.author,
|
|
134
|
-
generateRedlines: options.generateRedlines,
|
|
135
|
-
pipeline: options.listFallbackPipeline
|
|
136
|
-
});
|
|
137
|
-
if (fallbackResult?.hasChanges && fallbackResult?.oxml) {
|
|
138
|
-
const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
|
|
139
|
-
includeNumbering: fallbackResult.includeNumbering ?? true,
|
|
140
|
-
numberingXml: fallbackResult.numberingXml
|
|
141
|
-
});
|
|
142
|
-
const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
143
|
-
return withOoxmlSourceType({
|
|
144
|
-
oxml: wrappedOxml,
|
|
145
|
-
hasChanges: true,
|
|
146
|
-
warnings: [...sanitizationWarnings, ...fallbackWarnings],
|
|
147
|
-
listStructuralFallbackApplied: true,
|
|
148
|
-
listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
|
|
149
|
-
listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
preflightFallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const baseResult = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
|
|
156
|
-
|
|
157
|
-
if (!plan) {
|
|
158
|
-
return {
|
|
159
|
-
...baseResult,
|
|
160
|
-
warnings: [
|
|
161
|
-
...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
|
|
162
|
-
...preflightFallbackWarnings
|
|
163
|
-
],
|
|
164
|
-
listStructuralFallbackApplied: false
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (preferListFallback) {
|
|
169
|
-
return {
|
|
170
|
-
...baseResult,
|
|
171
|
-
warnings: [
|
|
172
|
-
...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
|
|
173
|
-
...preflightFallbackWarnings
|
|
174
|
-
],
|
|
175
|
-
listStructuralFallbackApplied: false
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (baseResult?.hasChanges) {
|
|
180
|
-
return {
|
|
181
|
-
...baseResult,
|
|
182
|
-
listStructuralFallbackApplied: false
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
|
|
187
|
-
author: options.author,
|
|
188
|
-
generateRedlines: options.generateRedlines,
|
|
189
|
-
pipeline: options.listFallbackPipeline
|
|
190
|
-
});
|
|
191
|
-
if (!fallbackResult?.hasChanges || !fallbackResult?.oxml) {
|
|
192
|
-
const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
|
|
193
|
-
const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
194
|
-
return {
|
|
195
|
-
...baseResult,
|
|
196
|
-
warnings: [...existingWarnings, ...fallbackWarnings],
|
|
197
|
-
listStructuralFallbackApplied: false
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
|
|
202
|
-
includeNumbering: fallbackResult.includeNumbering ?? true,
|
|
203
|
-
numberingXml: fallbackResult.numberingXml
|
|
204
|
-
});
|
|
205
|
-
const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
|
|
206
|
-
const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
207
|
-
|
|
208
|
-
return withOoxmlSourceType({
|
|
209
|
-
...baseResult,
|
|
210
|
-
oxml: wrappedOxml,
|
|
211
|
-
hasChanges: true,
|
|
212
|
-
warnings: [...existingWarnings, ...preflightFallbackWarnings, ...fallbackWarnings],
|
|
213
|
-
listStructuralFallbackApplied: true,
|
|
214
|
-
listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
|
|
215
|
-
listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export { sanitizeAiResponse, parseOoxml, serializeOoxml };
|
|
220
|
-
|
|
221
|
-
export {
|
|
222
|
-
createDynamicNumberingIdState,
|
|
223
|
-
reserveNextNumberingId,
|
|
224
|
-
reserveNextNumberingIdPair,
|
|
225
|
-
overwriteParagraphNumIds,
|
|
226
|
-
extractFirstParagraphNumId,
|
|
227
|
-
buildExplicitDecimalMultilevelNumberingXml,
|
|
228
|
-
remapNumberingPayloadForDocument,
|
|
229
|
-
mergeNumberingXmlBySchemaOrder
|
|
230
|
-
} from './services/numbering-helpers.js';
|
|
231
|
-
|
|
232
|
-
// Pipeline components
|
|
233
|
-
export { ReconciliationPipeline } from './pipeline/pipeline.js';
|
|
234
|
-
export { ingestOoxml } from './pipeline/ingestion.js';
|
|
235
|
-
export {
|
|
236
|
-
ingestWordOoxmlToPlainText,
|
|
237
|
-
ingestWordOoxmlToMarkdown,
|
|
238
|
-
ingestWordOoxmlToPlainTextResult,
|
|
239
|
-
ingestWordOoxmlToMarkdownResult
|
|
240
|
-
} from './pipeline/ingestion-export.js';
|
|
241
|
-
export { extractCanonicalParagraphText, readCanonicalRunText, isNodeVisibleInRevisionView, extractParagraphRevisionSegments } from './core/paragraph-text.js';
|
|
242
|
-
export { inspectDocumentParts } from './services/document-inspection.js';
|
|
243
|
-
export {
|
|
244
|
-
buildRevisionTokenFraming,
|
|
245
|
-
computeRevisionToken,
|
|
246
|
-
computeRevisionTokenSync,
|
|
247
|
-
computeDocumentPartsRevisionToken,
|
|
248
|
-
validateRevisionToken,
|
|
249
|
-
areRevisionTokensEqual,
|
|
250
|
-
normalizeOpcEntryName,
|
|
251
|
-
extractDocumentPartsEntries
|
|
252
|
-
} from './services/revision-token.js';
|
|
253
|
-
export { preprocessMarkdown } from './pipeline/markdown-processor.js';
|
|
254
|
-
export { serializeToOoxml, wrapInDocumentFragment } from './pipeline/serialization.js';
|
|
255
|
-
|
|
256
|
-
// Comment engine
|
|
257
|
-
export {
|
|
258
|
-
injectCommentsIntoOoxml,
|
|
259
|
-
injectCommentsIntoPackage,
|
|
260
|
-
buildCommentElement,
|
|
261
|
-
buildCommentsPartXml
|
|
262
|
-
} from './services/comment-engine.js';
|
|
263
|
-
export { applyCommentReplyToParts } from './services/comment-replies.js';
|
|
264
|
-
export {
|
|
265
|
-
acceptTrackedChangesInOoxml,
|
|
266
|
-
rejectTrackedChangesInOoxml,
|
|
267
|
-
deleteCommentsByAuthorInOoxml
|
|
268
|
-
} from './services/revision-comment-management.js';
|
|
269
|
-
|
|
270
|
-
// Formatting removal utilities
|
|
271
|
-
export {
|
|
272
|
-
removeFormattingFromRPr,
|
|
273
|
-
applyFormattingRemovalToOoxml,
|
|
274
|
-
applyHighlightToOoxml
|
|
275
|
-
} from './engine/formatting-removal.js';
|
|
276
|
-
|
|
277
|
-
// Table/list tools
|
|
278
|
-
export { generateTableOoxml } from './services/table-reconciliation.js';
|
|
279
|
-
export { NumberingService } from './services/numbering-service.js';
|
|
280
|
-
export {
|
|
281
|
-
getBodyElementFromDocument,
|
|
282
|
-
insertBodyElementBeforeSectPr,
|
|
283
|
-
normalizeBodySectionOrderStandalone,
|
|
284
|
-
sanitizeNestedParagraphsInTables,
|
|
285
|
-
getPackagePartName,
|
|
286
|
-
extractReplacementNodesFromOoxml,
|
|
287
|
-
ensureNumberingArtifactsInZip,
|
|
288
|
-
ensureCommentsArtifactsInZip,
|
|
289
|
-
ensureCommentsExtendedArtifactsInZip,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
export {
|
|
294
|
-
export {
|
|
295
|
-
export {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
export {
|
|
308
|
-
export {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
export {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Standalone reconciliation entrypoint (no Word JS API dependencies).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Adapters
|
|
6
|
+
export { configureXmlProvider, parseOoxmlSafe } from './adapters/xml-adapter.js';
|
|
7
|
+
export { configureLogger } from './adapters/logger.js';
|
|
8
|
+
export { setDefaultAuthor, getDefaultAuthor, setPlatform, getPlatform } from './adapters/config.js';
|
|
9
|
+
|
|
10
|
+
// Engine
|
|
11
|
+
import {
|
|
12
|
+
applyRedlineToOxml as applyRedlineToOxmlEngine,
|
|
13
|
+
sanitizeAiResponse,
|
|
14
|
+
parseOoxml,
|
|
15
|
+
serializeOoxml
|
|
16
|
+
} from './engine/oxml-engine.js';
|
|
17
|
+
import { parseTable as parseMarkdownTable } from './pipeline/pipeline.js';
|
|
18
|
+
import { wrapInDocumentFragment as wrapInDocumentFragmentShared } from './pipeline/serialization.js';
|
|
19
|
+
import {
|
|
20
|
+
buildSingleLineListStructuralFallbackPlan,
|
|
21
|
+
executeSingleLineListStructuralFallback
|
|
22
|
+
} from './orchestration/list-structural-fallback.js';
|
|
23
|
+
import { withOoxmlSourceType } from './core/word-xml.js';
|
|
24
|
+
export { containsTrackedChanges, getTrackedChangeAuthors } from './core/word-xml.js';
|
|
25
|
+
export { validateRedlineOoxml } from './core/redline-validation.js';
|
|
26
|
+
export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/structured-content.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Standalone-safe redline wrapper.
|
|
30
|
+
*
|
|
31
|
+
* In non-Word runtimes, the engine can return `{ useNativeApi: true, hasChanges: true }`
|
|
32
|
+
* without an OOXML payload for some format-only operations. Standalone callers cannot
|
|
33
|
+
* complete that native fallback path, so normalize to a no-op with warnings.
|
|
34
|
+
*/
|
|
35
|
+
export async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
|
|
36
|
+
const result = await applyRedlineToOxmlEngine(oxml, originalText, modifiedText, options);
|
|
37
|
+
if (result?.useNativeApi && typeof result?.oxml !== 'string') {
|
|
38
|
+
const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
39
|
+
return withOoxmlSourceType({
|
|
40
|
+
...result,
|
|
41
|
+
oxml,
|
|
42
|
+
hasChanges: false,
|
|
43
|
+
warnings: [
|
|
44
|
+
...existingWarnings,
|
|
45
|
+
'Standalone mode cannot execute native Word API fallback for this operation.'
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Reconciles a Markdown table against an OOXML scope.
|
|
54
|
+
*
|
|
55
|
+
* This centralizes table-specific validation + reconciliation so Word add-in
|
|
56
|
+
* and browser modules can share the same entrypoint.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} oxml - OOXML scope to reconcile (paragraph/range/table package)
|
|
59
|
+
* @param {string} originalText - Original visible text in that scope
|
|
60
|
+
* @param {string} markdownTable - Markdown table text
|
|
61
|
+
* @param {Object} [options={}] - Reconciliation options forwarded to applyRedlineToOxml
|
|
62
|
+
* @returns {Promise<{ oxml: string, hasChanges: boolean, warnings?: string[], isMarkdownTable: boolean, tableData?: Object }>}
|
|
63
|
+
*/
|
|
64
|
+
export async function reconcileMarkdownTableOoxml(oxml, originalText, markdownTable, options = {}) {
|
|
65
|
+
const sourceOoxml = typeof oxml === 'string' ? oxml : '';
|
|
66
|
+
const tableText = typeof markdownTable === 'string' ? markdownTable : String(markdownTable || '');
|
|
67
|
+
let tableData;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
tableData = parseMarkdownTable(tableText);
|
|
71
|
+
} catch {
|
|
72
|
+
tableData = { headers: [], rows: [] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const hasTableData = (tableData?.headers?.length || 0) > 0 || (tableData?.rows?.length || 0) > 0;
|
|
76
|
+
if (!hasTableData) {
|
|
77
|
+
return {
|
|
78
|
+
oxml: sourceOoxml,
|
|
79
|
+
hasChanges: false,
|
|
80
|
+
isMarkdownTable: false,
|
|
81
|
+
warnings: ['Could not parse Markdown table from input.']
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await applyRedlineToOxml(
|
|
86
|
+
sourceOoxml,
|
|
87
|
+
originalText || '',
|
|
88
|
+
tableText,
|
|
89
|
+
options
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
...result,
|
|
94
|
+
isMarkdownTable: true,
|
|
95
|
+
tableData
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { resolveParagraphRangeByRefs } from './core/paragraph-targeting.js';
|
|
100
|
+
export { inferTableReplacementParagraphBlock, isLikelyStructuredTableSourceParagraph } from './core/table-targeting.js';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Applies redline reconciliation, then forces single-line structural list
|
|
104
|
+
* conversion when the redline is a no-op on marker-prefixed list text.
|
|
105
|
+
*
|
|
106
|
+
* This is useful for inputs like `1. HEADER` where text diff is unchanged but
|
|
107
|
+
* OOXML should convert plain text markers into real Word list structure.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} oxml - Original OOXML
|
|
110
|
+
* @param {string} originalText - Original visible text
|
|
111
|
+
* @param {string} modifiedText - Proposed modified text
|
|
112
|
+
* @param {Object} [options={}] - Reconciliation options
|
|
113
|
+
* @param {boolean} [options.listFallbackAllowExistingList=true] - Allow fallback even when paragraph is already list-bound
|
|
114
|
+
* @returns {Promise<{ oxml: string, hasChanges: boolean } & Record<string, any>>}
|
|
115
|
+
*/
|
|
116
|
+
export async function applyRedlineToOxmlWithListFallback(oxml, originalText, modifiedText, options = {}) {
|
|
117
|
+
const allowExistingListForFallback = options.listFallbackAllowExistingList !== false;
|
|
118
|
+
const fallbackModifiedText = options.sanitizeInput === true ? sanitizeAiResponse(modifiedText) : modifiedText;
|
|
119
|
+
const sanitizationWarnings = fallbackModifiedText !== modifiedText
|
|
120
|
+
? ['Input was sanitized; pass sanitizeInput: false to disable.']
|
|
121
|
+
: [];
|
|
122
|
+
const plan = buildSingleLineListStructuralFallbackPlan({
|
|
123
|
+
oxml,
|
|
124
|
+
originalText,
|
|
125
|
+
modifiedText: fallbackModifiedText,
|
|
126
|
+
allowExistingList: allowExistingListForFallback
|
|
127
|
+
});
|
|
128
|
+
const preferListFallback = options.preferListStructuralFallback !== false;
|
|
129
|
+
let preflightFallbackWarnings = [];
|
|
130
|
+
|
|
131
|
+
if (plan && preferListFallback) {
|
|
132
|
+
const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
|
|
133
|
+
author: options.author,
|
|
134
|
+
generateRedlines: options.generateRedlines,
|
|
135
|
+
pipeline: options.listFallbackPipeline
|
|
136
|
+
});
|
|
137
|
+
if (fallbackResult?.hasChanges && fallbackResult?.oxml) {
|
|
138
|
+
const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
|
|
139
|
+
includeNumbering: fallbackResult.includeNumbering ?? true,
|
|
140
|
+
numberingXml: fallbackResult.numberingXml
|
|
141
|
+
});
|
|
142
|
+
const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
143
|
+
return withOoxmlSourceType({
|
|
144
|
+
oxml: wrappedOxml,
|
|
145
|
+
hasChanges: true,
|
|
146
|
+
warnings: [...sanitizationWarnings, ...fallbackWarnings],
|
|
147
|
+
listStructuralFallbackApplied: true,
|
|
148
|
+
listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
|
|
149
|
+
listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
preflightFallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const baseResult = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
|
|
156
|
+
|
|
157
|
+
if (!plan) {
|
|
158
|
+
return {
|
|
159
|
+
...baseResult,
|
|
160
|
+
warnings: [
|
|
161
|
+
...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
|
|
162
|
+
...preflightFallbackWarnings
|
|
163
|
+
],
|
|
164
|
+
listStructuralFallbackApplied: false
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (preferListFallback) {
|
|
169
|
+
return {
|
|
170
|
+
...baseResult,
|
|
171
|
+
warnings: [
|
|
172
|
+
...(Array.isArray(baseResult?.warnings) ? baseResult.warnings : []),
|
|
173
|
+
...preflightFallbackWarnings
|
|
174
|
+
],
|
|
175
|
+
listStructuralFallbackApplied: false
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (baseResult?.hasChanges) {
|
|
180
|
+
return {
|
|
181
|
+
...baseResult,
|
|
182
|
+
listStructuralFallbackApplied: false
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const fallbackResult = await executeSingleLineListStructuralFallback(plan, {
|
|
187
|
+
author: options.author,
|
|
188
|
+
generateRedlines: options.generateRedlines,
|
|
189
|
+
pipeline: options.listFallbackPipeline
|
|
190
|
+
});
|
|
191
|
+
if (!fallbackResult?.hasChanges || !fallbackResult?.oxml) {
|
|
192
|
+
const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
|
|
193
|
+
const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
194
|
+
return {
|
|
195
|
+
...baseResult,
|
|
196
|
+
warnings: [...existingWarnings, ...fallbackWarnings],
|
|
197
|
+
listStructuralFallbackApplied: false
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const wrappedOxml = wrapInDocumentFragmentShared(fallbackResult.oxml, {
|
|
202
|
+
includeNumbering: fallbackResult.includeNumbering ?? true,
|
|
203
|
+
numberingXml: fallbackResult.numberingXml
|
|
204
|
+
});
|
|
205
|
+
const existingWarnings = Array.isArray(baseResult?.warnings) ? baseResult.warnings : [];
|
|
206
|
+
const fallbackWarnings = Array.isArray(fallbackResult?.warnings) ? fallbackResult.warnings : [];
|
|
207
|
+
|
|
208
|
+
return withOoxmlSourceType({
|
|
209
|
+
...baseResult,
|
|
210
|
+
oxml: wrappedOxml,
|
|
211
|
+
hasChanges: true,
|
|
212
|
+
warnings: [...existingWarnings, ...preflightFallbackWarnings, ...fallbackWarnings],
|
|
213
|
+
listStructuralFallbackApplied: true,
|
|
214
|
+
listStructuralFallbackKey: fallbackResult.listStructuralFallbackKey || null,
|
|
215
|
+
listStructuralFallbackNumberingXml: fallbackResult.numberingXml || null
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export { sanitizeAiResponse, parseOoxml, serializeOoxml };
|
|
220
|
+
|
|
221
|
+
export {
|
|
222
|
+
createDynamicNumberingIdState,
|
|
223
|
+
reserveNextNumberingId,
|
|
224
|
+
reserveNextNumberingIdPair,
|
|
225
|
+
overwriteParagraphNumIds,
|
|
226
|
+
extractFirstParagraphNumId,
|
|
227
|
+
buildExplicitDecimalMultilevelNumberingXml,
|
|
228
|
+
remapNumberingPayloadForDocument,
|
|
229
|
+
mergeNumberingXmlBySchemaOrder
|
|
230
|
+
} from './services/numbering-helpers.js';
|
|
231
|
+
|
|
232
|
+
// Pipeline components
|
|
233
|
+
export { ReconciliationPipeline } from './pipeline/pipeline.js';
|
|
234
|
+
export { ingestOoxml } from './pipeline/ingestion.js';
|
|
235
|
+
export {
|
|
236
|
+
ingestWordOoxmlToPlainText,
|
|
237
|
+
ingestWordOoxmlToMarkdown,
|
|
238
|
+
ingestWordOoxmlToPlainTextResult,
|
|
239
|
+
ingestWordOoxmlToMarkdownResult
|
|
240
|
+
} from './pipeline/ingestion-export.js';
|
|
241
|
+
export { extractCanonicalParagraphText, readCanonicalRunText, isNodeVisibleInRevisionView, extractParagraphRevisionSegments } from './core/paragraph-text.js';
|
|
242
|
+
export { inspectDocumentParts } from './services/document-inspection.js';
|
|
243
|
+
export {
|
|
244
|
+
buildRevisionTokenFraming,
|
|
245
|
+
computeRevisionToken,
|
|
246
|
+
computeRevisionTokenSync,
|
|
247
|
+
computeDocumentPartsRevisionToken,
|
|
248
|
+
validateRevisionToken,
|
|
249
|
+
areRevisionTokensEqual,
|
|
250
|
+
normalizeOpcEntryName,
|
|
251
|
+
extractDocumentPartsEntries
|
|
252
|
+
} from './services/revision-token.js';
|
|
253
|
+
export { preprocessMarkdown } from './pipeline/markdown-processor.js';
|
|
254
|
+
export { serializeToOoxml, wrapInDocumentFragment } from './pipeline/serialization.js';
|
|
255
|
+
|
|
256
|
+
// Comment engine
|
|
257
|
+
export {
|
|
258
|
+
injectCommentsIntoOoxml,
|
|
259
|
+
injectCommentsIntoPackage,
|
|
260
|
+
buildCommentElement,
|
|
261
|
+
buildCommentsPartXml
|
|
262
|
+
} from './services/comment-engine.js';
|
|
263
|
+
export { applyCommentReplyToParts } from './services/comment-replies.js';
|
|
264
|
+
export {
|
|
265
|
+
acceptTrackedChangesInOoxml,
|
|
266
|
+
rejectTrackedChangesInOoxml,
|
|
267
|
+
deleteCommentsByAuthorInOoxml
|
|
268
|
+
} from './services/revision-comment-management.js';
|
|
269
|
+
|
|
270
|
+
// Formatting removal utilities
|
|
271
|
+
export {
|
|
272
|
+
removeFormattingFromRPr,
|
|
273
|
+
applyFormattingRemovalToOoxml,
|
|
274
|
+
applyHighlightToOoxml
|
|
275
|
+
} from './engine/formatting-removal.js';
|
|
276
|
+
|
|
277
|
+
// Table/list tools
|
|
278
|
+
export { generateTableOoxml } from './services/table-reconciliation.js';
|
|
279
|
+
export { NumberingService } from './services/numbering-service.js';
|
|
280
|
+
export {
|
|
281
|
+
getBodyElementFromDocument,
|
|
282
|
+
insertBodyElementBeforeSectPr,
|
|
283
|
+
normalizeBodySectionOrderStandalone,
|
|
284
|
+
sanitizeNestedParagraphsInTables,
|
|
285
|
+
getPackagePartName,
|
|
286
|
+
extractReplacementNodesFromOoxml,
|
|
287
|
+
ensureNumberingArtifactsInZip,
|
|
288
|
+
ensureCommentsArtifactsInZip,
|
|
289
|
+
ensureCommentsExtendedArtifactsInZip,
|
|
290
|
+
repairKnownContentTypes,
|
|
291
|
+
validateDocxPackage
|
|
292
|
+
} from './services/standalone-docx-plumbing.js';
|
|
293
|
+
export { buildReconciliationPlan, RoutePlanKind, normalizeContentEscapesForRouting } from './orchestration/route-plan.js';
|
|
294
|
+
export { parseMarkdownListContent, hasListItems } from './orchestration/list-parsing.js';
|
|
295
|
+
export { buildListMarkdown, inferNumberingStyleFromMarker, normalizeListItemsWithLevels } from './orchestration/list-markdown.js';
|
|
296
|
+
export {
|
|
297
|
+
buildSingleLineListStructuralFallbackPlan,
|
|
298
|
+
executeSingleLineListStructuralFallback,
|
|
299
|
+
resolveSingleLineListFallbackNumberingAction,
|
|
300
|
+
recordSingleLineListFallbackExplicitSequence,
|
|
301
|
+
clearSingleLineListFallbackExplicitSequence,
|
|
302
|
+
enforceListBindingOnParagraphNodes,
|
|
303
|
+
stripSingleLineListMarkerPrefix
|
|
304
|
+
} from './orchestration/list-structural-fallback.js';
|
|
305
|
+
|
|
306
|
+
// Core types/constants
|
|
307
|
+
export { DiffOp, RunKind, ContainerKind, ContentType, NS_W, escapeXml } from './core/types.js';
|
|
308
|
+
export { extractParagraphIdFromOoxml } from './core/ooxml-identifiers.js';
|
|
309
|
+
export {
|
|
310
|
+
WORD_MAIN_NS,
|
|
311
|
+
getParagraphText,
|
|
312
|
+
getParagraphId,
|
|
313
|
+
createParagraphFingerprint,
|
|
314
|
+
getDocumentParagraphNodes,
|
|
315
|
+
normalizeWhitespaceForTargeting,
|
|
316
|
+
isMarkdownTableText,
|
|
317
|
+
parseParagraphReference,
|
|
318
|
+
stripLeadingParagraphMarker,
|
|
319
|
+
splitLeadingParagraphMarker,
|
|
320
|
+
findContainingWordElement,
|
|
321
|
+
findParagraphByReference,
|
|
322
|
+
findParagraphByStrictText,
|
|
323
|
+
findParagraphByBestTextMatch,
|
|
324
|
+
findStrictTargetCandidates,
|
|
325
|
+
resolveTargetParagraph,
|
|
326
|
+
buildTargetReferenceSnapshot,
|
|
327
|
+
resolveTargetParagraphWithSnapshot
|
|
328
|
+
} from './core/paragraph-targeting.js';
|
|
329
|
+
export { synthesizeTableMarkdownFromMultilineCellEdit } from './core/table-targeting.js';
|
|
330
|
+
export {
|
|
331
|
+
getParagraphListInfo,
|
|
332
|
+
collectContiguousListParagraphBlock,
|
|
333
|
+
synthesizeExpandedListScopeEdit,
|
|
334
|
+
planListInsertionOnlyEdit,
|
|
335
|
+
stripRedundantLeadingListMarkers
|
|
336
|
+
} from './core/list-targeting.js';
|
|
337
|
+
export {
|
|
338
|
+
ReceiptCollector,
|
|
339
|
+
createEmptyReceipt,
|
|
340
|
+
reconcileReceiptsAgainstOutput
|
|
341
|
+
} from './services/receipt-collector.js';
|
|
342
|
+
|
|
343
|
+
// Universal Document Facade
|
|
344
|
+
export {
|
|
345
|
+
DocxDocument,
|
|
346
|
+
openDocx,
|
|
347
|
+
computePackageRevisionToken
|
|
348
|
+
} from './document/docx-document.js';
|
|
349
|
+
|