@ansonlai/docx-redline-js 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/ARCHITECTURE.md +221 -12
  2. package/CHANGELOG.md +803 -586
  3. package/README.md +1018 -901
  4. package/core/current-properties.js +136 -0
  5. package/core/paragraph-text.js +211 -209
  6. package/core/types.js +9 -3
  7. package/core/word-revision-semantics.js +256 -0
  8. package/dist/docx-redline-js.esm.js +4551 -967
  9. package/dist/docx-redline-js.esm.js.map +4 -4
  10. package/dist/docx-redline-js.esm.min.js +112 -106
  11. package/dist/docx-redline-js.esm.min.js.map +4 -4
  12. package/dist/docx-redline.bundle.cjs +4690 -1106
  13. package/dist/docx-redline.bundle.cjs.map +4 -4
  14. package/dist/docx-redline.bundle.js +4690 -1106
  15. package/dist/docx-redline.bundle.js.map +4 -4
  16. package/docs/AGENT_FAST_START.md +2 -0
  17. package/docs/AGENT_KNOWLEDGE_BASE.md +23 -4
  18. package/docs/SKILL_AUTHORING.md +165 -150
  19. package/docs/TESTING.md +1142 -1059
  20. package/docs/schemas/document-operations.schema.json +221 -168
  21. package/document/docx-document.js +1022 -1010
  22. package/engine/oxml-engine.js +567 -560
  23. package/engine/paragraph-mark-revisions.js +292 -0
  24. package/engine/reconstruction-mapper.js +312 -311
  25. package/engine/reconstruction-mode.js +183 -183
  26. package/engine/reconstruction-writer.js +67 -10
  27. package/engine/rpr-helpers.js +42 -0
  28. package/engine/run-builders.js +0 -73
  29. package/engine/surgical-diff-application.js +604 -605
  30. package/engine/table-mode.js +63 -63
  31. package/index.d.ts +51 -1
  32. package/index.js +47 -3
  33. package/node/cli-help.js +1 -0
  34. package/node/cli.js +992 -974
  35. package/orchestration/list-structural-fallback.js +67 -9
  36. package/package.json +1 -1
  37. package/pipeline/diff-engine.js +130 -1
  38. package/pipeline/ingestion-export.js +227 -243
  39. package/pipeline/ingestion-paragraph.js +93 -98
  40. package/pipeline/list-generation.js +415 -419
  41. package/pipeline/pipeline.js +206 -204
  42. package/pipeline/serialization.js +111 -94
  43. package/scripts/export-validation-fixtures.mjs +19 -6
  44. package/scripts/generate-word-revision-semantics-matrix.mjs +134 -0
  45. package/scripts/lib/word-coverage-catalogue.mjs +7 -2
  46. package/scripts/render-multilevel-bullet-visual.ps1 +2 -2
  47. package/scripts/word-com-corpus-visual-suite.ps1 +2 -2
  48. package/scripts/word-com-differential.ps1 +443 -335
  49. package/scripts/word-com-visual-suite.ps1 +2 -2
  50. package/scripts/word-revision-semantics-matrix.ps1 +108 -0
  51. package/services/batch-operation-orchestrator.js +26 -0
  52. package/services/capture-engine.js +228 -226
  53. package/services/document-operation-applier.js +215 -0
  54. package/services/document-operation-contract.js +628 -537
  55. package/services/document-operation-mutations.js +789 -46
  56. package/services/error-recovery.js +17 -0
  57. package/services/list-conversion-diagnostics.js +296 -0
  58. package/services/list-format-change.js +586 -0
  59. package/services/list-format-mutation.js +416 -0
  60. package/services/list-format.js +305 -0
  61. package/services/numbering-allocation.js +642 -0
  62. package/services/numbering-helpers.js +354 -524
  63. package/services/operation-batch-compiler.js +612 -436
  64. package/services/operation-heuristics.js +197 -173
  65. package/services/operation-preflight.js +1 -0
  66. package/services/receipt-collector.js +288 -288
  67. package/services/revision-comment-management.js +634 -594
  68. package/services/revision-fidelity-verifier.js +498 -0
  69. package/services/standalone-docx-plumbing.js +627 -631
  70. package/services/standalone-operation-runner.d.ts +606 -427
  71. package/services/table-reconciliation.js +103 -49
@@ -1,168 +1,221 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://github.com/AnsonLai/docx-redline-js/docs/schemas/document-operations.schema.json",
4
- "title": "docx-redline document operations",
5
- "oneOf": [
6
- { "$ref": "#/$defs/operationArray" },
7
- {
8
- "type": "object",
9
- "required": ["operations"],
10
- "properties": {
11
- "operations": { "$ref": "#/$defs/operationArray" },
12
- "expectedRevision": { "$ref": "#/$defs/revisionToken" }
13
- },
14
- "additionalProperties": false
15
- }
16
- ],
17
- "$defs": {
18
- "revisionToken": {
19
- "type": "object",
20
- "required": ["algorithm", "version", "scope", "value"],
21
- "properties": {
22
- "algorithm": { "type": "string", "enum": ["sha256"] },
23
- "version": { "type": "integer", "enum": [1] },
24
- "scope": { "type": "string", "enum": ["document-parts", "package"] },
25
- "value": { "type": "string", "pattern": "^[0-9a-fA-F]{64}$" },
26
- "coveredParts": { "type": "array", "items": { "type": "string" } }
27
- },
28
- "additionalProperties": false
29
- },
30
- "operationArray": { "type": "array", "items": { "$ref": "#/$defs/operation" } },
31
- "rejectedTextInsertionAnchor": {
32
- "type": "object",
33
- "required": ["exactText", "offset"],
34
- "properties": {
35
- "exactText": { "type": "string", "minLength": 1 },
36
- "occurrence": { "type": "integer", "minimum": 1 },
37
- "offset": { "type": "integer", "minimum": 0 }
38
- },
39
- "additionalProperties": false
40
- },
41
- "localizedReplacement": {
42
- "type": "object",
43
- "required": ["find", "replace"],
44
- "properties": {
45
- "find": { "type": "string", "minLength": 1 },
46
- "replace": { "type": "string" },
47
- "occurrence": { "type": "integer", "minimum": 1 }
48
- },
49
- "additionalProperties": false
50
- },
51
- "target": {
52
- "oneOf": [
53
- { "type": "string", "minLength": 1 },
54
- {
55
- "type": "object",
56
- "properties": {
57
- "exactText": { "type": "string" }, "text": { "type": "string" },
58
- "index": { "type": ["integer", "string"] }, "paragraphIndex": { "type": ["integer", "string"] },
59
- "paragraphId": { "type": "string" }, "occurrence": { "type": "integer", "minimum": 1 },
60
- "inTable": { "type": "boolean" }, "fingerprint": { "type": "string" }, "sourceFingerprint": { "type": "string" },
61
- "revisionView": {
62
- "type": "string",
63
- "enum": ["accepted", "rejected"],
64
- "description": "Defaults to rejected for restore operations and accepted for other operation types."
65
- },
66
- "captureRef": { "type": "string", "minLength": 1, "maxLength": 256 },
67
- "select": { "type": "string", "minLength": 1 }
68
- },
69
- "additionalProperties": false,
70
- "anyOf": [ { "required": ["exactText"] }, { "required": ["text"] }, { "required": ["index"] }, { "required": ["paragraphIndex"] }, { "required": ["paragraphId"] }, { "required": ["fingerprint"] }, { "required": ["sourceFingerprint"] }, { "required": ["captureRef"] } ]
71
- }
72
- ]
73
- },
74
- "headerFooterPart": {
75
- "description": "Targets a header or footer instead of the body. Use a part path from inspect().headersFooters, or a selector. Comments are not supported in headers or footers.",
76
- "oneOf": [
77
- { "type": "string", "minLength": 1, "examples": ["word/footer2.xml"] },
78
- {
79
- "type": "object",
80
- "required": ["kind"],
81
- "properties": {
82
- "kind": { "enum": ["header", "footer"] },
83
- "type": { "enum": ["default", "first", "even"], "description": "Defaults to default." },
84
- "section": { "type": "integer", "minimum": 0, "description": "Zero-based section; required when several sections have their own part." }
85
- },
86
- "additionalProperties": false
87
- }
88
- ]
89
- },
90
- "base": {
91
- "type": "object",
92
- "required": ["type", "target"],
93
- "properties": {
94
- "operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
95
- "captureKey": { "type": "string", "minLength": 1, "maxLength": 256 },
96
- "target": { "$ref": "#/$defs/target" }, "targetRef": { "type": ["integer", "string", "null"] },
97
- "part": { "$ref": "#/$defs/headerFooterPart" },
98
- "author": { "type": "string", "minLength": 1 }, "generateRedlines": { "type": "boolean" },
99
- "existingRevisions": { "enum": ["merge-same-author", "slice-cross-author", "reject-input", "accept-all-first", "accept-all-first-keep-normalized"] },
100
- "pairReplacements": { "type": "boolean" },
101
- "insertionAffinity": {
102
- "type": "object",
103
- "properties": {
104
- "formatting": { "enum": ["left", "right", "none"] },
105
- "hyperlink": { "enum": ["inside", "outside", "preserve"] },
106
- "revision": { "enum": ["coalesce_same_author", "separate"] },
107
- "bookmark": { "enum": ["inside", "outside"] },
108
- "comment": { "enum": ["inside", "outside"] }
109
- },
110
- "additionalProperties": false
111
- }
112
- }
113
- },
114
- "operation": {
115
- "oneOf": [
116
- {
117
- "type": "object",
118
- "required": ["type", "parentCommentId", "commentContent"],
119
- "properties": {
120
- "type": { "const": "comment_reply" },
121
- "operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
122
- "parentCommentId": { "type": ["integer", "string"] },
123
- "commentContent": { "type": "string", "minLength": 1 },
124
- "author": { "type": "string", "minLength": 1 },
125
- "date": { "type": "string", "format": "date-time" }
126
- },
127
- "additionalProperties": false
128
- },
129
- {
130
- "type": "object",
131
- "description": "Resolve or reopen the comment thread containing commentId. Resolved is thread-level in Word, so the root and every reply change together. Body-only; not allowed with part.",
132
- "required": ["type", "commentId"],
133
- "properties": {
134
- "type": { "const": "comment_resolve" },
135
- "operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
136
- "commentId": { "type": ["integer", "string"] },
137
- "resolved": { "type": "boolean", "default": true }
138
- },
139
- "additionalProperties": false
140
- },
141
- {
142
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["replacements"], "properties": { "type": { "enum": ["redline", "replace"] }, "replacements": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/localizedReplacement" } } } } ]
143
- },
144
- {
145
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["modified"], "properties": { "type": { "enum": ["redline", "replace", "format", "list-change", "table-reconciliation", "insert"] }, "modified": { "type": "string" }, "structuredContent": { "type": "boolean" }, "targetEnd": { "$ref": "#/$defs/target" }, "targetEndRef": { "type": ["integer", "string", "null"] }, "anchor": { "$ref": "#/$defs/rejectedTextInsertionAnchor" } } } ]
146
- },
147
- {
148
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "properties": { "type": { "const": "restore" }, "modified": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } } ] }, "replacements": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/localizedReplacement" } }, "targetEnd": { "$ref": "#/$defs/target" }, "targetEndRef": { "type": ["integer", "string", "null"] } }, "oneOf": [ { "required": ["modified"], "not": { "required": ["replacements"] } }, { "required": ["replacements"], "not": { "required": ["modified"] } }, { "not": { "anyOf": [ { "required": ["modified"] }, { "required": ["replacements"] } ] } } ] } ]
149
- },
150
- {
151
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "properties": { "type": { "const": "delete" }, "modified": { "const": "" } } } ]
152
- },
153
- {
154
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["commentContent"], "properties": { "type": { "const": "comment" }, "textToComment": { "type": "string" }, "commentContent": { "type": "string", "minLength": 1 } } } ]
155
- },
156
- {
157
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["textToHighlight"], "properties": { "type": { "const": "highlight" }, "textToHighlight": { "type": "string", "minLength": 1 }, "color": { "type": "string" } } } ]
158
- },
159
- {
160
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["textToFormat", "properties"], "properties": { "type": { "enum": ["format", "character-format"] }, "textToFormat": { "type": "string", "minLength": 1 }, "properties": { "type": "object" }, "formattingRevisionPolicy": { "enum": ["always", "coalesce-own-insertion"] } } } ]
161
- },
162
- {
163
- "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["properties"], "properties": { "type": { "const": "paragraph-format" }, "properties": { "type": "object" } } } ]
164
- }
165
- ]
166
- }
167
- }
168
- }
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/AnsonLai/docx-redline-js/docs/schemas/document-operations.schema.json",
4
+ "title": "docx-redline document operations",
5
+ "oneOf": [
6
+ { "$ref": "#/$defs/operationArray" },
7
+ {
8
+ "type": "object",
9
+ "required": ["operations"],
10
+ "properties": {
11
+ "operations": { "$ref": "#/$defs/operationArray" },
12
+ "expectedRevision": { "$ref": "#/$defs/revisionToken" }
13
+ },
14
+ "additionalProperties": false
15
+ }
16
+ ],
17
+ "$defs": {
18
+ "revisionToken": {
19
+ "type": "object",
20
+ "required": ["algorithm", "version", "scope", "value"],
21
+ "properties": {
22
+ "algorithm": { "type": "string", "enum": ["sha256"] },
23
+ "version": { "type": "integer", "enum": [1] },
24
+ "scope": { "type": "string", "enum": ["document-parts", "package"] },
25
+ "value": { "type": "string", "pattern": "^[0-9a-fA-F]{64}$" },
26
+ "coveredParts": { "type": "array", "items": { "type": "string" } }
27
+ },
28
+ "additionalProperties": false
29
+ },
30
+ "operationArray": { "type": "array", "items": { "$ref": "#/$defs/operation" } },
31
+ "rejectedTextInsertionAnchor": {
32
+ "type": "object",
33
+ "required": ["exactText", "offset"],
34
+ "properties": {
35
+ "exactText": { "type": "string", "minLength": 1 },
36
+ "occurrence": { "type": "integer", "minimum": 1 },
37
+ "offset": { "type": "integer", "minimum": 0 }
38
+ },
39
+ "additionalProperties": false
40
+ },
41
+ "localizedReplacement": {
42
+ "type": "object",
43
+ "required": ["find", "replace"],
44
+ "properties": {
45
+ "find": { "type": "string", "minLength": 1 },
46
+ "replace": { "type": "string" },
47
+ "occurrence": { "type": "integer", "minimum": 1 }
48
+ },
49
+ "additionalProperties": false
50
+ },
51
+ "target": {
52
+ "oneOf": [
53
+ { "type": "string", "minLength": 1 },
54
+ {
55
+ "type": "object",
56
+ "properties": {
57
+ "exactText": { "type": "string" }, "text": { "type": "string" },
58
+ "index": { "type": ["integer", "string"] }, "paragraphIndex": { "type": ["integer", "string"] },
59
+ "paragraphId": { "type": "string" }, "occurrence": { "type": "integer", "minimum": 1 },
60
+ "inTable": { "type": "boolean" }, "fingerprint": { "type": "string" }, "sourceFingerprint": { "type": "string" },
61
+ "revisionView": {
62
+ "type": "string",
63
+ "enum": ["accepted", "rejected"],
64
+ "description": "Defaults to rejected for restore operations and accepted for other operation types."
65
+ },
66
+ "captureRef": { "type": "string", "minLength": 1, "maxLength": 256 },
67
+ "select": { "type": "string", "minLength": 1 }
68
+ },
69
+ "additionalProperties": false,
70
+ "anyOf": [ { "required": ["exactText"] }, { "required": ["text"] }, { "required": ["index"] }, { "required": ["paragraphIndex"] }, { "required": ["paragraphId"] }, { "required": ["fingerprint"] }, { "required": ["sourceFingerprint"] }, { "required": ["captureRef"] } ]
71
+ }
72
+ ]
73
+ },
74
+ "headerFooterPart": {
75
+ "description": "Targets a header or footer instead of the body. Use a part path from inspect().headersFooters, or a selector. Comments are not supported in headers or footers.",
76
+ "oneOf": [
77
+ { "type": "string", "minLength": 1, "examples": ["word/footer2.xml"] },
78
+ {
79
+ "type": "object",
80
+ "required": ["kind"],
81
+ "properties": {
82
+ "kind": { "enum": ["header", "footer"] },
83
+ "type": { "enum": ["default", "first", "even"], "description": "Defaults to default." },
84
+ "section": { "type": "integer", "minimum": 0, "description": "Zero-based section; required when several sections have their own part." }
85
+ },
86
+ "additionalProperties": false
87
+ }
88
+ ]
89
+ },
90
+ "paragraphFormatProperties": {
91
+ "description": "Closed set. Unknown keys are rejected with INVALID_OPERATION (field properties.<key>) since 0.9.0.",
92
+ "type": "object",
93
+ "properties": {
94
+ "alignment": { "type": "string", "examples": ["left", "center", "right", "both"] },
95
+ "keepNext": { "type": "boolean" },
96
+ "keepLines": { "type": "boolean" },
97
+ "pageBreakBefore": { "type": "boolean" },
98
+ "style": { "type": "string", "description": "Paragraph style id (w:pStyle)." },
99
+ "numbering": {
100
+ "description": "Direct list binding recorded as a tracked w:pPrChange. An object binds the paragraph to an EXISTING numId at a level; null removes the direct binding. Never allocates a list. Refused with PARAGRAPH_NUMBERING_NOT_FOUND when the numId or level is not defined.",
101
+ "oneOf": [
102
+ { "type": "null" },
103
+ {
104
+ "type": "object",
105
+ "required": ["numId"],
106
+ "properties": {
107
+ "numId": { "oneOf": [ { "type": "integer", "minimum": 1 }, { "type": "string", "pattern": "^[1-9][0-9]*$" } ] },
108
+ "level": { "type": "integer", "minimum": 0, "maximum": 8, "default": 0 }
109
+ },
110
+ "additionalProperties": false
111
+ }
112
+ ]
113
+ }
114
+ },
115
+ "additionalProperties": false
116
+ },
117
+ "listFormat": {
118
+ "description": "list-format-v1 (CLI contract version 9). convert-plain binds plain paragraphs to a NEW list definition in place: the binding is a tracked w:pPrChange and, with strip \"marker\" (default), the typed marker (A. / 1. / 1) / iv. / - ) is removed with a tracked deletion; Reject All restores the literal marker and the unnumbered paragraph. Each marker's value is the item's start value; consecutive values share one list, and a later convert-plain in the same batch whose first value continues the previous one joins that list. Refusals: LIST_MARKER_NOT_FOUND, LIST_MARKER_AMBIGUOUS (single roman-looking letter such as I. or C.; set format), LIST_MARKER_UNSUPPORTED (mixed markers, multi-level 2.1., (a), marker not in plain runs, format mismatch), LIST_FORMAT_TARGET_IS_LIST, LIST_FORMAT_STYLE_NUMBERED, PARAGRAPH_FORMAT_REVISION_CONFLICT. change-format and join (capability list-format-change-v1, same contract version): change-format allocates a NEW abstractNum + num copying the source definition (levels, indentation, fonts) with only the requested level redefined, and rebinds the list items to it with one tracked w:pPrChange each; the shared definition is never edited in place, so Reject All restores the original labels and Accept All shows the new ones. join rebinds the target list to the numId of list.continueFrom (an earlier list) so it continues that count. Tracked-deleted paragraphs keep their numPr. Refusals: LIST_FORMAT_TARGET_NOT_LIST, LIST_FORMAT_MIXED_RANGE (non-list paragraphs, several lists, several levels), LIST_FORMAT_STYLE_LINKED, LIST_FORMAT_STYLE_NUMBERED, LIST_FORMAT_SPLIT_UNSUPPORTED (mid-sequence range, nested restart, shared abstract definition, interleaved join), LIST_FORMAT_DEFINITION_UNSUPPORTED (missing definition, instance level override, lvlRestart in a split), LIST_FORMAT_JOIN_INVALID, PARAGRAPH_FORMAT_REVISION_CONFLICT; LIST_FORMAT_UNCHANGED is a warning on a no-op. Body only (no part). Composition: one single-paragraph redline/replace of the same paragraph may share the batch when it keeps the typed marker exactly (\"1. Old\" -> \"1. New\" = redline + convert-plain); the redline always runs first. scope \"list\" counts as a write to every rebound item. Other combinations fail LIST_FORMAT_COMPOSITION_CONFLICT. detach and materializeMarker are not available.",
119
+ "type": "object",
120
+ "required": ["mode"],
121
+ "properties": {
122
+ "mode": { "enum": ["convert-plain", "change-format", "join"] },
123
+ "kind": { "enum": ["bullet", "numbered"] },
124
+ "format": { "enum": ["decimal", "upperLetter", "lowerLetter", "upperRoman", "lowerRoman", "bullet"], "description": "convert-plain: required with strip \"none\"; with strip \"marker\" it must match the typed markers and disambiguates letters from roman numerals. change-format: the new numbering format of the level." },
125
+ "level": { "type": "integer", "minimum": 0, "maximum": 8, "description": "convert-plain: the level to bind (default 0). change-format: the level to redefine (default: the single level the target paragraphs use; refused LIST_FORMAT_MIXED_RANGE reason levels when they use several)." },
126
+ "strip": { "enum": ["marker", "none"], "default": "marker", "description": "convert-plain only. marker: remove the typed marker (tracked). none: keep the text and only bind." },
127
+ "start": { "type": "integer", "minimum": 1, "maximum": 32767, "description": "convert-plain: strip \"none\" only; defaults to continuing the batch's previous list of the same format, else 1 (with strip \"marker\" the start is the typed marker's value). change-format: restart value of the level, written as the w:start of the new definition (scope \"list\" only)." },
128
+ "lvlText": { "type": "string", "minLength": 1, "maxLength": 32, "description": "change-format only: the level text. Numbered: %1..%9 placeholders including the level's own (\"%1.\", \"(%2)\"); bullet: the bullet character. Default: the source level text when the kind stays, \"%<level+1>.\" for bullet -> numbered, \"•\" for numbered -> bullet." },
129
+ "scope": { "enum": ["list", "range"], "default": "list", "description": "change-format and join. list: every live paragraph of the target's list is rebound (paragraphs outside the target range included; result items carry inTarget). range: only the target range, which must run to the end of its list (LIST_FORMAT_SPLIT_UNSUPPORTED reason mid-sequence otherwise); change-format continues the count with per-level start values on the new definition." },
130
+ "continueFrom": { "$ref": "#/$defs/target", "description": "join only (required): an item of the earlier list the target list continues; bound at batch start like target." }
131
+ },
132
+ "allOf": [
133
+ { "if": { "properties": { "mode": { "const": "convert-plain" } } }, "then": { "not": { "anyOf": [ { "required": ["lvlText"] }, { "required": ["scope"] }, { "required": ["continueFrom"] } ] } } },
134
+ { "if": { "properties": { "mode": { "const": "change-format" } } }, "then": { "anyOf": [ { "required": ["format"] }, { "required": ["kind"] }, { "required": ["lvlText"] }, { "required": ["start"] } ], "not": { "anyOf": [ { "required": ["strip"] }, { "required": ["continueFrom"] } ] } } },
135
+ { "if": { "properties": { "mode": { "const": "join" } } }, "then": { "required": ["continueFrom"], "not": { "anyOf": [ { "required": ["kind"] }, { "required": ["format"] }, { "required": ["level"] }, { "required": ["strip"] }, { "required": ["start"] }, { "required": ["lvlText"] } ] } } }
136
+ ],
137
+ "additionalProperties": false
138
+ },
139
+ "base": {
140
+ "type": "object",
141
+ "required": ["type", "target"],
142
+ "properties": {
143
+ "operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
144
+ "captureKey": { "type": "string", "minLength": 1, "maxLength": 256 },
145
+ "target": { "$ref": "#/$defs/target" }, "targetRef": { "type": ["integer", "string", "null"] },
146
+ "part": { "$ref": "#/$defs/headerFooterPart" },
147
+ "author": { "type": "string", "minLength": 1 }, "generateRedlines": { "type": "boolean" },
148
+ "existingRevisions": { "enum": ["merge-same-author", "slice-cross-author", "reject-input", "accept-all-first", "accept-all-first-keep-normalized"] },
149
+ "pairReplacements": { "type": "boolean" },
150
+ "listMarkers": { "const": "literal", "description": "Accepts list-marker text as literal text. Suppresses LIST_MARKER_NOT_BOUND / UNSUPPORTED_LIST_CONVERSION and LIST_FORMAT_NOT_APPLIED / UNSUPPORTED_LIST_FORMAT_CHANGE for this operation." },
151
+ "insertionAffinity": {
152
+ "type": "object",
153
+ "properties": {
154
+ "formatting": { "enum": ["left", "right", "none"] },
155
+ "hyperlink": { "enum": ["inside", "outside", "preserve"] },
156
+ "revision": { "enum": ["coalesce_same_author", "separate"] },
157
+ "bookmark": { "enum": ["inside", "outside"] },
158
+ "comment": { "enum": ["inside", "outside"] }
159
+ },
160
+ "additionalProperties": false
161
+ }
162
+ }
163
+ },
164
+ "operation": {
165
+ "oneOf": [
166
+ {
167
+ "type": "object",
168
+ "required": ["type", "parentCommentId", "commentContent"],
169
+ "properties": {
170
+ "type": { "const": "comment_reply" },
171
+ "operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
172
+ "parentCommentId": { "type": ["integer", "string"] },
173
+ "commentContent": { "type": "string", "minLength": 1 },
174
+ "author": { "type": "string", "minLength": 1 },
175
+ "date": { "type": "string", "format": "date-time" }
176
+ },
177
+ "additionalProperties": false
178
+ },
179
+ {
180
+ "type": "object",
181
+ "description": "Resolve or reopen the comment thread containing commentId. Resolved is thread-level in Word, so the root and every reply change together. Body-only; not allowed with part.",
182
+ "required": ["type", "commentId"],
183
+ "properties": {
184
+ "type": { "const": "comment_resolve" },
185
+ "operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
186
+ "commentId": { "type": ["integer", "string"] },
187
+ "resolved": { "type": "boolean", "default": true }
188
+ },
189
+ "additionalProperties": false
190
+ },
191
+ {
192
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["replacements"], "properties": { "type": { "enum": ["redline", "replace"] }, "replacements": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/localizedReplacement" } } } } ]
193
+ },
194
+ {
195
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["modified"], "properties": { "type": { "enum": ["redline", "replace", "format", "list-change", "table-reconciliation", "insert"] }, "modified": { "type": "string" }, "structuredContent": { "type": "boolean" }, "targetEnd": { "$ref": "#/$defs/target" }, "targetEndRef": { "type": ["integer", "string", "null"] }, "anchor": { "$ref": "#/$defs/rejectedTextInsertionAnchor" } } } ]
196
+ },
197
+ {
198
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "properties": { "type": { "const": "restore" }, "modified": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } } ] }, "replacements": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/localizedReplacement" } }, "targetEnd": { "$ref": "#/$defs/target" }, "targetEndRef": { "type": ["integer", "string", "null"] } }, "oneOf": [ { "required": ["modified"], "not": { "required": ["replacements"] } }, { "required": ["replacements"], "not": { "required": ["modified"] } }, { "not": { "anyOf": [ { "required": ["modified"] }, { "required": ["replacements"] } ] } } ] } ]
199
+ },
200
+ {
201
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "properties": { "type": { "const": "delete" }, "modified": { "const": "" } } } ]
202
+ },
203
+ {
204
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["commentContent"], "properties": { "type": { "const": "comment" }, "textToComment": { "type": "string" }, "commentContent": { "type": "string", "minLength": 1 } } } ]
205
+ },
206
+ {
207
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["textToHighlight"], "properties": { "type": { "const": "highlight" }, "textToHighlight": { "type": "string", "minLength": 1 }, "color": { "type": "string" } } } ]
208
+ },
209
+ {
210
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["textToFormat", "properties"], "properties": { "type": { "enum": ["format", "character-format"] }, "textToFormat": { "type": "string", "minLength": 1 }, "textOccurrence": { "type": "integer", "minimum": 1, "default": 1 }, "properties": { "type": "object" }, "formattingRevisionPolicy": { "enum": ["always", "coalesce-own-insertion"] } } } ]
211
+ },
212
+ {
213
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["list"], "not": { "anyOf": [ { "required": ["part"] }, { "required": ["modified"] }, { "required": ["replacements"] } ] }, "properties": { "type": { "const": "list-format" }, "list": { "$ref": "#/$defs/listFormat" }, "targetEnd": { "$ref": "#/$defs/target" }, "targetEndRef": { "type": ["integer", "string", "null"] } } } ]
214
+ },
215
+ {
216
+ "allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["properties"], "properties": { "type": { "const": "paragraph-format" }, "properties": { "$ref": "#/$defs/paragraphFormatProperties" } } } ]
217
+ }
218
+ ]
219
+ }
220
+ }
221
+ }