{ "$schema": "../_schema.json", "format": "docx", "element": "revision", "elementAliases": ["ins", "del", "moveto", "movefrom"], "parent": "paragraph|run|table|row|cell|section", "operations": { "add": false, "set": true, "get": true, "query": true, "remove": false, "note": "`set: true` covers the action verb (revision.action on a /revision selector or native path). `add: false` here means the *revision element itself* has no Add surface — tracked changes are created indirectly via `revision.type` / `revision.author` / `revision.date` / `revision.id` on the host run/paragraph/table/row/cell/section, where they read as `add: true` / `set: true`. Per-property add/set flags below describe behavior on the synthetic /revision node, not on the host." }, "paths": { "positional": ["/revision[N]", "/body/p[N]/ins[M]", "/body/p[N]/del[M]"], "selector": ["/revision", "/revision[@id=N]", "/revision[@id=N][@type=moveFrom|moveTo]", "/revision[@author=NAME]", "/revision[@type=ins|del|format|moveFrom|moveTo]", "/revision[@author=NAME][@type=TYPE]"] }, "note": "Tracked-change marker. `query revision` enumerates every w:ins / w:del / w:moveFrom / w:moveTo / w:*PrChange in the document. The synthetic Path is `/revision[@id=N]` when the marker carries a stable w:id (true for everything Word/WPS writes), falling back to positional `/revision[N]` only for markers without an id — same precedence as paragraphs (`p[@paraId=…]` → `p[N]`). When the same w:id is shared by more than one marker (canonical case: a moveFrom/moveTo pair binds via shared id), the Path is disambiguated as `/revision[@id=N][@type=moveFrom]` / `[@type=moveTo]` so single-end addressing stays unique. `set /revision[@id=N]` without the type segment still acts on both ends together (pair-wise accept/reject, the usual move-revision intent). Use the @id= form in scripts; positional indexes shift after accept/reject. Both `get /revision[@id=N]` and `set /revision[@id=N] --prop revision.action=…` accept the same path. Two disjoint property namespaces: `revision.type=ins|del|format|moveFrom|moveTo` creates a tracked change (used with add/set on the host run/paragraph/table/row/cell/section), while `revision.action=accept|reject` acts on existing markers via /revision selectors or native paths (/body/p[N], /body/p[N]/r[M], etc.). The two namespaces are mutually exclusive — mixing revision.action with any revision.type/.author/.date/.id key is rejected as ambiguous. Bare `revision=…` is no longer accepted. **Find + revision** (matches Word's Find&Replace with Track Changes on): `set --prop find=PATTERN [--prop regex=true] --prop revision.author=NAME` infers the marker shape from the operation — `--prop replace=NEW` produces a w:del(old) + w:ins(new) pair, `--prop replace=` (empty) produces w:del only, format props (bold, font.color, …) produce one w:rPrChange per matched run, and paragraph-scope props (align, indent, …) produce one w:pPrChange per matched paragraph. The handler auto-allocates a fresh revision.id per marker, so `--prop revision.id=…` is rejected on find (would collide across markers). `revision.type=ins|del|moveFrom|moveTo` and `revision.action=…` are also rejected on find — the shape is inferred and the action verb belongs on the selector, not the creation context. `find` / `replace` / `regex` themselves are cross-cutting Set keywords (not revision-specific properties; not listed below) — pairing any of them with `revision.author` routes the find pass through the track-changes pipeline. Separate from `revisionNumber` on / (document save counter) and `trackChanges` on /settings (track-mode toggle).", "properties": { "type": { "type": "enum", "values": ["ins", "del", "moveTo", "moveFrom", "format"], "add": false, "set": false, "get": true, "examples": ["query revision[@type=ins]"], "readback": "revision type (read-only on the revision node; create with --prop revision.type=… on the host element)", "enforcement": "report" }, "text": { "type": "string", "description": "text content for ins / content marker for del (read-only).", "add": false, "set": false, "get": true, "examples": [], "readback": "concatenated text (read-only)", "enforcement": "report" }, "author": { "type": "string", "description": "Author of the revision. Surfaced on `query revision` nodes and on host elements (run/paragraph/…) that carry an ins/del/*PrChange ancestor. Supply via --prop revision.author=… on the host element to create. **Run host is stricter:** `set --prop revision.author=X` alone is rejected — without an action to attribute, the legacy fallback wrote an empty-snapshot rPrChange (recording no change). Either pair revision.author with a real format prop (`--prop bold=true`), or pass `--prop revision.type=ins|del|moveFrom|moveTo` so the wrap itself is the action. The same strictness applies to bare `revision.type=format` on Run. Non-Run hosts (paragraph/table/row/cell/section) still accept bare attribution and emit an empty-snapshot *PrChange — needed for dump→batch round-trip of table-level markers whose pre-change baseline can't be reconstructed. Also accepted alongside `find` (and `replace` / format / paragraph props) — the find dispatch wraps each match in the matching marker shape and attributes it to this author.", "add": false, "set": false, "get": true, "examples": [ "query revision[@author=Alice]", "set /body --prop find=fox --prop replace=cat --prop revision.author=Alice", "set /body --prop find=red --prop bold=true --prop revision.author=Bob", "set /body --prop find=BAD --prop replace= --prop revision.author=Carol", "set /body --prop 'find=\\$\\d+' --prop regex=true --prop bold=true --prop revision.author=Dave" ], "readback": "revision author (read-only on the revision node)", "enforcement": "report" }, "date": { "type": "string", "description": "ISO-8601 timestamp (read-only on the revision node). Supply via --prop revision.date=… on the host element to create.", "add": false, "set": false, "get": true, "examples": [], "readback": "Date attribute (read-only)", "enforcement": "report" }, "id": { "type": "integer", "description": "w:id attribute. Stable across save; pair moveFrom/moveTo by reusing the same id. Supply via --prop revision.id=… on the host element.", "add": false, "set": false, "get": true, "examples": [], "readback": "revision id (read-only on the revision node)", "enforcement": "report" }, "nativePath": { "type": "string", "description": "Path to the underlying w:ins/w:del/w:moveFrom/w:moveTo/w:*PrChange element in document DOM terms (e.g. /body/p[2]/ins[1]). Use it to address the revision via the action native-path form: `set --prop revision.action=accept|reject`. Emitted on `query revision` synthetic nodes only.", "add": false, "set": false, "get": true, "examples": ["set /body/p[2]/ins[1] --prop revision.action=accept"], "readback": "DOM path to the revision element (read-only)", "enforcement": "report" }, "action": { "type": "enum", "values": ["accept", "reject"], "description": "Accept / reject verb. Lives in a disjoint namespace from the creation keys above; mixing with revision.type/.author/.date/.id throws. Walks matching elements in reverse document order so /revision[N] paths don't shift mid-batch.", "add": false, "set": true, "get": false, "examples": [ "set /revision --prop revision.action=accept", "set '/revision[@author=Alice]' --prop revision.action=reject", "set '/revision[@type=ins]' --prop revision.action=accept", "set /revision[@id=42] --prop revision.action=accept", "set '/revision[@id=42][@type=moveTo]' --prop revision.action=reject", "set /revision[3] --prop revision.action=accept", "set /body/p[1]/r[2] --prop revision.action=reject" ], "readback": "(action is a verb — not surfaced on get)", "enforcement": "strict" }, "paraMarkIns.author": { "type": "string", "description": "Author of a paragraph-mark-only insertion (w:rPr/w:ins inside the paragraph's pPr/rPr — the ¶ mark itself is part of the inserted change, distinct from a run-level w:ins wrapping content). Surfaced on paragraph nodes via get/query; on dump→batch the emitter rewrites it to bare revision.author on the synthetic paragraph + strips revision.type so the bare-attribution path defaults the type to ins.", "add": false, "set": false, "get": true, "examples": [], "readback": "paragraph-mark insertion author (read-only)", "enforcement": "report" }, "paraMarkIns.date": { "type": "string", "description": "ISO-8601 timestamp companion to paraMarkIns.author.", "add": false, "set": false, "get": true, "examples": [], "readback": "paragraph-mark insertion date (read-only)", "enforcement": "report" }, "paraMarkIns.id": { "type": "int", "description": "w:id of the paragraph-mark insertion. Get-only companion to paraMarkIns.author/.date.", "add": false, "set": false, "get": true, "enforcement": "report" }, "paraMarkDel.author": { "type": "string", "description": "Author of a paragraph-mark deletion (w:rPr/w:del in pPr/rPr — the ¶ join is tracked as a deletion). Deletion counterpart of paraMarkIns.author. Get-only.", "add": false, "set": false, "get": true, "enforcement": "report" }, "paraMarkDel.date": { "type": "string", "description": "ISO-8601 timestamp companion to paraMarkDel.author.", "add": false, "set": false, "get": true, "enforcement": "report" }, "paraMarkDel.id": { "type": "int", "description": "w:id of the paragraph-mark deletion. Get-only.", "add": false, "set": false, "get": true, "enforcement": "report" }, "revision.beforeLost": { "type": "bool", "description": "Get-only signal: true when a *PrChange 'before' property snapshot cannot round-trip through dump→batch (the prior-formatting baseline is lost on replay). Lets the batch emitter warn rather than silently drop it.", "add": false, "set": false, "get": true, "enforcement": "report" }, "sectPrChange.author": { "type": "string", "description": "Author of a w:sectPrChange on a section (revision.type=format equivalent at section scope; surfaced as a distinct key because the snapshot lives in a different OOXML wrapper than rPrChange/pPrChange).", "add": false, "set": false, "get": true, "examples": [], "readback": "section property-change author (read-only)", "enforcement": "report" }, "sectPrChange.date": { "type": "string", "description": "ISO-8601 timestamp companion to sectPrChange.author.", "add": false, "set": false, "get": true, "examples": [], "readback": "section property-change date (read-only)", "enforcement": "report" }, "sectPrChange.id": { "type": "string", "description": "Stable w:id companion to sectPrChange.author; preserved across dump→batch so the marker's identity survives the round-trip.", "add": false, "set": false, "get": true, "examples": [], "readback": "section property-change id (read-only)", "enforcement": "report" }, "tblPrChange.author": { "type": "string", "description": "Author of a w:tblPrChange on a table (table-properties revision — borders/style/layout snapshot).", "add": false, "set": false, "get": true, "examples": [], "readback": "table property-change author (read-only)", "enforcement": "report" }, "tblPrChange.date": { "type": "string", "description": "ISO-8601 timestamp companion to tblPrChange.author.", "add": false, "set": false, "get": true, "examples": [], "readback": "table property-change date (read-only)", "enforcement": "report" }, "trPrChange.author": { "type": "string", "description": "Author of a w:trPrChange on a table row (row-properties revision — row height/header/cantSplit snapshot). Separate from w:trPr/w:ins (= row insertion as tracked change).", "add": false, "set": false, "get": true, "examples": [], "readback": "row property-change author (read-only)", "enforcement": "report" }, "trPrChange.date": { "type": "string", "description": "ISO-8601 timestamp companion to trPrChange.author.", "add": false, "set": false, "get": true, "examples": [], "readback": "row property-change date (read-only)", "enforcement": "report" }, "tcPrChange.author": { "type": "string", "description": "Author of a w:tcPrChange on a table cell (cell-properties revision — borders/shading/width snapshot). Separate from w:tcPr/w:cellIns/w:cellDel (= cell insertion/deletion).", "add": false, "set": false, "get": true, "examples": [], "readback": "cell property-change author (read-only)", "enforcement": "report" }, "tcPrChange.date": { "type": "string", "description": "ISO-8601 timestamp companion to tcPrChange.author.", "add": false, "set": false, "get": true, "examples": [], "readback": "cell property-change date (read-only)", "enforcement": "report" } } }