@stll/folio-core 0.32.2 → 0.33.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/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +652 -86
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +219 -15
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
- package/dist/compare/__fixtures__/body-sequence.js +136 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +412 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1141 -0
- package/dist/compare/reproducible-package.d.ts +21 -0
- package/dist/compare/reproducible-package.js +47 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +297 -0
- package/dist/compare/types.js +42 -0
- package/dist/compare/verification.d.ts +83 -0
- package/dist/compare/verification.js +189 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +101 -26
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +27 -0
- package/dist/docx/revisionIdNormalization.js +121 -0
- package/dist/docx/rezip.d.ts +6 -0
- package/dist/docx/rezip.js +51 -19
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +73 -20
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +26 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +96 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { FolioAIBlockParagraphProperties, FolioAIBlockTableLocation, FolioAIEditSkippedOperation, FolioAIInlineFormatting } from "../ai-edits/types.js";
|
|
2
|
+
import { WordDiffGranularity } from "../ai-edits/word-diff.js";
|
|
3
|
+
import { FolioDocumentStoryHandle, FolioNumberingLevel } from "../ai-edits/headless.js";
|
|
4
|
+
import { CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkDeletion } from "./verification.js";
|
|
5
|
+
//#region src/compare/types.d.ts
|
|
6
|
+
/** Everything {@link compareDocx} needs; nothing it reads from the ambient clock. */
|
|
7
|
+
type CompareDocxOptions = {
|
|
8
|
+
/** Author recorded on every generated tracked change. */
|
|
9
|
+
author: string;
|
|
10
|
+
/**
|
|
11
|
+
* ISO-8601 date stamped on every generated tracked change. Required rather
|
|
12
|
+
* than defaulted so a caller cannot get a nondeterministic package by
|
|
13
|
+
* omission; pass the base document's own timestamp, a release date, or a
|
|
14
|
+
* fixed epoch.
|
|
15
|
+
*/
|
|
16
|
+
timestamp: string;
|
|
17
|
+
/**
|
|
18
|
+
* What to do when the round-trip self-check cannot prove the result.
|
|
19
|
+
*
|
|
20
|
+
* `"refuse"` (default) returns a {@link CompareDocxRoundTripError}: a
|
|
21
|
+
* redline that reads plausibly and is wrong is worse than no redline.
|
|
22
|
+
* `"emit"` returns the redline anyway, with `verification` naming every
|
|
23
|
+
* invariant that did not hold, for a caller that would rather show its best
|
|
24
|
+
* attempt and say what is missing.
|
|
25
|
+
*/
|
|
26
|
+
onUnverified?: "refuse" | "emit";
|
|
27
|
+
/**
|
|
28
|
+
* Token size a changed paragraph's redline is cut at: `"word"` (default)
|
|
29
|
+
* marks whole words, `"character"` marks the changed letters inside one.
|
|
30
|
+
*
|
|
31
|
+
* Case and whitespace normalization are not options here, though
|
|
32
|
+
* `diffWordSegments` offers them: a comparison that leaves a difference
|
|
33
|
+
* unmarked does not accept back to the target, and the round trip is the
|
|
34
|
+
* one thing this call promises.
|
|
35
|
+
*/
|
|
36
|
+
granularity?: WordDiffGranularity;
|
|
37
|
+
};
|
|
38
|
+
/** Where one change sits in the base or target document. */
|
|
39
|
+
type CompareChangeLocation = {
|
|
40
|
+
story: FolioDocumentStoryHandle;
|
|
41
|
+
/** Innermost table cell, when the change is inside a table. */
|
|
42
|
+
cell?: FolioAIBlockTableLocation;
|
|
43
|
+
};
|
|
44
|
+
/** One run of characters whose inline formatting differs, in base-block offsets. */
|
|
45
|
+
type CompareFormatRange = {
|
|
46
|
+
startOffset: number;
|
|
47
|
+
endOffset: number;
|
|
48
|
+
/** Only the properties that differ, set to the target document's value. */
|
|
49
|
+
formatting: FolioAIInlineFormatting;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* One difference between the two documents, in target-document order with
|
|
53
|
+
* base-only entries slotted where they sat. Every variant is plain JSON, so a
|
|
54
|
+
* caller can hand the list to an agent instead of the package.
|
|
55
|
+
*
|
|
56
|
+
* A `move` is reported, not represented: the tracked-change grammar has no
|
|
57
|
+
* "this paragraph came from there" mark that Word round-trips, so the buffer
|
|
58
|
+
* carries a deletion at the source and an insertion at the destination while
|
|
59
|
+
* the change list keeps the relocation visible.
|
|
60
|
+
*/
|
|
61
|
+
type CompareChange = {
|
|
62
|
+
kind: "insert";
|
|
63
|
+
location: CompareChangeLocation;
|
|
64
|
+
/** Id of the inserted block in the target document. */
|
|
65
|
+
targetBlockId: string;
|
|
66
|
+
after: string;
|
|
67
|
+
} | {
|
|
68
|
+
kind: "delete";
|
|
69
|
+
location: CompareChangeLocation;
|
|
70
|
+
baseBlockId: string;
|
|
71
|
+
before: string;
|
|
72
|
+
} | {
|
|
73
|
+
kind: "replace";
|
|
74
|
+
location: CompareChangeLocation;
|
|
75
|
+
baseBlockId: string;
|
|
76
|
+
targetBlockId: string;
|
|
77
|
+
before: string;
|
|
78
|
+
after: string;
|
|
79
|
+
} | {
|
|
80
|
+
kind: "move";
|
|
81
|
+
location: CompareChangeLocation;
|
|
82
|
+
/** The block's id where it sat in the base document. */
|
|
83
|
+
baseBlockId: string;
|
|
84
|
+
/** The same content's id where it sits in the target document. */
|
|
85
|
+
targetBlockId: string;
|
|
86
|
+
text: string;
|
|
87
|
+
} |
|
|
88
|
+
/**
|
|
89
|
+
* One paragraph became two: a paragraph mark was inserted and no words
|
|
90
|
+
* changed. Reported as its own kind so a reader is not told the tail was
|
|
91
|
+
* newly written.
|
|
92
|
+
*/
|
|
93
|
+
{
|
|
94
|
+
kind: "split";
|
|
95
|
+
location: CompareChangeLocation;
|
|
96
|
+
baseBlockId: string;
|
|
97
|
+
/** The two blocks the base block became, in target order. */
|
|
98
|
+
targetBlockIds: readonly string[];
|
|
99
|
+
text: string;
|
|
100
|
+
} |
|
|
101
|
+
/** Two paragraphs became one: a paragraph mark was deleted. */
|
|
102
|
+
{
|
|
103
|
+
kind: "merge";
|
|
104
|
+
location: CompareChangeLocation;
|
|
105
|
+
/** The two blocks that became one, in base order. */
|
|
106
|
+
baseBlockIds: readonly string[];
|
|
107
|
+
targetBlockId: string;
|
|
108
|
+
text: string;
|
|
109
|
+
} |
|
|
110
|
+
/**
|
|
111
|
+
* A paragraph property moved and no words did: a list item demoted a level,
|
|
112
|
+
* a paragraph restyled. Written as `w:pPrChange`, so rejecting restores the
|
|
113
|
+
* whole previous property set the way Word does.
|
|
114
|
+
*/
|
|
115
|
+
{
|
|
116
|
+
kind: "paragraph-format";
|
|
117
|
+
location: CompareChangeLocation;
|
|
118
|
+
baseBlockId: string;
|
|
119
|
+
targetBlockId: string;
|
|
120
|
+
/** Only the properties that differ, set to the target document's value. */
|
|
121
|
+
properties: FolioAIBlockParagraphProperties;
|
|
122
|
+
} | {
|
|
123
|
+
kind: "format";
|
|
124
|
+
location: CompareChangeLocation;
|
|
125
|
+
baseBlockId: string;
|
|
126
|
+
targetBlockId: string;
|
|
127
|
+
text: string;
|
|
128
|
+
ranges: readonly CompareFormatRange[];
|
|
129
|
+
} | {
|
|
130
|
+
kind: "table-row-insert";
|
|
131
|
+
location: CompareChangeLocation;
|
|
132
|
+
tableIndex: number;
|
|
133
|
+
/** Row index in the target document's table. */
|
|
134
|
+
rowIndex: number;
|
|
135
|
+
/** The new row's cell texts, in physical cell order. */
|
|
136
|
+
cells: readonly string[];
|
|
137
|
+
targetBlockIds: readonly string[];
|
|
138
|
+
} |
|
|
139
|
+
/**
|
|
140
|
+
* A numbering definition that differs. It carries no `location`: numbering
|
|
141
|
+
* lives in the package, not in a story, and one definition governs every
|
|
142
|
+
* list that references it.
|
|
143
|
+
*
|
|
144
|
+
* Reported and not represented. A renumbering that FOLLOWS from an edit —
|
|
145
|
+
* an item inserted, so the ones below it count on — is already shown
|
|
146
|
+
* as-if-accepted, because labels are rendered from these definitions rather
|
|
147
|
+
* than stored on the paragraphs. A definition that itself changed is a
|
|
148
|
+
* different thing, and OOXML has no tracked-change grammar for it: Word
|
|
149
|
+
* does not track `numbering.xml` either.
|
|
150
|
+
*/
|
|
151
|
+
{
|
|
152
|
+
kind: "numbering";
|
|
153
|
+
numId: number;
|
|
154
|
+
level: number;
|
|
155
|
+
/** `null` when the target added this level. */
|
|
156
|
+
before: FolioNumberingLevel | null;
|
|
157
|
+
/** `null` when the target dropped it. */
|
|
158
|
+
after: FolioNumberingLevel | null;
|
|
159
|
+
} |
|
|
160
|
+
/** A whole table the target added. */
|
|
161
|
+
{
|
|
162
|
+
kind: "table-insert";
|
|
163
|
+
location: CompareChangeLocation;
|
|
164
|
+
tableIndex: number;
|
|
165
|
+
/** Cell texts row by row, in physical cell order. */
|
|
166
|
+
rows: readonly (readonly string[])[];
|
|
167
|
+
targetBlockIds: readonly string[];
|
|
168
|
+
} |
|
|
169
|
+
/** A whole table the target dropped. */
|
|
170
|
+
{
|
|
171
|
+
kind: "table-delete";
|
|
172
|
+
location: CompareChangeLocation;
|
|
173
|
+
tableIndex: number;
|
|
174
|
+
rows: readonly (readonly string[])[];
|
|
175
|
+
baseBlockIds: readonly string[];
|
|
176
|
+
} | {
|
|
177
|
+
kind: "table-row-delete";
|
|
178
|
+
location: CompareChangeLocation;
|
|
179
|
+
tableIndex: number;
|
|
180
|
+
/** Row index in the base document's table. */
|
|
181
|
+
rowIndex: number;
|
|
182
|
+
cells: readonly string[];
|
|
183
|
+
baseBlockIds: readonly string[];
|
|
184
|
+
} | {
|
|
185
|
+
kind: "table-column-insert";
|
|
186
|
+
location: CompareChangeLocation;
|
|
187
|
+
tableIndex: number;
|
|
188
|
+
/** Grid-column index in the target table. */
|
|
189
|
+
columnIndex: number;
|
|
190
|
+
/** Newly created physical-cell text in row order. */
|
|
191
|
+
cells: readonly string[];
|
|
192
|
+
targetBlockIds: readonly string[];
|
|
193
|
+
} | {
|
|
194
|
+
kind: "table-column-delete";
|
|
195
|
+
location: CompareChangeLocation;
|
|
196
|
+
tableIndex: number;
|
|
197
|
+
/** Grid-column index in the base table. */
|
|
198
|
+
columnIndex: number;
|
|
199
|
+
cells: readonly string[];
|
|
200
|
+
baseBlockIds: readonly string[];
|
|
201
|
+
};
|
|
202
|
+
/** Why a part of the package is absent from `changes`. */
|
|
203
|
+
declare const COMPARE_UNSUPPORTED_REASONS: readonly ["story-missing-in-base", "story-missing-in-target", "story-not-editable"];
|
|
204
|
+
type CompareUnsupportedReason = (typeof COMPARE_UNSUPPORTED_REASONS)[number];
|
|
205
|
+
/**
|
|
206
|
+
* A package part the comparison did not cover. Reported rather than dropped so
|
|
207
|
+
* a caller can tell "no differences" from "not looked at".
|
|
208
|
+
*/
|
|
209
|
+
type CompareUnsupportedPart = {
|
|
210
|
+
reason: CompareUnsupportedReason;
|
|
211
|
+
baseStory: FolioDocumentStoryHandle | null;
|
|
212
|
+
targetStory: FolioDocumentStoryHandle | null;
|
|
213
|
+
};
|
|
214
|
+
type CompareResult = {
|
|
215
|
+
/** The base package carrying the generated tracked changes. */
|
|
216
|
+
buffer: ArrayBuffer;
|
|
217
|
+
changes: readonly CompareChange[];
|
|
218
|
+
/**
|
|
219
|
+
* Whether the round trip was proven. Always `verified` unless the call asked
|
|
220
|
+
* for `onUnverified: "emit"`, which is the only way an unproven redline is
|
|
221
|
+
* returned at all.
|
|
222
|
+
*/
|
|
223
|
+
verification: CompareVerification;
|
|
224
|
+
unsupported: readonly CompareUnsupportedPart[];
|
|
225
|
+
};
|
|
226
|
+
declare const InvalidCompareDocxOptionsError_base: import("better-result").TaggedErrorClass<"InvalidCompareDocxOptionsError">;
|
|
227
|
+
declare class InvalidCompareDocxOptionsError extends InvalidCompareDocxOptionsError_base<{
|
|
228
|
+
message: string;
|
|
229
|
+
option: "timestamp";
|
|
230
|
+
receivedValue: unknown;
|
|
231
|
+
}> {}
|
|
232
|
+
declare const CompareDocxParseError_base: import("better-result").TaggedErrorClass<"CompareDocxParseError">;
|
|
233
|
+
declare class CompareDocxParseError extends CompareDocxParseError_base<{
|
|
234
|
+
message: string;
|
|
235
|
+
side: "base" | "target";
|
|
236
|
+
cause: unknown;
|
|
237
|
+
}> {}
|
|
238
|
+
declare const CompareDocxApplyError_base: import("better-result").TaggedErrorClass<"CompareDocxApplyError">;
|
|
239
|
+
/**
|
|
240
|
+
* The applier refused at least one derived operation, so accepting the result
|
|
241
|
+
* would not reproduce the target. Reported instead of returning a package that
|
|
242
|
+
* silently under-represents the difference.
|
|
243
|
+
*/
|
|
244
|
+
declare class CompareDocxApplyError extends CompareDocxApplyError_base<{
|
|
245
|
+
message: string;
|
|
246
|
+
skipped: readonly FolioAIEditSkippedOperation[];
|
|
247
|
+
}> {}
|
|
248
|
+
declare const CompareDocxRoundTripError_base: import("better-result").TaggedErrorClass<"CompareDocxRoundTripError">;
|
|
249
|
+
/**
|
|
250
|
+
* The generated tracked changes do not accept back to the target. Raised by
|
|
251
|
+
* the self-check {@link compareDocx} runs before returning: a redline that
|
|
252
|
+
* quietly loses part of the difference is worse than no redline, so the
|
|
253
|
+
* mismatch is surfaced instead of the document.
|
|
254
|
+
*/
|
|
255
|
+
declare class CompareDocxRoundTripError extends CompareDocxRoundTripError_base<{
|
|
256
|
+
message: string;
|
|
257
|
+
story: FolioDocumentStoryHandle;
|
|
258
|
+
/** The invariant that did not hold, and what diverged under it. */
|
|
259
|
+
invariant: CompareVerificationInvariant;
|
|
260
|
+
cause: CompareVerificationCause;
|
|
261
|
+
/**
|
|
262
|
+
* Every invariant that failed, not only the one named above: a redline that
|
|
263
|
+
* loses a difference usually loses it in both directions, and a caller
|
|
264
|
+
* deciding what to do next wants the whole list. Each detail is structural,
|
|
265
|
+
* so it is safe to log or quote.
|
|
266
|
+
*/
|
|
267
|
+
failures: readonly CompareVerificationFailure[];
|
|
268
|
+
}> {}
|
|
269
|
+
declare const CompareDocxOperationLimitError_base: import("better-result").TaggedErrorClass<"CompareDocxOperationLimitError">;
|
|
270
|
+
/** The difference needs more operations than the engine will generate. */
|
|
271
|
+
declare class CompareDocxOperationLimitError extends CompareDocxOperationLimitError_base<{
|
|
272
|
+
message: string;
|
|
273
|
+
limit: number;
|
|
274
|
+
}> {}
|
|
275
|
+
declare const CompareDocxSerializeError_base: import("better-result").TaggedErrorClass<"CompareDocxSerializeError">;
|
|
276
|
+
declare class CompareDocxSerializeError extends CompareDocxSerializeError_base<{
|
|
277
|
+
message: string;
|
|
278
|
+
cause: unknown;
|
|
279
|
+
}> {}
|
|
280
|
+
declare const CompareDocxFinalParagraphMarkError_base: import("better-result").TaggedErrorClass<"CompareDocxFinalParagraphMarkError">;
|
|
281
|
+
/**
|
|
282
|
+
* A container's final paragraph mark carries a deletion, so the package would
|
|
283
|
+
* not open.
|
|
284
|
+
*
|
|
285
|
+
* A deleted paragraph mark means "merge this paragraph into the following
|
|
286
|
+
* one", and a container's last paragraph has no following one. Checked before
|
|
287
|
+
* the package is written, and fatal under either `onUnverified` setting: there
|
|
288
|
+
* is no redline to emit when a consumer refuses the file.
|
|
289
|
+
*/
|
|
290
|
+
declare class CompareDocxFinalParagraphMarkError extends CompareDocxFinalParagraphMarkError_base<{
|
|
291
|
+
message: string;
|
|
292
|
+
/** Every container that carries one, each named structurally. */
|
|
293
|
+
deletions: readonly FinalParagraphMarkDeletion[];
|
|
294
|
+
}> {}
|
|
295
|
+
type CompareDocxError = CompareDocxApplyError | CompareDocxFinalParagraphMarkError | CompareDocxOperationLimitError | CompareDocxParseError | CompareDocxRoundTripError | CompareDocxSerializeError | InvalidCompareDocxOptionsError;
|
|
296
|
+
//#endregion
|
|
297
|
+
export { COMPARE_UNSUPPORTED_REASONS, CompareChange, CompareChangeLocation, CompareDocxApplyError, CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, CompareFormatRange, CompareResult, CompareUnsupportedPart, CompareUnsupportedReason, InvalidCompareDocxOptionsError };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TaggedError } from "better-result";
|
|
2
|
+
//#region src/compare/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Public vocabulary of {@link ./compare.compareDocx}: the change list, the
|
|
5
|
+
* options that pin its output, and the failures it reports.
|
|
6
|
+
*/
|
|
7
|
+
/** Why a part of the package is absent from `changes`. */
|
|
8
|
+
const COMPARE_UNSUPPORTED_REASONS = Object.freeze([
|
|
9
|
+
"story-missing-in-base",
|
|
10
|
+
"story-missing-in-target",
|
|
11
|
+
"story-not-editable"
|
|
12
|
+
]);
|
|
13
|
+
var InvalidCompareDocxOptionsError = class extends TaggedError("InvalidCompareDocxOptionsError") {};
|
|
14
|
+
var CompareDocxParseError = class extends TaggedError("CompareDocxParseError") {};
|
|
15
|
+
/**
|
|
16
|
+
* The applier refused at least one derived operation, so accepting the result
|
|
17
|
+
* would not reproduce the target. Reported instead of returning a package that
|
|
18
|
+
* silently under-represents the difference.
|
|
19
|
+
*/
|
|
20
|
+
var CompareDocxApplyError = class extends TaggedError("CompareDocxApplyError") {};
|
|
21
|
+
/**
|
|
22
|
+
* The generated tracked changes do not accept back to the target. Raised by
|
|
23
|
+
* the self-check {@link compareDocx} runs before returning: a redline that
|
|
24
|
+
* quietly loses part of the difference is worse than no redline, so the
|
|
25
|
+
* mismatch is surfaced instead of the document.
|
|
26
|
+
*/
|
|
27
|
+
var CompareDocxRoundTripError = class extends TaggedError("CompareDocxRoundTripError") {};
|
|
28
|
+
/** The difference needs more operations than the engine will generate. */
|
|
29
|
+
var CompareDocxOperationLimitError = class extends TaggedError("CompareDocxOperationLimitError") {};
|
|
30
|
+
var CompareDocxSerializeError = class extends TaggedError("CompareDocxSerializeError") {};
|
|
31
|
+
/**
|
|
32
|
+
* A container's final paragraph mark carries a deletion, so the package would
|
|
33
|
+
* not open.
|
|
34
|
+
*
|
|
35
|
+
* A deleted paragraph mark means "merge this paragraph into the following
|
|
36
|
+
* one", and a container's last paragraph has no following one. Checked before
|
|
37
|
+
* the package is written, and fatal under either `onUnverified` setting: there
|
|
38
|
+
* is no redline to emit when a consumer refuses the file.
|
|
39
|
+
*/
|
|
40
|
+
var CompareDocxFinalParagraphMarkError = class extends TaggedError("CompareDocxFinalParagraphMarkError") {};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { COMPARE_UNSUPPORTED_REASONS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { FolioAIBlock } from "../ai-edits/types.js";
|
|
2
|
+
import { FolioDocumentStoryHandle } from "../ai-edits/headless.js";
|
|
3
|
+
//#region src/compare/verification.d.ts
|
|
4
|
+
/** The two directions of the round trip, each an invariant of its own. */
|
|
5
|
+
declare const COMPARE_VERIFICATION_INVARIANTS: readonly ["accept-reproduces-target", "reject-reproduces-base"];
|
|
6
|
+
type CompareVerificationInvariant = (typeof COMPARE_VERIFICATION_INVARIANTS)[number];
|
|
7
|
+
/**
|
|
8
|
+
* What diverged. Ordered from the structural to the textual: the first cause
|
|
9
|
+
* that fits is the one reported, because a block in the wrong container is a
|
|
10
|
+
* different finding from a block whose words are wrong even when both are true.
|
|
11
|
+
*/
|
|
12
|
+
declare const COMPARE_VERIFICATION_CAUSES: readonly ["invisible-structure", "block-count", "container", "style", "list-level", "inline-formatting", "whitespace", "text"];
|
|
13
|
+
type CompareVerificationCause = (typeof COMPARE_VERIFICATION_CAUSES)[number];
|
|
14
|
+
/** One invariant that did not hold, in one story. */
|
|
15
|
+
type CompareVerificationFailure = {
|
|
16
|
+
invariant: CompareVerificationInvariant;
|
|
17
|
+
cause: CompareVerificationCause;
|
|
18
|
+
story: FolioDocumentStoryHandle;
|
|
19
|
+
/** Structural facts only: counts, offsets, container kinds. Safe to quote. */
|
|
20
|
+
detail: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Whether the redline was proven to round-trip.
|
|
24
|
+
*
|
|
25
|
+
* `unverified` is only ever returned when the caller asked for it with
|
|
26
|
+
* `onUnverified: "emit"`; the default refuses instead, because a redline that
|
|
27
|
+
* reads plausibly and is wrong is worse than no redline.
|
|
28
|
+
*/
|
|
29
|
+
type CompareVerification = {
|
|
30
|
+
status: "verified";
|
|
31
|
+
} | {
|
|
32
|
+
status: "unverified";
|
|
33
|
+
failures: readonly CompareVerificationFailure[];
|
|
34
|
+
};
|
|
35
|
+
/** Effective supported formatting with equivalent adjacent runs normalized. */
|
|
36
|
+
declare const projectSupportedInlineFormatting: ({ text, previewRuns }: FolioAIBlock) => string;
|
|
37
|
+
/** Element by element, never by joining: a block's text may hold the separator. */
|
|
38
|
+
declare const sameProjection: (left: readonly string[], right: readonly string[]) => boolean;
|
|
39
|
+
type ClassifyOptions = {
|
|
40
|
+
invariant: CompareVerificationInvariant;
|
|
41
|
+
story: FolioDocumentStoryHandle;
|
|
42
|
+
/** What the redline actually leaves. */
|
|
43
|
+
actual: readonly string[];
|
|
44
|
+
/** What the invariant says it should leave. */
|
|
45
|
+
expected: readonly string[];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The failure two projections describe, or `null` when they agree.
|
|
49
|
+
*
|
|
50
|
+
* Total over the causes by construction: the last branch is unconditional, so
|
|
51
|
+
* a divergence always produces a failure rather than being dropped.
|
|
52
|
+
*/
|
|
53
|
+
declare const classifyProjectionMismatch: ({ invariant, story, actual, expected }: ClassifyOptions) => CompareVerificationFailure | null;
|
|
54
|
+
/**
|
|
55
|
+
* A container whose last paragraph carries a deletion on its mark.
|
|
56
|
+
*
|
|
57
|
+
* Structural facts only — a path through the package model and an index — so
|
|
58
|
+
* the finding is safe to log, report or quote.
|
|
59
|
+
*/
|
|
60
|
+
type FinalParagraphMarkDeletion = {
|
|
61
|
+
/** Where the container sits in the package model, e.g. `package.document.content`. */
|
|
62
|
+
container: string;
|
|
63
|
+
/** The paragraph's index among its container's children. */
|
|
64
|
+
paragraphIndex: number;
|
|
65
|
+
/** The mark kind found there: `del`, or `moveFrom` for a relocation's source. */
|
|
66
|
+
kind: "del" | "moveFrom";
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Every container in a package whose final paragraph mark carries a deletion.
|
|
70
|
+
*
|
|
71
|
+
* A deleted paragraph mark says "join this paragraph with the one after it".
|
|
72
|
+
* The last paragraph of a body, a table cell, a header or footer, a note or a
|
|
73
|
+
* text box has no paragraph after it, so the mark states an edit that cannot
|
|
74
|
+
* be carried out, and a consumer refuses the package rather than opening it.
|
|
75
|
+
*
|
|
76
|
+
* The walk is over the package model rather than over a list of the containers
|
|
77
|
+
* known today: a container is any sequence that ends in a paragraph, so a part
|
|
78
|
+
* the model grows later is covered the day it arrives instead of the day
|
|
79
|
+
* someone remembers this function.
|
|
80
|
+
*/
|
|
81
|
+
declare const deletedFinalParagraphMarks: (packageModel: unknown) => FinalParagraphMarkDeletion[];
|
|
82
|
+
//#endregion
|
|
83
|
+
export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkDeletion, classifyProjectionMismatch, deletedFinalParagraphMarks, projectSupportedInlineFormatting, sameProjection };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
//#region src/compare/verification.ts
|
|
2
|
+
/** The two directions of the round trip, each an invariant of its own. */
|
|
3
|
+
const COMPARE_VERIFICATION_INVARIANTS = Object.freeze(["accept-reproduces-target", "reject-reproduces-base"]);
|
|
4
|
+
/**
|
|
5
|
+
* What diverged. Ordered from the structural to the textual: the first cause
|
|
6
|
+
* that fits is the one reported, because a block in the wrong container is a
|
|
7
|
+
* different finding from a block whose words are wrong even when both are true.
|
|
8
|
+
*/
|
|
9
|
+
const COMPARE_VERIFICATION_CAUSES = Object.freeze([
|
|
10
|
+
"invisible-structure",
|
|
11
|
+
"block-count",
|
|
12
|
+
"container",
|
|
13
|
+
"style",
|
|
14
|
+
"list-level",
|
|
15
|
+
"inline-formatting",
|
|
16
|
+
"whitespace",
|
|
17
|
+
"text"
|
|
18
|
+
]);
|
|
19
|
+
const supportedInlineStyle = ({ bold, italic, underline, strike }) => `${bold === true ? "b" : ""}${italic === true ? "i" : ""}${underline === true ? "u" : ""}${strike === true ? "s" : ""}`;
|
|
20
|
+
/** Effective supported formatting with equivalent adjacent runs normalized. */
|
|
21
|
+
const projectSupportedInlineFormatting = ({ text, previewRuns }) => {
|
|
22
|
+
const projected = [];
|
|
23
|
+
for (const run of previewRuns ?? [{ text }]) {
|
|
24
|
+
if (run.text.length === 0) continue;
|
|
25
|
+
const style = supportedInlineStyle(run);
|
|
26
|
+
const previous = projected.at(-1);
|
|
27
|
+
if (previous?.style === style) {
|
|
28
|
+
previous.length += run.text.length;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
projected.push({
|
|
32
|
+
length: run.text.length,
|
|
33
|
+
style
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return projected.map(({ length, style }) => `${String(length)}:${style}`).join(",");
|
|
37
|
+
};
|
|
38
|
+
const parseProjection = (entry) => {
|
|
39
|
+
const first = entry.indexOf("|");
|
|
40
|
+
const second = entry.indexOf("|", first + 1);
|
|
41
|
+
const third = entry.indexOf("|", second + 1);
|
|
42
|
+
if (first === -1 || second === -1 || third === -1) return {
|
|
43
|
+
container: "",
|
|
44
|
+
styleId: "",
|
|
45
|
+
listLevel: "",
|
|
46
|
+
text: entry
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
container: entry.slice(0, first),
|
|
50
|
+
styleId: entry.slice(first + 1, second),
|
|
51
|
+
listLevel: entry.slice(second + 1, third),
|
|
52
|
+
text: entry.slice(third + 1)
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const CONTAINER_PATTERN = /^t(\d+)r(\d+)c(\d+)p(\d+)$/u;
|
|
56
|
+
const containerKind = (container) => CONTAINER_PATTERN.test(container) ? "cell" : "body";
|
|
57
|
+
const collapseWhitespace = (text) => text.replace(/\s+/gu, " ").trim();
|
|
58
|
+
/** Element by element, never by joining: a block's text may hold the separator. */
|
|
59
|
+
const sameProjection = (left, right) => left.length === right.length && left.every((entry, index) => entry === right[index]);
|
|
60
|
+
/**
|
|
61
|
+
* The same projection with every table coordinate renumbered by first
|
|
62
|
+
* appearance, so it counts the blocks the model holds rather than the
|
|
63
|
+
* paragraphs the package contains.
|
|
64
|
+
*
|
|
65
|
+
* The snapshot skips a hidden row's whole subtree, so a table that hides a row
|
|
66
|
+
* on one side only reports every later row one position along. No operation
|
|
67
|
+
* can put a block at those coordinates, because none can create or remove the
|
|
68
|
+
* hidden row that produces them. When two projections agree here and disagree
|
|
69
|
+
* on the raw coordinates, the redline holds every block the other side does,
|
|
70
|
+
* in order, and the difference is one the block model cannot see.
|
|
71
|
+
*/
|
|
72
|
+
const byVisibleOrdinal = (entries) => {
|
|
73
|
+
const ordinals = /* @__PURE__ */ new Map();
|
|
74
|
+
const counts = /* @__PURE__ */ new Map();
|
|
75
|
+
const ordinalWithin = (scope, index) => {
|
|
76
|
+
const key = `${scope}:${index}`;
|
|
77
|
+
const existing = ordinals.get(key);
|
|
78
|
+
if (existing !== void 0) return existing;
|
|
79
|
+
const next = counts.get(scope) ?? 0;
|
|
80
|
+
counts.set(scope, next + 1);
|
|
81
|
+
ordinals.set(key, next);
|
|
82
|
+
return next;
|
|
83
|
+
};
|
|
84
|
+
const paragraphCounts = /* @__PURE__ */ new Map();
|
|
85
|
+
return entries.map((entry) => {
|
|
86
|
+
const first = entry.indexOf("|");
|
|
87
|
+
const match = CONTAINER_PATTERN.exec(entry.slice(0, first));
|
|
88
|
+
if (!match) return entry;
|
|
89
|
+
const [, table = "", row = "", cell = ""] = match;
|
|
90
|
+
const tableOrdinal = ordinalWithin("t", table);
|
|
91
|
+
const rowScope = `r${String(tableOrdinal)}`;
|
|
92
|
+
const rowOrdinal = ordinalWithin(rowScope, row);
|
|
93
|
+
const cellScope = `c${String(tableOrdinal)}.${String(rowOrdinal)}`;
|
|
94
|
+
const cellOrdinal = ordinalWithin(cellScope, cell);
|
|
95
|
+
const cellKey = `${cellScope}:${cell}`;
|
|
96
|
+
const paragraphOrdinal = paragraphCounts.get(cellKey) ?? 0;
|
|
97
|
+
paragraphCounts.set(cellKey, paragraphOrdinal + 1);
|
|
98
|
+
return `t${String(tableOrdinal)}r${String(rowOrdinal)}c${String(cellOrdinal)}p${String(paragraphOrdinal)}` + entry.slice(first);
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Where two projections first diverge, and what diverged there. A pair that
|
|
103
|
+
* matches everywhere but in length diverges at the shorter one's end.
|
|
104
|
+
*/
|
|
105
|
+
const firstDivergence = (actual, expected) => {
|
|
106
|
+
const shared = Math.min(actual.length, expected.length);
|
|
107
|
+
for (let index = 0; index < shared; index++) if (actual[index] !== expected[index]) return {
|
|
108
|
+
index,
|
|
109
|
+
actual: parseProjection(actual[index] ?? ""),
|
|
110
|
+
expected: parseProjection(expected[index] ?? "")
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
index: shared,
|
|
114
|
+
actual: shared < actual.length ? parseProjection(actual[shared] ?? "") : null,
|
|
115
|
+
expected: shared < expected.length ? parseProjection(expected[shared] ?? "") : null
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* The failure two projections describe, or `null` when they agree.
|
|
120
|
+
*
|
|
121
|
+
* Total over the causes by construction: the last branch is unconditional, so
|
|
122
|
+
* a divergence always produces a failure rather than being dropped.
|
|
123
|
+
*/
|
|
124
|
+
const classifyProjectionMismatch = ({ invariant, story, actual, expected }) => {
|
|
125
|
+
if (sameProjection(actual, expected)) return null;
|
|
126
|
+
const failure = (cause, detail) => ({
|
|
127
|
+
invariant,
|
|
128
|
+
cause,
|
|
129
|
+
story,
|
|
130
|
+
detail
|
|
131
|
+
});
|
|
132
|
+
if (sameProjection(byVisibleOrdinal(actual), byVisibleOrdinal(expected))) return failure("invisible-structure", `every block matches once table coordinates count visible blocks (${String(expected.length)} blocks)`);
|
|
133
|
+
const divergence = firstDivergence(actual, expected);
|
|
134
|
+
const at = `at block ${String(divergence.index)}/${String(expected.length)}`;
|
|
135
|
+
const counts = `${String(actual.length)} blocks against ${String(expected.length)}`;
|
|
136
|
+
if (divergence.actual === null || divergence.expected === null) return failure("block-count", `${actual.length > expected.length ? "more" : "fewer"} blocks than expected (${counts}), diverging ${at}`);
|
|
137
|
+
const { actual: left, expected: right } = divergence;
|
|
138
|
+
if (left.container !== right.container) return failure("container", `a block sits in a ${containerKind(left.container)} where it is expected in a ${containerKind(right.container)}, ${at} (${counts})`);
|
|
139
|
+
if (left.text === right.text && left.styleId !== right.styleId) return failure("style", `the paragraph style did not move ${at} (${counts})`);
|
|
140
|
+
if (left.text === right.text && left.listLevel !== right.listLevel) return failure("list-level", `the list level did not move ${at} (${counts})`);
|
|
141
|
+
if (collapseWhitespace(left.text) === collapseWhitespace(right.text)) return failure("whitespace", `a block's text differs only in whitespace ${at} (${counts})`);
|
|
142
|
+
if (actual.length !== expected.length) return failure("block-count", `${counts}, first differing ${at}`);
|
|
143
|
+
return failure("text", `a ${containerKind(left.container)} block's text does not match ${at}, length ${String(left.text.length)} against ${String(right.text.length)} (${counts})`);
|
|
144
|
+
};
|
|
145
|
+
/** The mark kinds that resolve by joining the paragraph with the one after it. */
|
|
146
|
+
const JOINS_FORWARD_ON_ACCEPT = Object.freeze(["del", "moveFrom"]);
|
|
147
|
+
const joinsForwardOnAccept = (value) => JOINS_FORWARD_ON_ACCEPT.some((kind) => kind === value);
|
|
148
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
149
|
+
const isParagraph = (value) => isRecord(value) && value["type"] === "paragraph";
|
|
150
|
+
/**
|
|
151
|
+
* Every container in a package whose final paragraph mark carries a deletion.
|
|
152
|
+
*
|
|
153
|
+
* A deleted paragraph mark says "join this paragraph with the one after it".
|
|
154
|
+
* The last paragraph of a body, a table cell, a header or footer, a note or a
|
|
155
|
+
* text box has no paragraph after it, so the mark states an edit that cannot
|
|
156
|
+
* be carried out, and a consumer refuses the package rather than opening it.
|
|
157
|
+
*
|
|
158
|
+
* The walk is over the package model rather than over a list of the containers
|
|
159
|
+
* known today: a container is any sequence that ends in a paragraph, so a part
|
|
160
|
+
* the model grows later is covered the day it arrives instead of the day
|
|
161
|
+
* someone remembers this function.
|
|
162
|
+
*/
|
|
163
|
+
const deletedFinalParagraphMarks = (packageModel) => {
|
|
164
|
+
const found = [];
|
|
165
|
+
const visit = (value, path) => {
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
const last = value.at(-1);
|
|
168
|
+
const mark = isParagraph(last) ? last["pPrMark"] : void 0;
|
|
169
|
+
const kind = isRecord(mark) ? mark["kind"] : void 0;
|
|
170
|
+
if (joinsForwardOnAccept(kind)) found.push({
|
|
171
|
+
container: path,
|
|
172
|
+
paragraphIndex: value.length - 1,
|
|
173
|
+
kind
|
|
174
|
+
});
|
|
175
|
+
for (const [index, item] of value.entries()) visit(item, `${path}[${String(index)}]`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (value instanceof Map) {
|
|
179
|
+
for (const [key, item] of value) visit(item, `${path}.${String(key)}`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (!isRecord(value) || value instanceof Date || ArrayBuffer.isView(value)) return;
|
|
183
|
+
for (const [key, item] of Object.entries(value)) visit(item, `${path}.${key}`);
|
|
184
|
+
};
|
|
185
|
+
visit(packageModel, "package");
|
|
186
|
+
return found;
|
|
187
|
+
};
|
|
188
|
+
//#endregion
|
|
189
|
+
export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, classifyProjectionMismatch, deletedFinalParagraphMarks, projectSupportedInlineFormatting, sameProjection };
|