@ansonlai/docx-redline-js 0.1.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/AGENTS.md +176 -0
- package/ARCHITECTURE.md +121 -0
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/adapters/config.js +43 -0
- package/adapters/logger.js +89 -0
- package/adapters/xml-adapter.js +74 -0
- package/core/list-targeting.js +398 -0
- package/core/ooxml-identifiers.js +15 -0
- package/core/paragraph-offset-policy.js +50 -0
- package/core/paragraph-targeting.js +501 -0
- package/core/table-targeting.js +233 -0
- package/core/types.js +204 -0
- package/core/xml-query.js +99 -0
- package/dist/docx-redline-js.esm.js +8801 -0
- package/dist/docx-redline-js.esm.js.map +7 -0
- package/dist/docx-redline-js.esm.min.js +195 -0
- package/dist/docx-redline-js.esm.min.js.map +7 -0
- package/engine/format-application.js +358 -0
- package/engine/format-extraction.js +232 -0
- package/engine/format-paragraph-targeting.js +208 -0
- package/engine/format-span-application.js +178 -0
- package/engine/formatting-removal.js +330 -0
- package/engine/oxml-engine.js +279 -0
- package/engine/reconstruction-mapper.js +270 -0
- package/engine/reconstruction-mode.js +38 -0
- package/engine/reconstruction-writer.js +276 -0
- package/engine/rpr-helpers.js +194 -0
- package/engine/run-builders.js +235 -0
- package/engine/surgical-mode.js +520 -0
- package/engine/table-cell-context.js +151 -0
- package/engine/table-mode.js +172 -0
- package/index.js +308 -0
- package/orchestration/list-markdown.js +141 -0
- package/orchestration/list-parsing.js +73 -0
- package/orchestration/list-structural-fallback.js +530 -0
- package/orchestration/redline-operation-converter.js +141 -0
- package/orchestration/route-plan.js +160 -0
- package/package.json +76 -0
- package/pipeline/content-analysis.js +107 -0
- package/pipeline/diff-engine.js +204 -0
- package/pipeline/ingestion-export.js +255 -0
- package/pipeline/ingestion-paragraph.js +351 -0
- package/pipeline/ingestion-table.js +169 -0
- package/pipeline/ingestion-xml.js +39 -0
- package/pipeline/ingestion.js +8 -0
- package/pipeline/list-generation.js +280 -0
- package/pipeline/list-markers.js +77 -0
- package/pipeline/markdown-processor.js +160 -0
- package/pipeline/patching.js +408 -0
- package/pipeline/pipeline.js +326 -0
- package/pipeline/serialization.js +395 -0
- package/services/browser-demo-prompt-context.js +345 -0
- package/services/comment-builders.js +60 -0
- package/services/comment-engine.js +248 -0
- package/services/comment-locator.js +197 -0
- package/services/comment-package.js +113 -0
- package/services/numbering-helpers.js +416 -0
- package/services/numbering-service.js +290 -0
- package/services/package-builder.js +147 -0
- package/services/standalone-docx-plumbing.js +443 -0
- package/services/standalone-operation-runner.js +1169 -0
- package/services/table-reconciliation.js +344 -0
- package/standalone.js +5 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# AGENTS.md - AI Agent Quick Reference
|
|
2
|
+
|
|
3
|
+
> This file helps AI coding agents understand @ansonlai/docx-redline-js quickly.
|
|
4
|
+
> Read this instead of exploring the full source tree.
|
|
5
|
+
|
|
6
|
+
## What This Package Does
|
|
7
|
+
|
|
8
|
+
Converts text/markdown edits into valid Office Open XML (OOXML) with Word-native tracked changes. Feed it original OOXML + desired text and it returns OOXML with `w:ins`/`w:del` revision markup.
|
|
9
|
+
|
|
10
|
+
## Conceptual Model
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Input: (paragraph OOXML, original text, modified text, options)
|
|
14
|
+
|
|
|
15
|
+
v
|
|
16
|
+
Engine routes to: format-only | surgical | reconstruction | list | table mode
|
|
17
|
+
|
|
|
18
|
+
v
|
|
19
|
+
Output: { oxml: string, hasChanges: boolean, warnings?: string[] }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The engine works at paragraph scope. For full-document operations, callers iterate paragraph targets or use the standalone operation runner.
|
|
23
|
+
|
|
24
|
+
## Entry Point
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { applyRedlineToOxml, configureXmlProvider } from '@ansonlai/docx-redline-js';
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`index.js` is the single package entry point.
|
|
31
|
+
|
|
32
|
+
## Required Setup (Node.js only)
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
|
|
36
|
+
configureXmlProvider({ DOMParser, XMLSerializer });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Browsers have native DOM APIs, so no provider injection is typically needed.
|
|
40
|
+
|
|
41
|
+
## Key APIs by Use Case
|
|
42
|
+
|
|
43
|
+
### Apply a text edit with tracked changes
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
|
|
47
|
+
generateRedlines: true,
|
|
48
|
+
author: 'Agent Name'
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Apply a text edit without tracked changes
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
const result = await applyRedlineToOxml(oxml, originalText, modifiedText, {
|
|
56
|
+
generateRedlines: false
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Convert OOXML to readable text or markdown
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { ingestWordOoxmlToPlainText, ingestWordOoxmlToMarkdown } from '@ansonlai/docx-redline-js';
|
|
64
|
+
const plainText = ingestWordOoxmlToPlainText(documentXml);
|
|
65
|
+
const markdown = ingestWordOoxmlToMarkdown(documentXml);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Add a comment to OOXML
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { injectCommentsIntoOoxml } from '@ansonlai/docx-redline-js';
|
|
72
|
+
const result = injectCommentsIntoOoxml(paragraphOoxml, [
|
|
73
|
+
{ text: 'Review this clause', targetText: 'force majeure', author: 'Agent' }
|
|
74
|
+
]);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Apply multiple operations to full document XML
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { applyOperationToDocumentXml } from '@ansonlai/docx-redline-js/services/standalone-operation-runner.js';
|
|
81
|
+
const result = await applyOperationToDocumentXml(documentXml, operation, options);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Convert paragraph text into a Word list
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const result = await applyRedlineToOxml(oxml, 'Item text', '1. Item text', {
|
|
88
|
+
generateRedlines: true
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Reconcile a table
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
import { reconcileMarkdownTableOoxml } from '@ansonlai/docx-redline-js';
|
|
96
|
+
const result = await reconcileMarkdownTableOoxml(tableOoxml, originalText, markdownTable);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Module Map
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
index.js
|
|
103
|
+
adapters/
|
|
104
|
+
config.js
|
|
105
|
+
xml-adapter.js
|
|
106
|
+
logger.js
|
|
107
|
+
core/
|
|
108
|
+
types.js
|
|
109
|
+
paragraph-targeting.js
|
|
110
|
+
list-targeting.js
|
|
111
|
+
table-targeting.js
|
|
112
|
+
engine/
|
|
113
|
+
oxml-engine.js
|
|
114
|
+
surgical-mode.js
|
|
115
|
+
reconstruction-mode.js
|
|
116
|
+
format-application.js
|
|
117
|
+
formatting-removal.js
|
|
118
|
+
table-mode.js
|
|
119
|
+
pipeline/
|
|
120
|
+
pipeline.js
|
|
121
|
+
ingestion.js
|
|
122
|
+
ingestion-export.js
|
|
123
|
+
diff-engine.js
|
|
124
|
+
markdown-processor.js
|
|
125
|
+
serialization.js
|
|
126
|
+
list-generation.js
|
|
127
|
+
services/
|
|
128
|
+
standalone-operation-runner.js
|
|
129
|
+
standalone-docx-plumbing.js
|
|
130
|
+
numbering-helpers.js
|
|
131
|
+
comment-engine.js
|
|
132
|
+
table-reconciliation.js
|
|
133
|
+
package-builder.js
|
|
134
|
+
orchestration/
|
|
135
|
+
route-plan.js
|
|
136
|
+
list-markdown.js
|
|
137
|
+
list-structural-fallback.js
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Common Patterns
|
|
141
|
+
|
|
142
|
+
### Options shape
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
{
|
|
146
|
+
generateRedlines: true,
|
|
147
|
+
author: 'Name'
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Typical return shape
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
{
|
|
155
|
+
oxml: string,
|
|
156
|
+
hasChanges: boolean,
|
|
157
|
+
warnings?: string[],
|
|
158
|
+
numberingXml?: string,
|
|
159
|
+
useNativeApi?: boolean
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### OOXML wrapping for Word insertOoxml scenarios
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
import { wrapInDocumentFragment } from '@ansonlai/docx-redline-js';
|
|
167
|
+
const wrapped = wrapInDocumentFragment(rawOoxml, { includeNumbering: true, numberingXml });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Gotchas
|
|
171
|
+
|
|
172
|
+
1. Call `configureXmlProvider` first in Node.js.
|
|
173
|
+
2. `applyRedlineToOxml` is async.
|
|
174
|
+
3. Paragraph APIs expect paragraph-level OOXML, not full `word/document.xml` in all cases.
|
|
175
|
+
4. List operations may return `numberingXml` that must be merged into package parts.
|
|
176
|
+
5. `useNativeApi: true` means standalone mode cannot fully handle that operation path.
|
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Reconciliation Core Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the standalone, host-agnostic OOXML reconciliation package and how to work inside it safely.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This repository contains only the publishable package surface:
|
|
8
|
+
|
|
9
|
+
- `adapters/`
|
|
10
|
+
- `core/`
|
|
11
|
+
- `engine/`
|
|
12
|
+
- `pipeline/`
|
|
13
|
+
- `services/`
|
|
14
|
+
- `orchestration/`
|
|
15
|
+
- `index.js`
|
|
16
|
+
- `standalone.js`
|
|
17
|
+
|
|
18
|
+
No Word add-in entrypoints or host-specific integration layers are part of this package.
|
|
19
|
+
|
|
20
|
+
## Goals
|
|
21
|
+
|
|
22
|
+
- Preserve Word-compatible redlines by editing OOXML directly.
|
|
23
|
+
- Keep core logic host-independent (no Office.js globals, no Word API calls).
|
|
24
|
+
- Reuse the same engine in browser, Node.js, and other JavaScript runtimes.
|
|
25
|
+
|
|
26
|
+
## Folder Layout
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
.
|
|
30
|
+
├── adapters/
|
|
31
|
+
│ ├── config.js
|
|
32
|
+
│ ├── logger.js
|
|
33
|
+
│ └── xml-adapter.js
|
|
34
|
+
├── core/
|
|
35
|
+
├── engine/
|
|
36
|
+
│ └── formatting-removal.js
|
|
37
|
+
├── orchestration/
|
|
38
|
+
├── pipeline/
|
|
39
|
+
├── services/
|
|
40
|
+
│ ├── numbering-helpers.js
|
|
41
|
+
│ ├── standalone-docx-plumbing.js
|
|
42
|
+
│ └── standalone-operation-runner.js
|
|
43
|
+
├── index.js
|
|
44
|
+
└── standalone.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Entry Points
|
|
48
|
+
|
|
49
|
+
- `index.js`: primary package entrypoint.
|
|
50
|
+
- `standalone.js`: compatibility alias that re-exports from `index.js` (deprecated for new imports).
|
|
51
|
+
|
|
52
|
+
## Module Responsibilities
|
|
53
|
+
|
|
54
|
+
- `adapters/config.js`
|
|
55
|
+
- Runtime configuration for defaults (`setDefaultAuthor`, `getDefaultAuthor`, `setPlatform`, `getPlatform`).
|
|
56
|
+
- `adapters/xml-adapter.js`
|
|
57
|
+
- XML parser/serializer injection for browser or Node.js runtimes.
|
|
58
|
+
- `adapters/logger.js`
|
|
59
|
+
- Runtime logger injection and shared logging methods.
|
|
60
|
+
- `core/*`
|
|
61
|
+
- Shared types, OOXML identity helpers, target resolution, list/table targeting heuristics, and XML query helpers.
|
|
62
|
+
- `engine/oxml-engine.js`
|
|
63
|
+
- Main reconciliation router and mode selection.
|
|
64
|
+
- `engine/formatting-removal.js`
|
|
65
|
+
- Shared formatting removal and highlight helpers.
|
|
66
|
+
- `pipeline/*`
|
|
67
|
+
- Ingestion, markdown preprocessing, diffing, patching, and serialization stages.
|
|
68
|
+
- `services/numbering-helpers.js`
|
|
69
|
+
- Dynamic numbering ID allocation, numbering payload remapping, and schema-order-safe numbering merges.
|
|
70
|
+
- `services/standalone-docx-plumbing.js`
|
|
71
|
+
- Package-level extraction/wiring/validation for `word/document.xml`, `word/numbering.xml`, and `word/comments.xml`.
|
|
72
|
+
- `services/standalone-operation-runner.js`
|
|
73
|
+
- Host-agnostic operation bridge for `redline`, `highlight`, and `comment` workflows.
|
|
74
|
+
- `orchestration/*`
|
|
75
|
+
- Route planning and list fallback orchestration utilities.
|
|
76
|
+
|
|
77
|
+
## End-to-End Flow
|
|
78
|
+
|
|
79
|
+
1. Caller imports from `index.js` (or legacy `standalone.js` alias).
|
|
80
|
+
2. Caller configures XML provider/logger/defaults when needed via `adapters/*`.
|
|
81
|
+
3. Caller invokes reconciliation APIs (`applyRedlineToOxml`, operation runner, ingestion/export helpers).
|
|
82
|
+
4. `engine/oxml-engine.js` routes to format, table, list, surgical, or reconstruction flows.
|
|
83
|
+
5. Pipeline/services return OOXML and optional package artifacts (`numberingXml`, comments payloads).
|
|
84
|
+
6. Caller writes resulting XML back to package/document boundaries.
|
|
85
|
+
|
|
86
|
+
## Public Surfaces
|
|
87
|
+
|
|
88
|
+
- Primary: `index.js`
|
|
89
|
+
- Compatibility alias: `standalone.js` (deprecated)
|
|
90
|
+
|
|
91
|
+
Keep exports centralized through `index.js`; maintain `standalone.js` only for backward compatibility.
|
|
92
|
+
|
|
93
|
+
## Build Output
|
|
94
|
+
|
|
95
|
+
`npm run build` generates CDN-ready ESM bundles under `dist/`:
|
|
96
|
+
|
|
97
|
+
- `dist/docx-redline-js.esm.js`
|
|
98
|
+
- `dist/docx-redline-js.esm.js.map`
|
|
99
|
+
- `dist/docx-redline-js.esm.min.js`
|
|
100
|
+
- `dist/docx-redline-js.esm.min.js.map`
|
|
101
|
+
|
|
102
|
+
The bundle inlines `diff-match-patch` and keeps `@xmldom/xmldom` external.
|
|
103
|
+
|
|
104
|
+
## Testing
|
|
105
|
+
|
|
106
|
+
- `npm test`
|
|
107
|
+
- Runs the package test runner (`scripts/run-tests.mjs`) against all `tests/*.mjs` except setup helpers.
|
|
108
|
+
- `npm run test:isolation`
|
|
109
|
+
- Runs boundary checks for Word API markers and dependency-graph isolation.
|
|
110
|
+
|
|
111
|
+
Use these checks before publishing or tagging.
|
|
112
|
+
|
|
113
|
+
## Fast Orientation For Contributors
|
|
114
|
+
|
|
115
|
+
Use this sequence to understand or modify behavior without reading everything:
|
|
116
|
+
|
|
117
|
+
1. Start at `index.js` to locate the exported API.
|
|
118
|
+
2. Follow exports into `engine/oxml-engine.js` or relevant `services/*` module.
|
|
119
|
+
3. For targeting bugs, inspect `core/paragraph-targeting.js`, `core/list-targeting.js`, and `core/table-targeting.js`.
|
|
120
|
+
4. For package wiring issues, inspect `services/standalone-docx-plumbing.js`.
|
|
121
|
+
5. For numbering/list issues, inspect `services/numbering-helpers.js` and orchestration list-fallback modules.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anson Lai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# @ansonlai/docx-redline-js
|
|
2
|
+
|
|
3
|
+
Host-independent OOXML reconciliation engine for `.docx` manipulation with track changes (redlines).
|
|
4
|
+
|
|
5
|
+
Converts AI-generated or programmatic text/markdown edits into valid Office Open XML (OOXML) with `w:ins`/`w:del` revision markup that Microsoft Word renders as native tracked changes.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Text reconciliation with word-level diffing and native-looking redlines
|
|
10
|
+
- Formatting updates (bold, italic, underline, strikethrough) via surgical `w:rPrChange`
|
|
11
|
+
- Lists: generate and edit real Word lists (`w:numPr`) from markdown
|
|
12
|
+
- Tables: virtual-grid diffing for cell-level edits with merge safety
|
|
13
|
+
- Comments: inject OOXML comments anchored to text ranges
|
|
14
|
+
- Highlights: apply highlight colors to runs
|
|
15
|
+
- Markdown and OOXML conversion in both directions
|
|
16
|
+
- Package plumbing helpers for numbering.xml, comments.xml, content types, and relationships
|
|
17
|
+
- Zero host dependencies: works in Node.js, browsers, Deno, and similar JS runtimes with DOM parsing support
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
### npm / Node.js
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @ansonlai/docx-redline-js
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### CDN (browser `<script type="module">`)
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<script type="module">
|
|
31
|
+
import { applyRedlineToOxml } from 'https://esm.sh/@ansonlai/docx-redline-js';
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or use the pre-bundled file (no import map needed, `diff-match-patch` is inlined):
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<script type="module">
|
|
39
|
+
import { applyRedlineToOxml } from 'https://cdn.jsdelivr.net/npm/@ansonlai/docx-redline-js/dist/docx-redline-js.esm.min.js';
|
|
40
|
+
</script>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Local git clone
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/YOUR_ORG/docx-redline-js.git
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import { applyRedlineToOxml } from './docx-redline-js/index.js';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Node.js
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
|
|
59
|
+
import {
|
|
60
|
+
configureXmlProvider,
|
|
61
|
+
setDefaultAuthor,
|
|
62
|
+
applyRedlineToOxml
|
|
63
|
+
} from '@ansonlai/docx-redline-js';
|
|
64
|
+
|
|
65
|
+
configureXmlProvider({ DOMParser, XMLSerializer });
|
|
66
|
+
setDefaultAuthor('My App');
|
|
67
|
+
|
|
68
|
+
const result = await applyRedlineToOxml(
|
|
69
|
+
paragraphOoxml,
|
|
70
|
+
'Original sentence.',
|
|
71
|
+
'Updated sentence.',
|
|
72
|
+
{ generateRedlines: true, author: 'Editor' }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
console.log(result.hasChanges);
|
|
76
|
+
console.log(result.oxml);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Browser
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
import {
|
|
83
|
+
setDefaultAuthor,
|
|
84
|
+
applyRedlineToOxml
|
|
85
|
+
} from '@ansonlai/docx-redline-js';
|
|
86
|
+
|
|
87
|
+
setDefaultAuthor('Browser Editor');
|
|
88
|
+
|
|
89
|
+
const result = await applyRedlineToOxml(oxml, original, modified, {
|
|
90
|
+
generateRedlines: true
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API Reference
|
|
95
|
+
|
|
96
|
+
### Configuration (call once at startup)
|
|
97
|
+
|
|
98
|
+
| Function | Purpose |
|
|
99
|
+
|----------|---------|
|
|
100
|
+
| `configureXmlProvider({ DOMParser, XMLSerializer })` | Inject XML parser. Required in Node.js; browsers usually provide native support. |
|
|
101
|
+
| `configureLogger({ log, warn, error })` | Replace default console logger. |
|
|
102
|
+
| `setDefaultAuthor(name)` | Set fallback track-change author (default: `'Author'`). |
|
|
103
|
+
| `setPlatform(label)` | Set platform label for diagnostics (default: `'Unknown'`). |
|
|
104
|
+
|
|
105
|
+
### Engine (primary reconciliation APIs)
|
|
106
|
+
|
|
107
|
+
| Function | Purpose |
|
|
108
|
+
|----------|---------|
|
|
109
|
+
| `applyRedlineToOxml(oxml, original, modified, options)` | Core engine entry point for text/markdown reconciliation with optional redlines. |
|
|
110
|
+
| `applyRedlineToOxmlWithListFallback(oxml, original, modified, options)` | Core engine with automatic single-line list structural fallback. |
|
|
111
|
+
| `reconcileMarkdownTableOoxml(oxml, original, markdownTable, options)` | Table-specific reconciliation helper. |
|
|
112
|
+
|
|
113
|
+
### Pipeline (lower-level access)
|
|
114
|
+
|
|
115
|
+
| Function | Purpose |
|
|
116
|
+
|----------|---------|
|
|
117
|
+
| `ReconciliationPipeline` | Direct pipeline access (ingest, diff, patch, serialize). |
|
|
118
|
+
| `ingestWordOoxmlToPlainText(oxml)` | Extract plain text from OOXML. |
|
|
119
|
+
| `ingestWordOoxmlToMarkdown(oxml)` | Convert OOXML to markdown. |
|
|
120
|
+
| `ingestOoxml(oxml)` | Flatten OOXML into an internal run model with offsets. |
|
|
121
|
+
| `preprocessMarkdown(text)` | Normalize markdown and extract format hints. |
|
|
122
|
+
|
|
123
|
+
### Services
|
|
124
|
+
|
|
125
|
+
| Function | Purpose |
|
|
126
|
+
|----------|---------|
|
|
127
|
+
| `injectCommentsIntoOoxml(oxml, comments, options)` | Add comments anchored to text ranges. |
|
|
128
|
+
| `generateTableOoxml(headers, rows, options)` | Generate a `w:tbl` from tabular data. |
|
|
129
|
+
| `createDynamicNumberingIdState(numberingXml)` | Allocate numbering IDs without collisions. |
|
|
130
|
+
| `ensureNumberingArtifactsInZip(zip, numberingXml)` | Merge numbering artifacts into a `.docx` package. |
|
|
131
|
+
| `ensureCommentsArtifactsInZip(zip, commentsXml)` | Merge comments artifacts into a `.docx` package. |
|
|
132
|
+
| `validateDocxPackage(zip)` | Validate `.docx` structural consistency. |
|
|
133
|
+
|
|
134
|
+
### Deep Imports
|
|
135
|
+
|
|
136
|
+
For advanced usage, import specific submodules:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
import { applyOperationToDocumentXml } from '@ansonlai/docx-redline-js/services/standalone-operation-runner.js';
|
|
140
|
+
import { getParagraphText } from '@ansonlai/docx-redline-js/core/paragraph-targeting.js';
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Working With `.docx` Files
|
|
144
|
+
|
|
145
|
+
This package operates on OOXML strings (XML parts inside `.docx` zip archives), not raw `.docx` binaries.
|
|
146
|
+
|
|
147
|
+
Typical flow:
|
|
148
|
+
|
|
149
|
+
1. Extract the `.docx` zip (for example with JSZip, fflate, or similar)
|
|
150
|
+
2. Read `word/document.xml`
|
|
151
|
+
3. Apply reconciliation APIs to XML strings
|
|
152
|
+
4. Merge numbering/comments artifacts when needed
|
|
153
|
+
5. Write the archive back to a `.docx` file
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import JSZip from 'jszip';
|
|
157
|
+
import {
|
|
158
|
+
configureXmlProvider,
|
|
159
|
+
applyRedlineToOxml,
|
|
160
|
+
ensureNumberingArtifactsInZip,
|
|
161
|
+
validateDocxPackage
|
|
162
|
+
} from '@ansonlai/docx-redline-js';
|
|
163
|
+
|
|
164
|
+
const zip = await JSZip.loadAsync(docxBuffer);
|
|
165
|
+
const documentXml = await zip.file('word/document.xml').async('string');
|
|
166
|
+
|
|
167
|
+
// Apply edits with applyRedlineToOxml(...)
|
|
168
|
+
// Merge artifacts with ensureNumberingArtifactsInZip(...) as needed
|
|
169
|
+
|
|
170
|
+
const output = await zip.generateAsync({ type: 'nodebuffer' });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Architecture
|
|
174
|
+
|
|
175
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for module layout, data flow, and contributor guidance.
|
|
176
|
+
|
|
177
|
+
See [AGENTS.md](./AGENTS.md) for a concise reference for AI coding agents.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable runtime defaults for the reconciliation core.
|
|
3
|
+
* Callers can set these during bootstrap.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let _defaultAuthor = 'Author';
|
|
7
|
+
let _platform = 'Unknown';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set the default track-change author for revision metadata.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} author
|
|
13
|
+
*/
|
|
14
|
+
export function setDefaultAuthor(author) {
|
|
15
|
+
_defaultAuthor = typeof author === 'string' && author.trim() ? author.trim() : 'Author';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the current default track-change author.
|
|
20
|
+
*
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
export function getDefaultAuthor() {
|
|
24
|
+
return _defaultAuthor;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set the platform identifier (e.g. 'Win32', 'Mac', 'OfficeOnline').
|
|
29
|
+
*
|
|
30
|
+
* @param {string} platform
|
|
31
|
+
*/
|
|
32
|
+
export function setPlatform(platform) {
|
|
33
|
+
_platform = typeof platform === 'string' && platform.trim() ? platform.trim() : 'Unknown';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the current platform identifier.
|
|
38
|
+
*
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function getPlatform() {
|
|
42
|
+
return _platform;
|
|
43
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger adapter for reconciliation modules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
let _logger = console;
|
|
6
|
+
|
|
7
|
+
const LEVELS = Object.freeze({
|
|
8
|
+
silent: 0,
|
|
9
|
+
error: 1,
|
|
10
|
+
warn: 2,
|
|
11
|
+
info: 3
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const DEFAULT_LOG_LEVEL = (() => {
|
|
15
|
+
const isProd = typeof process !== 'undefined' && process?.env?.NODE_ENV === 'production';
|
|
16
|
+
return isProd ? 'warn' : 'info';
|
|
17
|
+
})();
|
|
18
|
+
|
|
19
|
+
let _logLevel = DEFAULT_LOG_LEVEL;
|
|
20
|
+
|
|
21
|
+
function normalizeLogLevel(level) {
|
|
22
|
+
const normalized = String(level || '').toLowerCase();
|
|
23
|
+
return Object.prototype.hasOwnProperty.call(LEVELS, normalized) ? normalized : _logLevel;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isEnabled(level) {
|
|
27
|
+
return LEVELS[_logLevel] >= LEVELS[level];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Configures logger implementation.
|
|
32
|
+
*
|
|
33
|
+
* @param {{log?: Function, warn?: Function, error?: Function}} logger - Logger object
|
|
34
|
+
* @param {{ level?: 'silent'|'error'|'warn'|'info' }} [options={}] - Logger options
|
|
35
|
+
*/
|
|
36
|
+
export function configureLogger(logger, options = {}) {
|
|
37
|
+
_logger = logger || console;
|
|
38
|
+
if (options.level) {
|
|
39
|
+
_logLevel = normalizeLogLevel(options.level);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the minimum log level for reconciliation logs.
|
|
45
|
+
*
|
|
46
|
+
* @param {'silent'|'error'|'warn'|'info'} level - Desired log level
|
|
47
|
+
*/
|
|
48
|
+
export function setLogLevel(level) {
|
|
49
|
+
_logLevel = normalizeLogLevel(level);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Gets current logger level.
|
|
54
|
+
*
|
|
55
|
+
* @returns {'silent'|'error'|'warn'|'info'}
|
|
56
|
+
*/
|
|
57
|
+
export function getLogLevel() {
|
|
58
|
+
return _logLevel;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Log passthrough.
|
|
63
|
+
*
|
|
64
|
+
* @param {...any} args - Log args
|
|
65
|
+
*/
|
|
66
|
+
export function log(...args) {
|
|
67
|
+
if (!isEnabled('info')) return;
|
|
68
|
+
(_logger.log || (() => { }))(...args);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Warn passthrough.
|
|
73
|
+
*
|
|
74
|
+
* @param {...any} args - Warn args
|
|
75
|
+
*/
|
|
76
|
+
export function warn(...args) {
|
|
77
|
+
if (!isEnabled('warn')) return;
|
|
78
|
+
(_logger.warn || (() => { }))(...args);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Error passthrough.
|
|
83
|
+
*
|
|
84
|
+
* @param {...any} args - Error args
|
|
85
|
+
*/
|
|
86
|
+
export function error(...args) {
|
|
87
|
+
if (!isEnabled('error')) return;
|
|
88
|
+
(_logger.error || (() => { }))(...args);
|
|
89
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML adapter for parser/serializer portability.
|
|
3
|
+
*
|
|
4
|
+
* Default behavior uses browser-provided DOMParser/XMLSerializer.
|
|
5
|
+
* Consumers can override these constructors for non-browser runtimes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let _DOMParser = globalThis.DOMParser;
|
|
9
|
+
let _XMLSerializer = globalThis.XMLSerializer;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configures XML provider constructors.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} [options={}] - Provider overrides
|
|
15
|
+
* @param {typeof DOMParser} [options.DOMParser] - DOMParser constructor
|
|
16
|
+
* @param {typeof XMLSerializer} [options.XMLSerializer] - XMLSerializer constructor
|
|
17
|
+
*/
|
|
18
|
+
export function configureXmlProvider(options = {}) {
|
|
19
|
+
if (options.DOMParser) _DOMParser = options.DOMParser;
|
|
20
|
+
if (options.XMLSerializer) _XMLSerializer = options.XMLSerializer;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a parser instance.
|
|
25
|
+
*
|
|
26
|
+
* @returns {DOMParser}
|
|
27
|
+
*/
|
|
28
|
+
export function createParser() {
|
|
29
|
+
if (!_DOMParser && globalThis.DOMParser) {
|
|
30
|
+
_DOMParser = globalThis.DOMParser;
|
|
31
|
+
}
|
|
32
|
+
if (!_DOMParser) {
|
|
33
|
+
throw new Error('DOMParser is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.');
|
|
34
|
+
}
|
|
35
|
+
return new _DOMParser();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a serializer instance.
|
|
40
|
+
*
|
|
41
|
+
* @returns {XMLSerializer}
|
|
42
|
+
*/
|
|
43
|
+
export function createSerializer() {
|
|
44
|
+
if (!_XMLSerializer && globalThis.XMLSerializer) {
|
|
45
|
+
_XMLSerializer = globalThis.XMLSerializer;
|
|
46
|
+
}
|
|
47
|
+
if (!_XMLSerializer) {
|
|
48
|
+
throw new Error('XMLSerializer is not configured. Call configureXmlProvider({ DOMParser, XMLSerializer }) first.');
|
|
49
|
+
}
|
|
50
|
+
return new _XMLSerializer();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parses XML text into a DOM document.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} xmlString - XML string
|
|
57
|
+
* @param {string} [contentType='text/xml'] - MIME type
|
|
58
|
+
* @returns {Document}
|
|
59
|
+
*/
|
|
60
|
+
export function parseXml(xmlString, contentType = 'text/xml') {
|
|
61
|
+
const parser = createParser();
|
|
62
|
+
return parser.parseFromString(xmlString, contentType);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Serializes a node to XML text.
|
|
67
|
+
*
|
|
68
|
+
* @param {Node} node - Node to serialize
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
export function serializeXml(node) {
|
|
72
|
+
const serializer = createSerializer();
|
|
73
|
+
return serializer.serializeToString(node);
|
|
74
|
+
}
|