@ansonlai/docx-redline-js 0.8.3 → 0.9.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/ARCHITECTURE.md +221 -12
- package/CHANGELOG.md +803 -586
- package/README.md +1018 -901
- package/core/current-properties.js +136 -0
- package/core/paragraph-text.js +211 -209
- package/core/types.js +9 -3
- package/core/word-revision-semantics.js +256 -0
- package/dist/docx-redline-js.esm.js +4551 -967
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +112 -106
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +4690 -1106
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +4690 -1106
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_FAST_START.md +2 -0
- package/docs/AGENT_KNOWLEDGE_BASE.md +23 -4
- package/docs/SKILL_AUTHORING.md +165 -150
- package/docs/TESTING.md +1142 -1059
- package/docs/schemas/document-operations.schema.json +221 -168
- package/document/docx-document.js +1022 -1010
- package/engine/oxml-engine.js +567 -560
- package/engine/paragraph-mark-revisions.js +292 -0
- package/engine/reconstruction-mapper.js +312 -311
- package/engine/reconstruction-mode.js +183 -183
- package/engine/reconstruction-writer.js +67 -10
- package/engine/rpr-helpers.js +42 -0
- package/engine/run-builders.js +0 -73
- package/engine/surgical-diff-application.js +604 -605
- package/engine/table-mode.js +63 -63
- package/index.d.ts +51 -1
- package/index.js +47 -3
- package/node/cli-help.js +1 -0
- package/node/cli.js +992 -974
- package/orchestration/list-structural-fallback.js +67 -9
- package/package.json +1 -1
- package/pipeline/diff-engine.js +130 -1
- package/pipeline/ingestion-export.js +227 -243
- package/pipeline/ingestion-paragraph.js +93 -98
- package/pipeline/list-generation.js +415 -419
- package/pipeline/pipeline.js +206 -204
- package/pipeline/serialization.js +111 -94
- package/scripts/export-validation-fixtures.mjs +19 -6
- package/scripts/generate-word-revision-semantics-matrix.mjs +134 -0
- package/scripts/lib/word-coverage-catalogue.mjs +7 -2
- package/scripts/render-multilevel-bullet-visual.ps1 +2 -2
- package/scripts/word-com-corpus-visual-suite.ps1 +2 -2
- package/scripts/word-com-differential.ps1 +443 -335
- package/scripts/word-com-visual-suite.ps1 +2 -2
- package/scripts/word-revision-semantics-matrix.ps1 +108 -0
- package/services/batch-operation-orchestrator.js +26 -0
- package/services/capture-engine.js +228 -226
- package/services/document-operation-applier.js +215 -0
- package/services/document-operation-contract.js +628 -537
- package/services/document-operation-mutations.js +789 -46
- package/services/error-recovery.js +17 -0
- package/services/list-conversion-diagnostics.js +296 -0
- package/services/list-format-change.js +586 -0
- package/services/list-format-mutation.js +416 -0
- package/services/list-format.js +305 -0
- package/services/numbering-allocation.js +642 -0
- package/services/numbering-helpers.js +354 -524
- package/services/operation-batch-compiler.js +612 -436
- package/services/operation-heuristics.js +197 -173
- package/services/operation-preflight.js +1 -0
- package/services/receipt-collector.js +288 -288
- package/services/revision-comment-management.js +634 -594
- package/services/revision-fidelity-verifier.js +498 -0
- package/services/standalone-docx-plumbing.js +627 -631
- package/services/standalone-operation-runner.d.ts +606 -427
- package/services/table-reconciliation.js +103 -49
package/engine/table-mode.js
CHANGED
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
* Table-specific reconciliation and transformation flows.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
NS_W,
|
|
7
|
-
createRevisionMetadata,
|
|
8
|
-
getRevisionIdAllocatorForDocument,
|
|
9
|
-
setRevisionIdAllocatorForDocument
|
|
10
|
-
} from '../core/types.js';
|
|
11
|
-
import {
|
|
12
|
-
getElementsByTagNS,
|
|
13
|
-
getElementsByTagNSOrTag,
|
|
14
|
-
getFirstElementByTagNS,
|
|
15
|
-
getFirstElementByTagNSOrTag,
|
|
16
|
-
getXmlParseError
|
|
17
|
-
} from '../core/xml-query.js';
|
|
18
|
-
import { parseOoxmlSafe } from '../adapters/xml-adapter.js';
|
|
19
|
-
import { log, error } from '../adapters/logger.js';
|
|
20
|
-
import { diffTablesWithVirtualGrid, serializeVirtualGridToOoxml, generateTableOoxml } from '../services/table-reconciliation.js';
|
|
21
|
-
import { parseTable } from '../pipeline/pipeline.js';
|
|
22
|
-
import { ingestTableToVirtualGrid } from '../pipeline/ingestion.js';
|
|
23
|
-
import { createWordElement, withOoxmlSourceType } from '../core/word-xml.js';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
function noChanges(serializer, xmlDoc) {
|
|
27
|
-
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
28
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
NS_W,
|
|
7
|
+
createRevisionMetadata,
|
|
8
|
+
getRevisionIdAllocatorForDocument,
|
|
9
|
+
setRevisionIdAllocatorForDocument
|
|
10
|
+
} from '../core/types.js';
|
|
11
|
+
import {
|
|
12
|
+
getElementsByTagNS,
|
|
13
|
+
getElementsByTagNSOrTag,
|
|
14
|
+
getFirstElementByTagNS,
|
|
15
|
+
getFirstElementByTagNSOrTag,
|
|
16
|
+
getXmlParseError
|
|
17
|
+
} from '../core/xml-query.js';
|
|
18
|
+
import { parseOoxmlSafe } from '../adapters/xml-adapter.js';
|
|
19
|
+
import { log, error } from '../adapters/logger.js';
|
|
20
|
+
import { diffTablesWithVirtualGrid, serializeVirtualGridToOoxml, generateTableOoxml } from '../services/table-reconciliation.js';
|
|
21
|
+
import { parseTable } from '../pipeline/pipeline.js';
|
|
22
|
+
import { ingestTableToVirtualGrid } from '../pipeline/ingestion.js';
|
|
23
|
+
import { createWordElement, withOoxmlSourceType } from '../core/word-xml.js';
|
|
24
|
+
import { markParagraphDeleted } from './paragraph-mark-revisions.js';
|
|
25
|
+
|
|
26
|
+
function noChanges(serializer, xmlDoc) {
|
|
27
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
28
|
+
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Applies structural reconciliation to tables using Virtual Grid.
|
|
@@ -38,8 +38,8 @@ function noChanges(serializer, xmlDoc) {
|
|
|
38
38
|
* @param {boolean} [generateRedlines=true] - Track change toggle
|
|
39
39
|
* @returns {{ oxml: string, hasChanges: boolean }}
|
|
40
40
|
*/
|
|
41
|
-
export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
|
|
42
|
-
const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, 'tbl');
|
|
41
|
+
export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
|
|
42
|
+
const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, 'tbl');
|
|
43
43
|
const newTableData = parseTable(modifiedText);
|
|
44
44
|
const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
|
|
45
45
|
|
|
@@ -55,15 +55,15 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
|
|
|
55
55
|
return noChanges(serializer, xmlDoc);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const options = {
|
|
59
|
-
generateRedlines,
|
|
60
|
-
author,
|
|
61
|
-
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
|
|
62
|
-
};
|
|
58
|
+
const options = {
|
|
59
|
+
generateRedlines,
|
|
60
|
+
author,
|
|
61
|
+
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
|
|
62
|
+
};
|
|
63
63
|
const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
|
|
64
64
|
const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
|
|
65
|
-
const reconciledDoc = parseOoxmlSafe(wrappedOxml, 'application/xml').doc;
|
|
66
|
-
if (!reconciledDoc) return noChanges(serializer, xmlDoc);
|
|
65
|
+
const reconciledDoc = parseOoxmlSafe(wrappedOxml, 'application/xml').doc;
|
|
66
|
+
if (!reconciledDoc) return noChanges(serializer, xmlDoc);
|
|
67
67
|
|
|
68
68
|
const parseError = getXmlParseError(reconciledDoc);
|
|
69
69
|
if (parseError) {
|
|
@@ -71,7 +71,7 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
|
|
|
71
71
|
return noChanges(serializer, xmlDoc);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const newTableNode = getFirstElementByTagNSOrTag(reconciledDoc, NS_W, 'tbl');
|
|
74
|
+
const newTableNode = getFirstElementByTagNSOrTag(reconciledDoc, NS_W, 'tbl');
|
|
75
75
|
if (!newTableNode) {
|
|
76
76
|
error('[OxmlEngine] No table found in reconciled OOXML');
|
|
77
77
|
return noChanges(serializer, xmlDoc);
|
|
@@ -80,7 +80,7 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
|
|
|
80
80
|
const importedTable = xmlDoc.importNode(newTableNode, true);
|
|
81
81
|
targetTable.parentNode.replaceChild(importedTable, targetTable);
|
|
82
82
|
|
|
83
|
-
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
83
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
@@ -94,17 +94,17 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
|
|
|
94
94
|
* @param {boolean} generateRedlines - Track change toggle
|
|
95
95
|
* @returns {{ oxml: string, hasChanges: boolean }}
|
|
96
96
|
*/
|
|
97
|
-
export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
98
|
-
const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
|
|
97
|
+
export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
98
|
+
const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
|
|
99
99
|
const tableData = parseTable(modifiedText);
|
|
100
100
|
if (!tableData || (tableData.rows.length === 0 && tableData.headers.length === 0)) {
|
|
101
101
|
log('[OxmlEngine] Failed to parse table data from Markdown');
|
|
102
102
|
return noChanges(serializer, xmlDoc);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator });
|
|
106
|
-
const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, 'application/xml').doc;
|
|
107
|
-
if (!tableDoc) return noChanges(serializer, xmlDoc);
|
|
105
|
+
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true });
|
|
106
|
+
const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, 'application/xml').doc;
|
|
107
|
+
if (!tableDoc) return noChanges(serializer, xmlDoc);
|
|
108
108
|
|
|
109
109
|
const tableParseError = getXmlParseError(tableDoc);
|
|
110
110
|
if (tableParseError) {
|
|
@@ -132,16 +132,16 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
|
|
|
132
132
|
let parent = firstParagraph.parentNode;
|
|
133
133
|
|
|
134
134
|
if (parent && parent.nodeType === 9) {
|
|
135
|
-
const wrappedDoc = parseOoxmlSafe(
|
|
136
|
-
`<w:document xmlns:w="${NS_W}"><w:body/></w:document>`,
|
|
137
|
-
'application/xml'
|
|
138
|
-
).doc;
|
|
139
|
-
if (!wrappedDoc) return noChanges(serializer, workingDoc);
|
|
135
|
+
const wrappedDoc = parseOoxmlSafe(
|
|
136
|
+
`<w:document xmlns:w="${NS_W}"><w:body/></w:document>`,
|
|
137
|
+
'application/xml'
|
|
138
|
+
).doc;
|
|
139
|
+
if (!wrappedDoc) return noChanges(serializer, workingDoc);
|
|
140
140
|
const wrappedBody = getFirstElementByTagNS(wrappedDoc, NS_W, 'body');
|
|
141
141
|
paragraphs.forEach(p => wrappedBody.appendChild(wrappedDoc.importNode(p, true)));
|
|
142
142
|
|
|
143
|
-
workingDoc = wrappedDoc;
|
|
144
|
-
setRevisionIdAllocatorForDocument(workingDoc, revisionIdAllocator);
|
|
143
|
+
workingDoc = wrappedDoc;
|
|
144
|
+
setRevisionIdAllocatorForDocument(workingDoc, revisionIdAllocator);
|
|
145
145
|
paragraphs = getElementsByTagNS(workingDoc, NS_W, 'p');
|
|
146
146
|
firstParagraph = paragraphs[0];
|
|
147
147
|
parent = firstParagraph.parentNode;
|
|
@@ -149,26 +149,26 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
|
|
|
149
149
|
|
|
150
150
|
const importedTable = workingDoc.importNode(newTableElement, true);
|
|
151
151
|
|
|
152
|
-
if (generateRedlines) {
|
|
153
|
-
paragraphs.forEach(p => {
|
|
154
|
-
|
|
155
|
-
const runs = getElementsByTagNS(p, NS_W, 'r');
|
|
156
|
-
runs.forEach(run => {
|
|
152
|
+
if (generateRedlines) {
|
|
153
|
+
paragraphs.forEach(p => {
|
|
154
|
+
markParagraphDeleted(p, { author, revisionIdAllocator: workingDoc });
|
|
155
|
+
const runs = getElementsByTagNS(p, NS_W, 'r');
|
|
156
|
+
runs.forEach(run => {
|
|
157
157
|
const textNodes = getElementsByTagNS(run, NS_W, 't');
|
|
158
158
|
textNodes.forEach(t => {
|
|
159
159
|
const text = t.textContent || '';
|
|
160
160
|
if (text.trim()) {
|
|
161
|
-
const delText = createWordElement(workingDoc, 'w:delText');
|
|
162
|
-
delText.textContent = text;
|
|
163
|
-
t.parentNode.replaceChild(delText, t);
|
|
161
|
+
const delText = createWordElement(workingDoc, 'w:delText');
|
|
162
|
+
delText.textContent = text;
|
|
163
|
+
t.parentNode.replaceChild(delText, t);
|
|
164
164
|
}
|
|
165
165
|
});
|
|
166
|
-
|
|
167
|
-
const del = createWordElement(workingDoc, 'w:del');
|
|
168
|
-
const metadata = createRevisionMetadata(author, workingDoc, 'del');
|
|
169
|
-
del.setAttribute('w:id', String(metadata.id));
|
|
170
|
-
del.setAttribute('w:author', metadata.author);
|
|
171
|
-
del.setAttribute('w:date', metadata.date);
|
|
166
|
+
|
|
167
|
+
const del = createWordElement(workingDoc, 'w:del');
|
|
168
|
+
const metadata = createRevisionMetadata(author, workingDoc, 'del');
|
|
169
|
+
del.setAttribute('w:id', String(metadata.id));
|
|
170
|
+
del.setAttribute('w:author', metadata.author);
|
|
171
|
+
del.setAttribute('w:date', metadata.date);
|
|
172
172
|
run.parentNode.insertBefore(del, run);
|
|
173
173
|
del.appendChild(run);
|
|
174
174
|
});
|
|
@@ -183,5 +183,5 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
log('[OxmlEngine] Text-to-table transformation complete');
|
|
186
|
-
return withOoxmlSourceType({ oxml: serializer.serializeToString(workingDoc), hasChanges: true });
|
|
187
|
-
}
|
|
186
|
+
return withOoxmlSourceType({ oxml: serializer.serializeToString(workingDoc), hasChanges: true });
|
|
187
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -30,6 +30,16 @@ export type {
|
|
|
30
30
|
DocumentOperationBatchResult,
|
|
31
31
|
HighlightDocumentOperation,
|
|
32
32
|
InsertionAffinity,
|
|
33
|
+
ListConversionDiagnostic,
|
|
34
|
+
ListFormatDocumentOperation,
|
|
35
|
+
ListFormatRequest,
|
|
36
|
+
ListFormatConvertPlainRequest,
|
|
37
|
+
ListFormatChangeFormatRequest,
|
|
38
|
+
ListFormatJoinRequest,
|
|
39
|
+
ListFormatResult,
|
|
40
|
+
ListFormatConvertPlainResult,
|
|
41
|
+
ListFormatRebindResult,
|
|
42
|
+
ListNumberFormat,
|
|
33
43
|
LocalizedReplacement,
|
|
34
44
|
LocalizedReplacementChange,
|
|
35
45
|
OperationConflict,
|
|
@@ -52,6 +62,13 @@ export interface RedlineError {
|
|
|
52
62
|
| 'DIFF_TOKEN_LIMIT'
|
|
53
63
|
| 'UNSUPPORTED_REVISION_VIEW_MUTATION'
|
|
54
64
|
| 'UNSUPPORTED_INSERTION_AFFINITY'
|
|
65
|
+
| 'UNSUPPORTED_LIST_CONVERSION'
|
|
66
|
+
| 'UNSUPPORTED_LIST_FORMAT_CHANGE'
|
|
67
|
+
| 'LIST_MARKER_NOT_FOUND'
|
|
68
|
+
| 'LIST_MARKER_AMBIGUOUS'
|
|
69
|
+
| 'LIST_MARKER_UNSUPPORTED'
|
|
70
|
+
| 'LIST_FORMAT_TARGET_IS_LIST'
|
|
71
|
+
| 'LIST_FORMAT_STYLE_NUMBERED'
|
|
55
72
|
| 'UNSAFE_PARAGRAPH_BOUNDARY'
|
|
56
73
|
| 'FOREIGN_PARAGRAPH_MARK_DELETION'
|
|
57
74
|
| 'RESTORATION_STATE_REQUIRED'
|
|
@@ -80,6 +97,11 @@ export interface RedlineError {
|
|
|
80
97
|
recoveryVersion?: number;
|
|
81
98
|
category?: 'request_fixable' | 'target_refresh_required' | 'candidate_selection_required' | 'policy_choice_required' | 'user_authorization_required' | 'source_conflict' | 'library_or_builder_failure' | 'manual_document_resolution' | string;
|
|
82
99
|
context?: Record<string, unknown>;
|
|
100
|
+
/** List-conversion errors: the literal marker, the requested list kind/format and the kept source definition. */
|
|
101
|
+
listMarker?: string;
|
|
102
|
+
requestedList?: { kind: 'bullet' | 'numbered'; format: string };
|
|
103
|
+
sourceList?: { numId: string; level: number; kind: 'bullet' | 'numbered'; format: string };
|
|
104
|
+
targetExcerpt?: string;
|
|
83
105
|
recovery?: {
|
|
84
106
|
action: string;
|
|
85
107
|
sameArgumentsSafe: boolean;
|
|
@@ -87,6 +109,8 @@ export interface RedlineError {
|
|
|
87
109
|
requiresUserAuthorization: boolean;
|
|
88
110
|
field?: string;
|
|
89
111
|
recommendedValue?: unknown;
|
|
112
|
+
allowedValue?: unknown;
|
|
113
|
+
hint?: string;
|
|
90
114
|
};
|
|
91
115
|
expectedRevision?: RevisionToken;
|
|
92
116
|
currentRevision?: RevisionToken;
|
|
@@ -105,6 +129,10 @@ export interface RedlineOptions {
|
|
|
105
129
|
structuredContent?: boolean;
|
|
106
130
|
pairReplacements?: boolean;
|
|
107
131
|
insertionAffinity?: InsertionAffinity;
|
|
132
|
+
/** Source numbering part; generated list definitions are remapped to IDs that do not collide with it. */
|
|
133
|
+
numberingXml?: string | null;
|
|
134
|
+
/** Shared numbering ID state (from `createDynamicNumberingIdState`) for allocation across calls. */
|
|
135
|
+
numberingIdState?: unknown;
|
|
108
136
|
[key: string]: unknown;
|
|
109
137
|
}
|
|
110
138
|
|
|
@@ -433,7 +461,29 @@ export function overwriteParagraphNumIds(oxml: string, numId: string | number):
|
|
|
433
461
|
export function extractFirstParagraphNumId(oxml: string): string | null;
|
|
434
462
|
export function buildExplicitDecimalMultilevelNumberingXml(options?: Record<string, unknown>): string;
|
|
435
463
|
export function remapNumberingPayloadForDocument(numberingXml: string, state: unknown): unknown;
|
|
436
|
-
export
|
|
464
|
+
export interface NumberingIdConflict { kind: 'abstractNum' | 'num'; id: number }
|
|
465
|
+
export interface NumberingMergeOptions { onConflict?: (conflict: NumberingIdConflict) => void }
|
|
466
|
+
export interface NumberingIntegrityIssue { code: string; severity: 'error'; message: string; numId?: number; abstractNumId?: number; ilvl?: number }
|
|
467
|
+
export function mergeNumberingXmlBySchemaOrder(baseXml: string, incomingXml: string, options?: NumberingMergeOptions): string;
|
|
468
|
+
export function mergeNumberingParts(existingXml: string | null | undefined, incomingParts: string | Array<string | null | undefined> | null | undefined, options?: NumberingMergeOptions): string;
|
|
469
|
+
export function allocateNumberingDefinition(params: {
|
|
470
|
+
numberingIdState: unknown;
|
|
471
|
+
sourceNumberingXml?: string | null;
|
|
472
|
+
sourceNumId?: number | string | null;
|
|
473
|
+
levels?: Array<{ ilvl: number; numFmt?: string; lvlText?: string; start?: number }>;
|
|
474
|
+
startOverrides?: Record<number | string, number>;
|
|
475
|
+
}): { numId: number; abstractNumId: number; numberingXml: string } | null;
|
|
476
|
+
export function allocateNumberingForReplacement(params: {
|
|
477
|
+
numberingXml: string | null;
|
|
478
|
+
replacementNodes: unknown[];
|
|
479
|
+
sourceParagraphs?: unknown[];
|
|
480
|
+
numberingIdState?: unknown;
|
|
481
|
+
allowContinueSource?: boolean;
|
|
482
|
+
}): { policy: 'continue-source' | 'allocate' | 'passthrough'; numberingXml: string | null; replacementNodes: unknown[]; numId?: number };
|
|
483
|
+
export function applyStartOverridesToNum(numElement: unknown, startOverrides: Record<number | string, number>): void;
|
|
484
|
+
export function copyLevelGeometry(sourceLvl: unknown, targetLvl: unknown): void;
|
|
485
|
+
export function canonicalNumberingDefinition(numbering: unknown, numId: string | number): string | null;
|
|
486
|
+
export function validateNumberingIntegrity(documentXml: unknown, numberingXml?: unknown): NumberingIntegrityIssue[];
|
|
437
487
|
|
|
438
488
|
export function buildListMarkdown(items: unknown[], options?: Record<string, unknown>): string;
|
|
439
489
|
export function inferNumberingStyleFromMarker(marker: string): string;
|
package/index.js
CHANGED
|
@@ -21,10 +21,44 @@ import {
|
|
|
21
21
|
executeSingleLineListStructuralFallback
|
|
22
22
|
} from './orchestration/list-structural-fallback.js';
|
|
23
23
|
import { withOoxmlSourceType } from './core/word-xml.js';
|
|
24
|
+
import { createDynamicNumberingIdState, remapNumberingPayloadForDocument } from './services/numbering-helpers.js';
|
|
24
25
|
export { containsTrackedChanges, getTrackedChangeAuthors } from './core/word-xml.js';
|
|
25
26
|
export { validateRedlineOoxml } from './core/redline-validation.js';
|
|
26
27
|
export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/structured-content.js';
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* When the caller supplies the source `numberingXml` (or a numbering ID state), generated list
|
|
31
|
+
* definitions are remapped to IDs that cannot collide with any existing `w:num`/`w:abstractNum`.
|
|
32
|
+
*/
|
|
33
|
+
function remapGeneratedNumberingForSource(result, options) {
|
|
34
|
+
const sourceNumbering = typeof options?.numberingXml === 'string' && options.numberingXml.trim()
|
|
35
|
+
? options.numberingXml
|
|
36
|
+
: null;
|
|
37
|
+
const state = options?.numberingIdState && typeof options.numberingIdState === 'object'
|
|
38
|
+
? options.numberingIdState
|
|
39
|
+
: (sourceNumbering ? createDynamicNumberingIdState(sourceNumbering) : null);
|
|
40
|
+
if (!state || !result?.hasChanges || typeof result.oxml !== 'string' || !result.oxml.includes('/word/numbering.xml')) {
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
const pkgDoc = parseOoxml(result.oxml);
|
|
44
|
+
const parts = Array.from(pkgDoc.getElementsByTagNameNS('*', 'part'));
|
|
45
|
+
const payloadOf = name => {
|
|
46
|
+
const part = parts.find(candidate => (candidate.getAttribute('pkg:name') || candidate.getAttribute('name')) === name);
|
|
47
|
+
const xmlData = part?.getElementsByTagNameNS('*', 'xmlData')[0];
|
|
48
|
+
return xmlData ? Array.from(xmlData.childNodes).find(node => node.nodeType === 1) || null : null;
|
|
49
|
+
};
|
|
50
|
+
const numberingNode = payloadOf('/word/numbering.xml');
|
|
51
|
+
const body = payloadOf('/word/document.xml')?.getElementsByTagNameNS('*', 'body')[0];
|
|
52
|
+
if (!numberingNode || !body) return result;
|
|
53
|
+
|
|
54
|
+
const bodyNodes = Array.from(body.childNodes).filter(node => node.nodeType === 1 && node.localName !== 'sectPr');
|
|
55
|
+
const remapped = remapNumberingPayloadForDocument(serializeOoxml(numberingNode), bodyNodes, state);
|
|
56
|
+
const newNumbering = pkgDoc.importNode(parseOoxml(remapped.numberingXml).documentElement, true);
|
|
57
|
+
numberingNode.parentNode.replaceChild(newNumbering, numberingNode);
|
|
58
|
+
bodyNodes.forEach((node, index) => body.replaceChild(remapped.replacementNodes[index], node));
|
|
59
|
+
return { ...result, oxml: serializeOoxml(pkgDoc) };
|
|
60
|
+
}
|
|
61
|
+
|
|
28
62
|
/**
|
|
29
63
|
* Standalone-safe redline wrapper.
|
|
30
64
|
*
|
|
@@ -33,7 +67,8 @@ export { analyzeStructuredContent, planStructuredReplacement } from './pipeline/
|
|
|
33
67
|
* complete that native fallback path, so normalize to a no-op with warnings.
|
|
34
68
|
*/
|
|
35
69
|
export async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
|
|
36
|
-
const
|
|
70
|
+
const rawResult = await applyRedlineToOxmlEngine(oxml, originalText, modifiedText, options);
|
|
71
|
+
const result = remapGeneratedNumberingForSource(rawResult, options);
|
|
37
72
|
if (result?.useNativeApi && typeof result?.oxml !== 'string') {
|
|
38
73
|
const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
39
74
|
return withOoxmlSourceType({
|
|
@@ -225,9 +260,18 @@ export {
|
|
|
225
260
|
overwriteParagraphNumIds,
|
|
226
261
|
extractFirstParagraphNumId,
|
|
227
262
|
buildExplicitDecimalMultilevelNumberingXml,
|
|
228
|
-
remapNumberingPayloadForDocument
|
|
229
|
-
mergeNumberingXmlBySchemaOrder
|
|
263
|
+
remapNumberingPayloadForDocument
|
|
230
264
|
} from './services/numbering-helpers.js';
|
|
265
|
+
export {
|
|
266
|
+
allocateNumberingDefinition,
|
|
267
|
+
allocateNumberingForReplacement,
|
|
268
|
+
applyStartOverridesToNum,
|
|
269
|
+
canonicalNumberingDefinition,
|
|
270
|
+
copyLevelGeometry,
|
|
271
|
+
mergeNumberingParts,
|
|
272
|
+
mergeNumberingXmlBySchemaOrder,
|
|
273
|
+
validateNumberingIntegrity
|
|
274
|
+
} from './services/numbering-allocation.js';
|
|
231
275
|
|
|
232
276
|
// Pipeline components
|
|
233
277
|
export { ReconciliationPipeline } from './pipeline/pipeline.js';
|
package/node/cli-help.js
CHANGED
|
@@ -144,6 +144,7 @@ export const CLI_COMMAND_HELP = Object.freeze({
|
|
|
144
144
|
option('textToComment', '--text-to-comment <text>', 'Anchor an inline comment to an exact subspan.'),
|
|
145
145
|
option('targetRef', '--target-ref <N>', 'Disambiguate an inline target with a 1-based machine index.'),
|
|
146
146
|
option('existingRevisions', '--existing-revisions <policy>', 'Select revision handling; cross-author slicing must be deliberate.'),
|
|
147
|
+
option('revisionFidelity', '--revision-fidelity <enforce|warn|off>', 'Reject All verification of each tracked operation (default enforce: refuse with REVISION_FIDELITY_VIOLATION); the agent profile rejects off.'),
|
|
147
148
|
option('atomic', '--atomic[=true|false]', 'Choose all-or-nothing or progressive batch execution.'),
|
|
148
149
|
option('generateRedlines', '--generate-redlines[=true|false]', 'Control tracked-change generation.'),
|
|
149
150
|
option('noRedlines', '--no-redlines', 'Apply clean text without tracked-change markup.'),
|