@ansonlai/docx-redline-js 0.1.4 → 0.2.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.
Files changed (47) hide show
  1. package/AGENTS.md +53 -4
  2. package/ARCHITECTURE.md +75 -11
  3. package/README.md +62 -3
  4. package/core/redline-validation.js +156 -0
  5. package/core/types.js +35 -8
  6. package/core/word-xml.js +90 -0
  7. package/dist/docx-redline-js.esm.js +3195 -2592
  8. package/dist/docx-redline-js.esm.js.map +4 -4
  9. package/dist/docx-redline-js.esm.min.js +71 -67
  10. package/dist/docx-redline-js.esm.min.js.map +4 -4
  11. package/docs/VALIDATION.md +104 -0
  12. package/docs/plans/2026-03-01-release-0.1.4-design.md +31 -0
  13. package/docs/plans/2026-03-01-release-0.1.4.md +108 -0
  14. package/docs/plans/2026-05-31-architectural changes.md +591 -0
  15. package/engine/format-application.js +13 -14
  16. package/engine/format-span-application.js +7 -6
  17. package/engine/formatting-removal.js +15 -12
  18. package/engine/oxml-engine.js +146 -55
  19. package/engine/reconstruction-mapper.js +35 -8
  20. package/engine/reconstruction-mode.js +14 -13
  21. package/engine/reconstruction-writer.js +97 -78
  22. package/engine/rpr-helpers.js +34 -32
  23. package/engine/run-builders.js +150 -39
  24. package/engine/surgical-diff-application.js +216 -0
  25. package/engine/surgical-mode.js +84 -519
  26. package/engine/surgical-run-splitting.js +96 -0
  27. package/engine/surgical-spans.js +169 -0
  28. package/engine/table-cell-context.js +15 -13
  29. package/engine/table-mode.js +39 -35
  30. package/index.d.ts +172 -0
  31. package/index.js +50 -47
  32. package/package.json +10 -2
  33. package/pipeline/ingestion-export.js +1 -0
  34. package/pipeline/ingestion-paragraph.js +37 -12
  35. package/pipeline/ingestion-table.js +11 -8
  36. package/scripts/build.mjs +40 -0
  37. package/scripts/check-types.mjs +29 -0
  38. package/scripts/export-validation-fixtures.mjs +125 -0
  39. package/scripts/lib/minimal-zip.mjs +155 -0
  40. package/scripts/run-tests.mjs +43 -0
  41. package/scripts/validate-fixtures-xsd.sh +37 -0
  42. package/scripts/word-com-differential.ps1 +133 -0
  43. package/scripts/word-com-smoke.ps1 +48 -0
  44. package/services/comment-locator.js +10 -9
  45. package/services/revision-comment-management.js +115 -1
  46. package/services/standalone-operation-runner.js +119 -69
  47. package/services/table-reconciliation.js +7 -8
@@ -0,0 +1,96 @@
1
+ import { createWordElement, isWordElement } from '../core/word-xml.js';
2
+ import { getRunChildText, isTextLikeRunChild } from './surgical-spans.js';
3
+
4
+ export function getRunContentPieces(runElement) {
5
+ const pieces = [];
6
+ let offset = 0;
7
+
8
+ for (const child of Array.from(runElement.childNodes || [])) {
9
+ if (isWordElement(child, 'rPr')) continue;
10
+ if (!isTextLikeRunChild(child)) continue;
11
+
12
+ const text = getRunChildText(child);
13
+ if (text.length === 0) continue;
14
+
15
+ pieces.push({
16
+ node: child,
17
+ start: offset,
18
+ end: offset + text.length,
19
+ text
20
+ });
21
+ offset += text.length;
22
+ }
23
+
24
+ return pieces;
25
+ }
26
+
27
+ export function getRunTextLength(pieces) {
28
+ if (pieces.length === 0) return 0;
29
+ return pieces[pieces.length - 1].end;
30
+ }
31
+
32
+ export function sliceRunPieces(xmlDoc, pieces, start, end, asDeletedText) {
33
+ const sliced = [];
34
+ if (end <= start) return sliced;
35
+
36
+ pieces.forEach(piece => {
37
+ const overlapStart = Math.max(start, piece.start);
38
+ const overlapEnd = Math.min(end, piece.end);
39
+ if (overlapEnd <= overlapStart) return;
40
+
41
+ const localStart = overlapStart - piece.start;
42
+ const localEnd = overlapEnd - piece.start;
43
+ const text = piece.text.slice(localStart, localEnd);
44
+
45
+ sliced.push(cloneRunPiece(xmlDoc, piece.node, text, asDeletedText));
46
+ });
47
+
48
+ return sliced;
49
+ }
50
+
51
+ export function createRunFromPieces(xmlDoc, pieces, rPr) {
52
+ const run = createWordElement(xmlDoc, 'w:r');
53
+ if (rPr) run.appendChild(rPr.cloneNode(true));
54
+ pieces.forEach(piece => run.appendChild(piece));
55
+ return run;
56
+ }
57
+
58
+ export function insertRunPiecesBefore(xmlDoc, parent, referenceNode, pieces, rPr) {
59
+ if (pieces.length === 0) return null;
60
+ const run = createRunFromPieces(xmlDoc, pieces, rPr);
61
+ parent.insertBefore(run, referenceNode);
62
+ return run;
63
+ }
64
+
65
+ function cloneRunPiece(xmlDoc, sourceNode, text, asDeletedText) {
66
+ if (asDeletedText) {
67
+ const delText = createWordElement(xmlDoc, 'w:delText');
68
+ delText.setAttribute('xml:space', 'preserve');
69
+ delText.textContent = text;
70
+ return delText;
71
+ }
72
+
73
+ if (isWordElement(sourceNode, 't')) {
74
+ const textNode = sourceNode.cloneNode(false);
75
+ textNode.textContent = text;
76
+ if (/^\s|\s$/.test(text)) {
77
+ textNode.setAttribute('xml:space', 'preserve');
78
+ }
79
+ return textNode;
80
+ }
81
+
82
+ if (text === '\n' && (isWordElement(sourceNode, 'br') || isWordElement(sourceNode, 'cr'))) {
83
+ return sourceNode.cloneNode(true);
84
+ }
85
+ if (text === '\t' && isWordElement(sourceNode, 'tab')) {
86
+ return sourceNode.cloneNode(true);
87
+ }
88
+ if (text === '\u2011' && isWordElement(sourceNode, 'noBreakHyphen')) {
89
+ return sourceNode.cloneNode(true);
90
+ }
91
+
92
+ const textNode = createWordElement(xmlDoc, 'w:t');
93
+ textNode.setAttribute('xml:space', 'preserve');
94
+ textNode.textContent = text;
95
+ return textNode;
96
+ }
@@ -0,0 +1,169 @@
1
+ import { appendParagraphBoundary } from '../core/paragraph-offset-policy.js';
2
+ import { getFirstElementByTag } from '../core/xml-query.js';
3
+ import { isWordElement } from '../core/word-xml.js';
4
+
5
+ export function getRunChildText(child) {
6
+ if (isWordElement(child, 't')) return child.textContent || '';
7
+ if (isWordElement(child, 'br') || isWordElement(child, 'cr')) return '\n';
8
+ if (isWordElement(child, 'tab')) return '\t';
9
+ if (isWordElement(child, 'noBreakHyphen')) return '\u2011';
10
+ return '';
11
+ }
12
+
13
+ export function isTextLikeRunChild(child) {
14
+ return isWordElement(child, 't')
15
+ || isWordElement(child, 'br')
16
+ || isWordElement(child, 'cr')
17
+ || isWordElement(child, 'tab')
18
+ || isWordElement(child, 'noBreakHyphen');
19
+ }
20
+
21
+ export function buildSurgicalTextSpans(paragraphs) {
22
+ let fullText = '';
23
+ const textSpans = [];
24
+
25
+ paragraphs.forEach((paragraph, paragraphIndex) => {
26
+ const container = paragraph.parentNode;
27
+
28
+ for (let child = paragraph.firstChild; child; child = child.nextSibling) {
29
+ if (isWordElement(child, 'r')) {
30
+ fullText += processRunElement(child, paragraph, container, fullText.length, textSpans).text;
31
+ } else if (isWordElement(child, 'hyperlink')) {
32
+ for (let hc = child.firstChild; hc; hc = hc.nextSibling) {
33
+ if (isWordElement(hc, 'r')) {
34
+ fullText += processRunElement(hc, paragraph, container, fullText.length, textSpans).text;
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ fullText = appendParagraphBoundary(fullText, paragraphIndex, paragraphs.length);
41
+ });
42
+
43
+ return { fullText, textSpans };
44
+ }
45
+
46
+ function processRunElement(run, paragraph, container, currentOffset, textSpans) {
47
+ const rPr = getFirstElementByTag(run, 'w:rPr');
48
+ let localOffset = currentOffset;
49
+ const textParts = [];
50
+
51
+ for (let child = run.firstChild; child; child = child.nextSibling) {
52
+ if (isWordElement(child, 't')) {
53
+ const text = child.textContent || '';
54
+ if (text.length === 0) continue;
55
+
56
+ textSpans.push({
57
+ charStart: localOffset,
58
+ charEnd: localOffset + text.length,
59
+ textElement: child,
60
+ runElement: run,
61
+ paragraph,
62
+ container,
63
+ rPr
64
+ });
65
+ localOffset += text.length;
66
+ textParts.push(text);
67
+ } else if (isTextLikeRunChild(child)) {
68
+ const text = getRunChildText(child);
69
+ textSpans.push({
70
+ charStart: localOffset,
71
+ charEnd: localOffset + 1,
72
+ textElement: child,
73
+ runElement: run,
74
+ paragraph,
75
+ container,
76
+ rPr
77
+ });
78
+ localOffset += 1;
79
+ textParts.push(text);
80
+ }
81
+ }
82
+
83
+ return { text: textParts.join('') };
84
+ }
85
+
86
+ export function buildSpanIndex(textSpans) {
87
+ const spans = textSpans
88
+ .slice()
89
+ .sort((a, b) => a.charStart - b.charStart || a.charEnd - b.charEnd);
90
+
91
+ const starts = spans.map(span => span.charStart);
92
+ const ends = spans.map(span => span.charEnd);
93
+
94
+ return { spans, starts, ends };
95
+ }
96
+
97
+ export function forEachOverlappingSpan(spanIndex, startPos, endPos, callback) {
98
+ if (endPos <= startPos || spanIndex.spans.length === 0) {
99
+ return;
100
+ }
101
+
102
+ let index = upperBound(spanIndex.ends, startPos);
103
+ while (index < spanIndex.spans.length) {
104
+ const span = spanIndex.spans[index];
105
+ if (span.charStart >= endPos) {
106
+ break;
107
+ }
108
+ callback(span);
109
+ index++;
110
+ }
111
+ }
112
+
113
+ export function findContainingSpan(spanIndex, pos) {
114
+ if (spanIndex.spans.length === 0) return null;
115
+
116
+ const index = upperBound(spanIndex.starts, pos) - 1;
117
+ if (index < 0) return null;
118
+
119
+ const span = spanIndex.spans[index];
120
+ return pos >= span.charStart && pos < span.charEnd ? span : null;
121
+ }
122
+
123
+ export function findFirstSpanEndingAt(spanIndex, pos) {
124
+ const index = lowerBound(spanIndex.ends, pos);
125
+ if (index < spanIndex.spans.length && spanIndex.ends[index] === pos) {
126
+ return spanIndex.spans[index];
127
+ }
128
+ return null;
129
+ }
130
+
131
+ export function findLastSpanEndingBeforeOrAt(spanIndex, pos) {
132
+ const index = upperBound(spanIndex.ends, pos) - 1;
133
+ if (index >= 0) {
134
+ return spanIndex.spans[index];
135
+ }
136
+ return null;
137
+ }
138
+
139
+ function upperBound(values, target) {
140
+ let left = 0;
141
+ let right = values.length;
142
+
143
+ while (left < right) {
144
+ const middle = (left + right) >> 1;
145
+ if (values[middle] <= target) {
146
+ left = middle + 1;
147
+ } else {
148
+ right = middle;
149
+ }
150
+ }
151
+
152
+ return left;
153
+ }
154
+
155
+ function lowerBound(values, target) {
156
+ let left = 0;
157
+ let right = values.length;
158
+
159
+ while (left < right) {
160
+ const middle = (left + right) >> 1;
161
+ if (values[middle] < target) {
162
+ left = middle + 1;
163
+ } else {
164
+ right = middle;
165
+ }
166
+ }
167
+
168
+ return left;
169
+ }
@@ -6,11 +6,13 @@
6
6
  */
7
7
 
8
8
  import { getDocumentParagraphs } from './format-extraction.js';
9
- import { log } from '../adapters/logger.js';
10
- import { buildParagraphOnlyPackage } from '../services/package-builder.js';
11
- import { getElementsByTag } from '../core/xml-query.js';
12
-
13
- const W14_NS = 'http://schemas.microsoft.com/office/word/2010/wordml';
9
+ import { log } from '../adapters/logger.js';
10
+ import { buildParagraphOnlyPackage } from '../services/package-builder.js';
11
+ import { getElementsByTagNSOrTag } from '../core/xml-query.js';
12
+ import { NS_W } from '../core/types.js';
13
+ import { isWordElement } from '../core/word-xml.js';
14
+
15
+ const W14_NS = 'http://schemas.microsoft.com/office/word/2010/wordml';
14
16
 
15
17
  /**
16
18
  * Detects whether the current XML is table-wrapped and resolves target paragraph context.
@@ -30,19 +32,19 @@ const W14_NS = 'http://schemas.microsoft.com/office/word/2010/wordml';
30
32
  */
31
33
  export function detectTableCellContext(xmlDoc, originalText, options = {}) {
32
34
  const { targetParagraphId = null } = options;
33
- const tables = getElementsByTag(xmlDoc, 'w:tbl');
35
+ const tables = getElementsByTagNSOrTag(xmlDoc, NS_W, 'tbl');
34
36
  if (tables.length === 0) {
35
37
  return { hasTableWrapper: false, isTableCellParagraph: false, paragraphs: [], paragraph: null, tableElement: null };
36
38
  }
37
39
 
38
40
  const allParagraphs = getDocumentParagraphs(xmlDoc);
39
41
  const paragraphsInCells = allParagraphs.filter(p => {
40
- let parent = p.parentNode;
41
- while (parent) {
42
- if (parent.nodeName === 'w:tc') return true;
43
- parent = parent.parentNode;
44
- }
45
- return false;
42
+ let parent = p.parentNode;
43
+ while (parent) {
44
+ if (isWordElement(parent, 'tc')) return true;
45
+ parent = parent.parentNode;
46
+ }
47
+ return false;
46
48
  });
47
49
 
48
50
  log(`[OxmlEngine] Table wrapper detected: ${tables.length} tables, ${paragraphsInCells.length} paragraphs in cells`);
@@ -68,7 +70,7 @@ export function detectTableCellContext(xmlDoc, originalText, options = {}) {
68
70
  const normalizedTarget = originalText.trim();
69
71
  if (!targetParagraph) {
70
72
  for (const p of paragraphsInCells) {
71
- const textNodes = getElementsByTag(p, 'w:t');
73
+ const textNodes = getElementsByTagNSOrTag(p, NS_W, 't');
72
74
  let paragraphText = '';
73
75
  for (const t of textNodes) {
74
76
  paragraphText += t.textContent || '';
@@ -2,23 +2,26 @@
2
2
  * Table-specific reconciliation and transformation flows.
3
3
  */
4
4
 
5
- import { NS_W, getNextRevisionId, getRevisionTimestamp } from '../core/types.js';
6
- import {
7
- getElementsByTag,
8
- getElementsByTagNS,
9
- getFirstElementByTag,
10
- getFirstElementByTagNS,
11
- getXmlParseError
12
- } from '../core/xml-query.js';
5
+ import { NS_W, createRevisionMetadata } from '../core/types.js';
6
+ import {
7
+ getElementsByTagNS,
8
+ getElementsByTagNSOrTag,
9
+ getFirstElementByTag,
10
+ getFirstElementByTagNS,
11
+ getFirstElementByTagNSOrTag,
12
+ getXmlParseError
13
+ } from '../core/xml-query.js';
13
14
  import { createParser } from '../adapters/xml-adapter.js';
14
- import { log, error } from '../adapters/logger.js';
15
- import { diffTablesWithVirtualGrid, serializeVirtualGridToOoxml, generateTableOoxml } from '../services/table-reconciliation.js';
16
- import { parseTable } from '../pipeline/pipeline.js';
17
- import { ingestTableToVirtualGrid } from '../pipeline/ingestion.js';
18
-
19
- function noChanges(serializer, xmlDoc) {
20
- return { oxml: serializer.serializeToString(xmlDoc), hasChanges: false };
21
- }
15
+ import { log, error } from '../adapters/logger.js';
16
+ import { diffTablesWithVirtualGrid, serializeVirtualGridToOoxml, generateTableOoxml } from '../services/table-reconciliation.js';
17
+ import { parseTable } from '../pipeline/pipeline.js';
18
+ import { ingestTableToVirtualGrid } from '../pipeline/ingestion.js';
19
+ import { createWordElement, withOoxmlSourceType } from '../core/word-xml.js';
20
+ import { markParagraphMarkDeleted } from './run-builders.js';
21
+
22
+ function noChanges(serializer, xmlDoc) {
23
+ return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
24
+ }
22
25
 
23
26
  /**
24
27
  * Applies structural reconciliation to tables using Virtual Grid.
@@ -31,8 +34,8 @@ function noChanges(serializer, xmlDoc) {
31
34
  * @param {boolean} [generateRedlines=true] - Track change toggle
32
35
  * @returns {{ oxml: string, hasChanges: boolean }}
33
36
  */
34
- export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
35
- const tableNodes = getElementsByTag(xmlDoc, 'w:tbl');
37
+ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
38
+ const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, 'tbl');
36
39
  const newTableData = parseTable(modifiedText);
37
40
  const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
38
41
 
@@ -60,7 +63,7 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
60
63
  return noChanges(serializer, xmlDoc);
61
64
  }
62
65
 
63
- const newTableNode = getFirstElementByTag(reconciledDoc, 'w:tbl');
66
+ const newTableNode = getFirstElementByTagNSOrTag(reconciledDoc, NS_W, 'tbl');
64
67
  if (!newTableNode) {
65
68
  error('[OxmlEngine] No table found in reconciled OOXML');
66
69
  return noChanges(serializer, xmlDoc);
@@ -69,7 +72,7 @@ export function applyTableReconciliation(xmlDoc, modifiedText, serializer, parse
69
72
  const importedTable = xmlDoc.importNode(newTableNode, true);
70
73
  targetTable.parentNode.replaceChild(importedTable, targetTable);
71
74
 
72
- return { oxml: serializer.serializeToString(xmlDoc), hasChanges: true };
75
+ return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
73
76
  }
74
77
 
75
78
  /**
@@ -135,25 +138,26 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
135
138
 
136
139
  const importedTable = workingDoc.importNode(newTableElement, true);
137
140
 
138
- if (generateRedlines) {
139
- const date = getRevisionTimestamp();
140
- paragraphs.forEach(p => {
141
- const runs = getElementsByTagNS(p, NS_W, 'r');
142
- runs.forEach(run => {
141
+ if (generateRedlines) {
142
+ paragraphs.forEach(p => {
143
+ markParagraphMarkDeleted(workingDoc, p, author);
144
+ const runs = getElementsByTagNS(p, NS_W, 'r');
145
+ runs.forEach(run => {
143
146
  const textNodes = getElementsByTagNS(run, NS_W, 't');
144
147
  textNodes.forEach(t => {
145
148
  const text = t.textContent || '';
146
149
  if (text.trim()) {
147
- const delText = workingDoc.createElementNS(NS_W, 'w:delText');
148
- delText.textContent = text;
149
- t.parentNode.replaceChild(delText, t);
150
+ const delText = createWordElement(workingDoc, 'w:delText');
151
+ delText.textContent = text;
152
+ t.parentNode.replaceChild(delText, t);
150
153
  }
151
154
  });
152
-
153
- const del = workingDoc.createElementNS(NS_W, 'w:del');
154
- del.setAttribute('w:id', String(getNextRevisionId()));
155
- del.setAttribute('w:author', author);
156
- del.setAttribute('w:date', date);
155
+
156
+ const del = createWordElement(workingDoc, 'w:del');
157
+ const metadata = createRevisionMetadata(author);
158
+ del.setAttribute('w:id', String(metadata.id));
159
+ del.setAttribute('w:author', metadata.author);
160
+ del.setAttribute('w:date', metadata.date);
157
161
  run.parentNode.insertBefore(del, run);
158
162
  del.appendChild(run);
159
163
  });
@@ -168,5 +172,5 @@ export function applyTextToTableTransformation(xmlDoc, modifiedText, serializer,
168
172
  }
169
173
 
170
174
  log('[OxmlEngine] Text-to-table transformation complete');
171
- return { oxml: serializer.serializeToString(workingDoc), hasChanges: true };
172
- }
175
+ return withOoxmlSourceType({ oxml: serializer.serializeToString(workingDoc), hasChanges: true });
176
+ }
package/index.d.ts ADDED
@@ -0,0 +1,172 @@
1
+ export type OoxmlSourceType = 'package' | 'document' | 'fragment';
2
+ export type RedlineStatus = 'ok' | 'no-op' | 'error';
3
+ export type ExistingRevisionsPolicy = 'reject-input' | 'accept-all-first';
4
+
5
+ export interface RedlineError {
6
+ code: 'PARSE_ERROR' | 'TARGET_NOT_FOUND' | 'EXISTING_REVISIONS' | string;
7
+ message: string;
8
+ }
9
+
10
+ export interface RedlineOptions {
11
+ generateRedlines?: boolean;
12
+ author?: string;
13
+ targetParagraphId?: string | null;
14
+ existingRevisions?: ExistingRevisionsPolicy;
15
+ [key: string]: unknown;
16
+ }
17
+
18
+ export interface RedlineResult {
19
+ oxml: string;
20
+ hasChanges: boolean;
21
+ sourceType?: OoxmlSourceType;
22
+ status?: RedlineStatus;
23
+ error?: RedlineError;
24
+ warnings?: string[];
25
+ numberingXml?: string;
26
+ useNativeApi?: boolean;
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ export interface TableReconciliationResult extends RedlineResult {
31
+ isMarkdownTable: boolean;
32
+ tableData?: {
33
+ headers?: string[];
34
+ rows?: string[][];
35
+ [key: string]: unknown;
36
+ };
37
+ }
38
+
39
+ export interface RevisionFilterOptions {
40
+ author?: string;
41
+ allAuthors?: boolean;
42
+ }
43
+
44
+ export interface AcceptTrackedChangesResult {
45
+ oxml: string;
46
+ hasChanges: boolean;
47
+ acceptedCount: number;
48
+ warnings: string[];
49
+ }
50
+
51
+ export interface RejectTrackedChangesResult {
52
+ oxml: string;
53
+ hasChanges: boolean;
54
+ rejectedCount: number;
55
+ warnings: string[];
56
+ }
57
+
58
+ export interface DeleteCommentsResult {
59
+ oxml: string;
60
+ hasChanges: boolean;
61
+ commentsRemoved: number;
62
+ referencesRemoved: number;
63
+ warnings: string[];
64
+ }
65
+
66
+ export interface XmlProvider {
67
+ DOMParser: typeof DOMParser;
68
+ XMLSerializer: typeof XMLSerializer;
69
+ }
70
+
71
+ export interface LoggerConfig {
72
+ log?: (...args: unknown[]) => void;
73
+ warn?: (...args: unknown[]) => void;
74
+ error?: (...args: unknown[]) => void;
75
+ }
76
+
77
+ export interface DocumentOperationResult {
78
+ documentXml: string;
79
+ hasChanges: boolean;
80
+ numberingXml?: string | null;
81
+ commentsXml?: string | null;
82
+ warnings?: string[];
83
+ status?: RedlineStatus;
84
+ error?: RedlineError;
85
+ }
86
+
87
+ export function configureXmlProvider(provider: XmlProvider): void;
88
+ export function configureLogger(logger: LoggerConfig): void;
89
+ export function setDefaultAuthor(name: string): void;
90
+ export function getDefaultAuthor(): string;
91
+ export function setPlatform(label: string): void;
92
+ export function getPlatform(): string;
93
+
94
+ export function applyRedlineToOxml(
95
+ oxml: string,
96
+ originalText: string,
97
+ modifiedText: string,
98
+ options?: RedlineOptions
99
+ ): Promise<RedlineResult>;
100
+
101
+ export function applyRedlineToOxmlWithListFallback(
102
+ oxml: string,
103
+ originalText: string,
104
+ modifiedText: string,
105
+ options?: RedlineOptions
106
+ ): Promise<RedlineResult>;
107
+
108
+ export function reconcileMarkdownTableOoxml(
109
+ oxml: string,
110
+ originalText: string,
111
+ markdownTable: string,
112
+ options?: RedlineOptions
113
+ ): Promise<TableReconciliationResult>;
114
+
115
+ export function ingestWordOoxmlToPlainText(oxml: string): string;
116
+ export function ingestWordOoxmlToMarkdown(oxml: string): string;
117
+ export function ingestOoxml(oxml: string): unknown;
118
+ export function preprocessMarkdown(text: string): { cleanText: string; formatHints: unknown[] };
119
+ export function serializeToOoxml(runModel: unknown[], pPrXml?: string | null, formatHints?: unknown[], options?: Record<string, unknown>): string;
120
+ export function wrapInDocumentFragment(rawOoxml: string, options?: Record<string, unknown>): string;
121
+
122
+ export function injectCommentsIntoOoxml(oxml: string, comments: unknown[], options?: Record<string, unknown>): unknown;
123
+ export function injectCommentsIntoPackage(packageXml: string, comments: unknown[], options?: Record<string, unknown>): unknown;
124
+ export function acceptTrackedChangesInOoxml(oxml: string, options?: RevisionFilterOptions): AcceptTrackedChangesResult;
125
+ export function rejectTrackedChangesInOoxml(oxml: string, options?: RevisionFilterOptions): RejectTrackedChangesResult;
126
+ export function deleteCommentsByAuthorInOoxml(oxml: string, options?: RevisionFilterOptions): DeleteCommentsResult;
127
+ export function containsTrackedChanges(xmlDoc: Document | Element): boolean;
128
+
129
+ export type RedlineValidationSeverity = 'error' | 'warning';
130
+
131
+ export interface RedlineValidationIssue {
132
+ code:
133
+ | 'PARSE_ERROR'
134
+ | 'NESTED_REVISION'
135
+ | 'DEL_CONTAINS_T'
136
+ | 'MISSING_REVISION_METADATA'
137
+ | 'DUPLICATE_REVISION_ID'
138
+ | 'MISSING_SPACE_PRESERVE'
139
+ | 'EMPTY_TEXT_ELEMENT'
140
+ | 'EMPTY_REVISION_WRAPPER'
141
+ | string;
142
+ severity: RedlineValidationSeverity;
143
+ message: string;
144
+ }
145
+
146
+ export interface RedlineValidationResult {
147
+ valid: boolean;
148
+ issues: RedlineValidationIssue[];
149
+ }
150
+
151
+ export function validateRedlineOoxml(oxml: string): RedlineValidationResult;
152
+
153
+ export function applyHighlightToOoxml(oxml: string, targetText: string, color: string, options?: Record<string, unknown>): string;
154
+ export function generateTableOoxml(headersOrData: unknown, rowsOrOptions?: unknown, options?: Record<string, unknown>): string;
155
+ export function extractReplacementNodesFromOoxml(oxml: string): unknown;
156
+ export function validateDocxPackage(zip: unknown): Promise<unknown> | unknown;
157
+ export function ensureNumberingArtifactsInZip(zip: unknown, numberingXml: string): Promise<unknown> | unknown;
158
+ export function ensureCommentsArtifactsInZip(zip: unknown, commentsXml: string): Promise<unknown> | unknown;
159
+ export function createDynamicNumberingIdState(numberingXml?: string): unknown;
160
+
161
+ export function parseOoxml(ooxml: string): Document;
162
+ export function serializeOoxml(node: Node): string;
163
+ export function sanitizeAiResponse(text: string): string;
164
+
165
+ export class ReconciliationPipeline {
166
+ constructor(options?: Record<string, unknown>);
167
+ execute(oxml: string, modifiedText: string, options?: Record<string, unknown>): Promise<unknown>;
168
+ }
169
+
170
+ export class NumberingService {
171
+ constructor(...args: unknown[]);
172
+ }