@ansonlai/docx-redline-js 0.1.6 → 0.2.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/ARCHITECTURE.md +19 -3
- package/README.md +15 -0
- package/core/redline-validation.js +156 -0
- package/dist/docx-redline-js.esm.js +152 -31
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +62 -62
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/VALIDATION.md +79 -23
- package/docs/plans/2026-05-31-architectural changes.md +591 -0
- package/engine/formatting-removal.js +14 -8
- package/engine/run-builders.js +14 -10
- package/engine/surgical-diff-application.js +7 -21
- package/index.d.ts +24 -0
- package/index.js +35 -34
- package/package.json +5 -4
- package/scripts/build.mjs +10 -5
- package/scripts/check-types.mjs +2 -1
- package/scripts/export-validation-fixtures.mjs +73 -16
- package/scripts/lib/minimal-zip.mjs +155 -0
- package/scripts/validate-fixtures-xsd.sh +37 -0
- package/scripts/word-com-differential.ps1 +133 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$FixturesDir = "tmp/validation-docx"
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
# Differential validation against desktop Microsoft Word (the authoritative
|
|
6
|
+
# OOXML consumer). For each generated fixture, Word itself accepts all
|
|
7
|
+
# revisions and then rejects all revisions, and the resulting document text
|
|
8
|
+
# is compared to the expected outcomes recorded at generation time. This
|
|
9
|
+
# makes Word an independent oracle for the redline engine instead of
|
|
10
|
+
# verifying the library against its own accept/reject transforms.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# node scripts/export-validation-fixtures.mjs
|
|
14
|
+
# npm run smoke:word:diff
|
|
15
|
+
|
|
16
|
+
$ErrorActionPreference = 'Stop'
|
|
17
|
+
|
|
18
|
+
function Get-NormalizedText([string]$text) {
|
|
19
|
+
if ($null -eq $text) { return '' }
|
|
20
|
+
$text = $text -replace [string][char]7, ' ' # table cell markers
|
|
21
|
+
return ($text -replace '\s+', ' ').Trim()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$resolvedDir = Resolve-Path -LiteralPath $FixturesDir -ErrorAction SilentlyContinue
|
|
25
|
+
if (-not $resolvedDir) {
|
|
26
|
+
Write-Error "Fixtures directory '$FixturesDir' not found. Run: node scripts/export-validation-fixtures.mjs"
|
|
27
|
+
exit 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
$expectations = Get-ChildItem -LiteralPath $resolvedDir -Filter '*.expected.json' | Sort-Object Name
|
|
31
|
+
if ($expectations.Count -eq 0) {
|
|
32
|
+
Write-Error "No *.expected.json fixtures in '$resolvedDir'. Run: node scripts/export-validation-fixtures.mjs"
|
|
33
|
+
exit 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
$word = $null
|
|
37
|
+
$failures = 0
|
|
38
|
+
$results = @()
|
|
39
|
+
|
|
40
|
+
function Open-FixtureDocument($word, [string]$path) {
|
|
41
|
+
# Single-argument Open: Windows PowerShell 5.1 COM binding rejects the
|
|
42
|
+
# long optional-parameter signature. Defaults leave the document
|
|
43
|
+
# writable, which accept/reject requires; nothing is ever saved.
|
|
44
|
+
return $word.Documents.Open($path)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
$word = New-Object -ComObject Word.Application
|
|
49
|
+
$word.Visible = $false
|
|
50
|
+
$word.DisplayAlerts = 0 # wdAlertsNone
|
|
51
|
+
|
|
52
|
+
foreach ($expectationFile in $expectations) {
|
|
53
|
+
$name = $expectationFile.BaseName -replace '\.expected$', ''
|
|
54
|
+
$docxPath = Join-Path $resolvedDir "$name.docx"
|
|
55
|
+
if (-not (Test-Path -LiteralPath $docxPath)) {
|
|
56
|
+
Write-Warning "SKIP ${name}: no matching .docx"
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# -Encoding UTF8 is required: Windows PowerShell 5.1 otherwise reads
|
|
61
|
+
# BOM-less UTF-8 sidecars as ANSI and garbles non-ASCII expectations.
|
|
62
|
+
$expected = Get-Content -LiteralPath $expectationFile.FullName -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
63
|
+
$expectedAccepted = Get-NormalizedText $expected.expectedAcceptedText
|
|
64
|
+
$expectedRejected = Get-NormalizedText $expected.expectedRejectedText
|
|
65
|
+
$caseFailed = $false
|
|
66
|
+
$document = $null
|
|
67
|
+
|
|
68
|
+
# Phase 1: open cleanly, revisions present, accept-all matches intent.
|
|
69
|
+
try {
|
|
70
|
+
$document = Open-FixtureDocument $word ([string]$docxPath)
|
|
71
|
+
$revisionCount = $document.Revisions.Count
|
|
72
|
+
if ($revisionCount -lt 1) {
|
|
73
|
+
Write-Output "FAIL ${name}: Word sees no tracked revisions"
|
|
74
|
+
$caseFailed = $true
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
$document.AcceptAllRevisions()
|
|
78
|
+
$acceptedText = Get-NormalizedText $document.Content.Text
|
|
79
|
+
if ($acceptedText -ne $expectedAccepted) {
|
|
80
|
+
Write-Output "FAIL ${name}: accept-all mismatch"
|
|
81
|
+
Write-Output " expected: $expectedAccepted"
|
|
82
|
+
Write-Output " actual: $acceptedText"
|
|
83
|
+
$caseFailed = $true
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
Write-Output "FAIL ${name}: Word could not open/accept: $($_.Exception.Message)"
|
|
89
|
+
$caseFailed = $true
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
if ($null -ne $document) { $document.Close(0) | Out-Null; $document = $null }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Phase 2: fresh open, reject-all restores the original text.
|
|
96
|
+
if (-not $caseFailed) {
|
|
97
|
+
try {
|
|
98
|
+
$document = Open-FixtureDocument $word ([string]$docxPath)
|
|
99
|
+
$document.RejectAllRevisions()
|
|
100
|
+
$rejectedText = Get-NormalizedText $document.Content.Text
|
|
101
|
+
if ($rejectedText -ne $expectedRejected) {
|
|
102
|
+
Write-Output "FAIL ${name}: reject-all mismatch"
|
|
103
|
+
Write-Output " expected: $expectedRejected"
|
|
104
|
+
Write-Output " actual: $rejectedText"
|
|
105
|
+
$caseFailed = $true
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
Write-Output "FAIL ${name}: Word could not open/reject: $($_.Exception.Message)"
|
|
110
|
+
$caseFailed = $true
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
if ($null -ne $document) { $document.Close(0) | Out-Null; $document = $null }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if ($caseFailed) {
|
|
118
|
+
$failures++
|
|
119
|
+
$results += "FAIL $name"
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
Write-Output "PASS ${name} (revisions: $revisionCount)"
|
|
123
|
+
$results += "PASS $name"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if ($null -ne $word) { $word.Quit() | Out-Null }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Write-Output ""
|
|
132
|
+
Write-Output "Word differential: $($results.Count - $failures)/$($results.Count) fixtures passed."
|
|
133
|
+
if ($failures -gt 0) { exit 1 }
|