@superdoc/mcp 1.2.0-next.12 → 1.2.0-next.14

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 (2) hide show
  1. package/dist/index.js +1378 -208
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -31314,7 +31314,7 @@ var init_host = __esm(() => {
31314
31314
  });
31315
31315
 
31316
31316
  // ../../packages/sdk/langs/node/src/runtime/sdk-version.generated.ts
31317
- var SDK_VERSION = "2.15.0-next.12";
31317
+ var SDK_VERSION = "2.15.0-next.14";
31318
31318
 
31319
31319
  // ../../packages/sdk/langs/node/src/runtime/embedded-platform.ts
31320
31320
  import { chmodSync, existsSync } from "node:fs";
@@ -134832,26 +134832,68 @@ function canonicalName(namespace, localName2) {
134832
134832
  return `xml:${localName2}`;
134833
134833
  return namespace ? `{${namespace}}${localName2}` : localName2;
134834
134834
  }
134835
- function computeRunSignature(runXml) {
134836
- const canonicalXml = canonicalizeXmlForSignature(runXml);
134837
- const normalized = normalizeRunXmlForSignature(canonicalXml.replace(REVISION_ATTR_RE, (attribute, offset, xml) => {
134838
- const precedingTag = xml.slice(xml.lastIndexOf("<", offset), offset);
134839
- return /^<w:proofErr(?:\s|$)/.test(precedingTag) ? attribute : "";
134840
- }).replace(REVISION_ELEMENT_RE, (markup) => markup.replace(REVISION_ID_ATTR_RE, "")).replace(COMMENT_MARKUP_RE, (markup, name, attributes) => isWordprocessingElement3(name, attributes, canonicalXml) ? "" : markup).replace(NOTE_REFERENCE_MARKUP_RE, (markup, runName, runAttributes, _rPrName, nestedName, nestedAttributes, standaloneName, standaloneAttributes) => {
134841
- const name = nestedName ?? standaloneName;
134842
- const attributes = nestedAttributes ?? standaloneAttributes;
134843
- if (!isWordprocessingElement3(name, attributes, canonicalXml))
134844
- return markup;
134845
- if (runName && !isWordprocessingElement3(runName, runAttributes, canonicalXml))
134846
- return markup;
134847
- return "";
134848
- }));
134849
- let hash2 = 2166136261;
134850
- for (let i = 0;i < normalized.length; i += 1) {
134851
- hash2 ^= normalized.charCodeAt(i);
134852
- hash2 = Math.imul(hash2, 16777619) >>> 0;
134835
+ function normalizeBalancedBookmarkIds(canonicalXmls) {
134836
+ const starts = new Map;
134837
+ const ends = new Set;
134838
+ const names = new Set;
134839
+ for (const xml of canonicalXmls) {
134840
+ const tags = [...xml.matchAll(BOOKMARK_TAG_RE)];
134841
+ if (tags.length !== [...xml.matchAll(BOOKMARK_OPENING_RE)].length)
134842
+ return canonicalXmls;
134843
+ for (const tag of tags) {
134844
+ if (tag[1] || !tag[0].endsWith("/>"))
134845
+ return canonicalXmls;
134846
+ const attributes = [...tag[3].matchAll(ATTRIBUTE_RE)];
134847
+ const ids = attributes.filter((attribute) => attribute[1] === "w:id");
134848
+ const bookmarkNames = attributes.filter((attribute) => attribute[1] === "w:name");
134849
+ const id = ids[0]?.[2] ?? ids[0]?.[3];
134850
+ if (ids.length !== 1 || !id || !/^\d+$/u.test(id))
134851
+ return canonicalXmls;
134852
+ if (tag[2] === "Start") {
134853
+ const name = bookmarkNames[0]?.[2] ?? bookmarkNames[0]?.[3];
134854
+ if (bookmarkNames.length !== 1 || !name || starts.has(id) || names.has(name))
134855
+ return canonicalXmls;
134856
+ starts.set(id, name);
134857
+ names.add(name);
134858
+ } else {
134859
+ if (bookmarkNames.length > 0 || !starts.has(id) || ends.has(id))
134860
+ return canonicalXmls;
134861
+ ends.add(id);
134862
+ }
134863
+ }
134853
134864
  }
134854
- return `v2-run-${hash2.toString(16).padStart(8, "0")}`;
134865
+ if (starts.size !== ends.size)
134866
+ return canonicalXmls;
134867
+ return canonicalXmls.map((xml) => xml.replace(BOOKMARK_TAG_RE, (tag, _closing, _kind, attributes) => {
134868
+ const id = /\bw:id="([^"]*)"/u.exec(attributes)[1];
134869
+ return tag.replace(/\bw:id="[^"]*"/u, `w:id=${JSON.stringify(`bookmark:${starts.get(id)}`)}`);
134870
+ }));
134871
+ }
134872
+ function computeRunSignatures(runXmls) {
134873
+ const canonicalXmls = normalizeBalancedBookmarkIds(runXmls.map(canonicalizeXmlForSignature));
134874
+ return canonicalXmls.map((canonicalXml) => {
134875
+ const normalized = normalizeRunXmlForSignature(canonicalXml.replace(REVISION_ATTR_RE, (attribute, offset, xml) => {
134876
+ const precedingTag = xml.slice(xml.lastIndexOf("<", offset), offset);
134877
+ return /^<w:proofErr(?:\s|$)/.test(precedingTag) ? attribute : "";
134878
+ }).replace(REVISION_ELEMENT_RE, (markup) => markup.replace(REVISION_ID_ATTR_RE, "")).replace(COMMENT_MARKUP_RE, (markup, name, attributes) => isWordprocessingElement3(name, attributes, canonicalXml) ? "" : markup).replace(NOTE_REFERENCE_MARKUP_RE, (markup, runName, runAttributes, _rPrName, nestedName, nestedAttributes, standaloneName, standaloneAttributes) => {
134879
+ const name = nestedName ?? standaloneName;
134880
+ const attributes = nestedAttributes ?? standaloneAttributes;
134881
+ if (!isWordprocessingElement3(name, attributes, canonicalXml))
134882
+ return markup;
134883
+ if (runName && !isWordprocessingElement3(runName, runAttributes, canonicalXml))
134884
+ return markup;
134885
+ return "";
134886
+ }));
134887
+ let hash2 = 2166136261;
134888
+ for (let i = 0;i < normalized.length; i += 1) {
134889
+ hash2 ^= normalized.charCodeAt(i);
134890
+ hash2 = Math.imul(hash2, 16777619) >>> 0;
134891
+ }
134892
+ return `v2-run-${hash2.toString(16).padStart(8, "0")}`;
134893
+ });
134894
+ }
134895
+ function computeRunSignature(runXml) {
134896
+ return computeRunSignatures([runXml])[0];
134855
134897
  }
134856
134898
  function normalizeRunXmlForSignature(runXml) {
134857
134899
  const pieces = [];
@@ -134934,7 +134976,7 @@ function isWordprocessingElement3(name, attributes, namespaceContext) {
134934
134976
  return true;
134935
134977
  return (declaration[2] ?? declaration[3] ?? "") === WORDPROCESSINGML_NAMESPACE2;
134936
134978
  }
134937
- var REVISION_ATTR_RE, REVISION_ELEMENT_RE, REVISION_ID_ATTR_RE, COMMENT_MARKUP_RE, PROOFING_BOUNDARY_MARKUP_RE, NOTE_REFERENCE_MARKUP_RE, RUN_RE, RUN_PROPERTIES_RE, EMPTY_RUN_PROPERTIES_RE, TEXTISH_RE, RUN_CONTROL_RE, ATTRIBUTE_RE, WORDPROCESSINGML_NAMESPACE2 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main", WORDPROCESSINGML_2010_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml", MARKUP_COMPATIBILITY_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006", XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace", XML_TAG_RE;
134979
+ var REVISION_ATTR_RE, REVISION_ELEMENT_RE, REVISION_ID_ATTR_RE, COMMENT_MARKUP_RE, PROOFING_BOUNDARY_MARKUP_RE, NOTE_REFERENCE_MARKUP_RE, RUN_RE, RUN_PROPERTIES_RE, EMPTY_RUN_PROPERTIES_RE, TEXTISH_RE, RUN_CONTROL_RE, ATTRIBUTE_RE, WORDPROCESSINGML_NAMESPACE2 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main", WORDPROCESSINGML_2010_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml", MARKUP_COMPATIBILITY_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006", XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace", XML_TAG_RE, BOOKMARK_OPENING_RE, BOOKMARK_TAG_RE;
134938
134980
  var init_run_signature = __esm(() => {
134939
134981
  REVISION_ATTR_RE = /\s+(?:w:rsid\w*|w14:paraId|w14:textId)="[^"]*"/g;
134940
134982
  REVISION_ELEMENT_RE = /<w:(?:ins|del|moveFrom|moveTo)\b[^>]*>/g;
@@ -134949,6 +134991,8 @@ var init_run_signature = __esm(() => {
134949
134991
  RUN_CONTROL_RE = /<((?:[\w.-]+:)?(tab|br|cr))\b([^>]*)\/>/g;
134950
134992
  ATTRIBUTE_RE = /([\w.:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
134951
134993
  XML_TAG_RE = /<(\/?)(([A-Za-z_][\w.-]*)(?::([A-Za-z_][\w.-]*))?)((?:(?:"[^"]*"|'[^']*'|[^>"'])*))>/g;
134994
+ BOOKMARK_OPENING_RE = /<\/?w:bookmark(?:Start|End)\b/g;
134995
+ BOOKMARK_TAG_RE = /<(\/?)w:bookmark(Start|End)\b((?:"[^"]*"|[^">])*)>/g;
134952
134996
  });
134953
134997
 
134954
134998
  // ../../../v2/document-compare/src/note-story-projection.ts
@@ -134960,7 +135004,7 @@ function projectCapturedNoteStoryEntries(value) {
134960
135004
  const ranges = collectWordParagraphRanges(value.notePartXml);
134961
135005
  if (!ranges)
134962
135006
  return null;
134963
- return ranges.flatMap((range, ordinal) => {
135007
+ const entries = ranges.flatMap((range, ordinal) => {
134964
135008
  if (range.start < noteStart || range.end > noteEnd)
134965
135009
  return [];
134966
135010
  const paragraphXml = value.notePartXml.slice(range.start, range.end);
@@ -134974,12 +135018,13 @@ function projectCapturedNoteStoryEntries(value) {
134974
135018
  text: visibleParagraphText(paragraphXml, range.namespaceBindings),
134975
135019
  headingLevel: metadata2.headingLevel,
134976
135020
  styleId: metadata2.styleId,
134977
- runSignature: computeRunSignature(runXml),
134978
135021
  runXml,
134979
135022
  paragraphPropertiesXml,
134980
135023
  paragraphPropertiesSignature: computeParagraphPropertiesSignature(paragraphPropertiesXml)
134981
135024
  }];
134982
135025
  });
135026
+ const runSignatures = computeRunSignatures(entries.map((entry) => entry.runXml ?? ""));
135027
+ return entries.map((entry, index) => ({ ...entry, runSignature: runSignatures[index] }));
134983
135028
  }
134984
135029
  function collectWordParagraphRanges(xml) {
134985
135030
  let valid = true;
@@ -188376,6 +188421,8 @@ function renderPasteParagraphBlock(spec) {
188376
188421
  return `<w:p${nsDecl} w14:paraId="${escapeXmlAttribute(spec.paraId)}">${pPr}${renderPasteRuns(spec.runs)}</w:p>`;
188377
188422
  }
188378
188423
  function renderReviewAwarePasteRun(run, stamp) {
188424
+ if (run.kind === "sdt")
188425
+ return renderPasteSdt(run, stamp);
188379
188426
  let xml = renderPasteRun(run);
188380
188427
  if (!("text" in run))
188381
188428
  return xml;
@@ -188516,6 +188563,11 @@ function renderTrackedPasteRuns(runs, stamp) {
188516
188563
  out.push(wrapTrackedInsertion(inner, stamp));
188517
188564
  };
188518
188565
  for (const run of runs) {
188566
+ if (run.kind === "sdt") {
188567
+ flushOrdinary();
188568
+ out.push(wrapTrackedInsertion(renderPasteSdt(run), stamp));
188569
+ continue;
188570
+ }
188519
188571
  if (run.kind !== "hyperlink") {
188520
188572
  if (run.kind === "footnoteRef" || run.kind === "endnoteRef") {
188521
188573
  flushOrdinary();
@@ -188581,6 +188633,8 @@ function renderPasteTableCell(cell, renderParagraph = renderPasteParagraphBlock)
188581
188633
  return `<w:tc>${tcPr}${cell.paragraphs.map(renderParagraph).join("")}</w:tc>`;
188582
188634
  }
188583
188635
  function renderPasteRun(run, options = {}) {
188636
+ if (run.kind === "sdt")
188637
+ return renderPasteSdt(run);
188584
188638
  if (run.kind === "image")
188585
188639
  return run.xml;
188586
188640
  if (run.kind === "hyperlink")
@@ -188611,6 +188665,32 @@ function renderPasteRun(run, options = {}) {
188611
188665
  }
188612
188666
  return out.join("");
188613
188667
  }
188668
+ function renderPasteSdt(run, stamp) {
188669
+ const properties = (run.alias !== undefined ? `<w:alias w:val="${escapeXmlAttribute(run.alias)}"/>` : "") + (run.tag !== undefined ? `<w:tag w:val="${escapeXmlAttribute(run.tag)}"/>` : "") + `<w:id w:val="${escapeXmlAttribute(run.id)}"/>` + (run.lockMode !== undefined ? `<w:lock w:val="${run.lockMode}"/>` : "") + (run.controlType === "text" && run.multiline !== undefined ? `<w:text w:multiLine="${run.multiline ? "1" : "0"}"/>` : `<w:${run.controlType}/>`);
188670
+ return `<w:sdt><w:sdtPr>${properties}</w:sdtPr><w:sdtContent>${renderPasteRuns(run.runs, stamp)}</w:sdtContent></w:sdt>`;
188671
+ }
188672
+ function renderBlockSdtSequence(blocks, xml, ids) {
188673
+ let result = "";
188674
+ let previous = [];
188675
+ for (let index = 0;index <= blocks.length; index += 1) {
188676
+ const block = blocks[index];
188677
+ const current = block && "blockSdts" in block ? block.blockSdts ?? [] : [];
188678
+ let shared = 0;
188679
+ while (shared < previous.length && shared < current.length && previous[shared].sourceId === current[shared].sourceId)
188680
+ shared += 1;
188681
+ for (let depth = previous.length - 1;depth >= shared; depth -= 1)
188682
+ result += "</w:sdtContent></w:sdt>";
188683
+ for (let depth = shared;depth < current.length; depth += 1) {
188684
+ const wrapper = current[depth];
188685
+ const id = ids.get(wrapper.sourceId);
188686
+ result += "<w:sdt><w:sdtPr>" + (wrapper.alias !== undefined ? `<w:alias w:val="${escapeXmlAttribute(wrapper.alias)}"/>` : "") + (wrapper.tag !== undefined ? `<w:tag w:val="${escapeXmlAttribute(wrapper.tag)}"/>` : "") + `<w:id w:val="${escapeXmlAttribute(id)}"/>` + (wrapper.lockMode !== undefined ? `<w:lock w:val="${wrapper.lockMode}"/>` : "") + (wrapper.controlType === "text" && wrapper.multiline !== undefined ? `<w:text w:multiLine="${wrapper.multiline ? "1" : "0"}"/>` : `<w:${wrapper.controlType}/>`) + "</w:sdtPr><w:sdtContent>";
188687
+ }
188688
+ if (index < blocks.length)
188689
+ result += xml[index];
188690
+ previous = current;
188691
+ }
188692
+ return result;
188693
+ }
188614
188694
  function renderPasteHyperlink(run) {
188615
188695
  const targetAttribute = run.target.kind === "external" ? `r:id="${escapeXmlAttribute(run.target.relationshipId ?? "")}"` : `w:anchor="${escapeXmlAttribute(run.target.anchor)}"`;
188616
188696
  const tooltipAttribute = run.tooltip ? ` w:tooltip="${escapeXmlAttribute(run.tooltip)}"` : "";
@@ -189891,6 +189971,18 @@ function compileParsedOverlapEdit(input, tokens, descendedEditableContainers) {
189891
189971
  });
189892
189972
  token._classification = input.existingRevisionOwnership === "foreign" ? "different-user" : classifyOwnership(current, wrapperAuthor);
189893
189973
  }
189974
+ const commented = wrappers.filter((entry) => entry.token.kind === "ins" && entry.token.commentedBoundary != null);
189975
+ if (commented.length > 0) {
189976
+ const parent = commented[0].token;
189977
+ const following = touched.filter((entry) => entry.token.kind === "plain");
189978
+ if (commented.length !== 1 || wrappers.length !== 1 || following.length !== 1 || following[0].token.byteStart < parent.byteEnd || parent._classification === "same-user" || input.changeMode !== "tracked" || input.newText.length === 0 || input.rangeStart <= parent.visibleStart || input.rangeStart >= parent.visibleEnd || input.rangeEnd <= parent.visibleEnd || input.rangeEnd > following[0].token.visibleEnd) {
189979
+ return {
189980
+ ok: false,
189981
+ reason: "text-range-in-unsupported-tracked-wrapper",
189982
+ detail: "overlap compiler: comment-bearing insertion requires a bounded foreign spanning replacement."
189983
+ };
189984
+ }
189985
+ }
189894
189986
  if (input.newText.length > 0 && input.rangeStart < input.rangeEnd) {
189895
189987
  const spanningReplacement = tryRangeReplacementAcrossTokens(input, tokens, touched);
189896
189988
  if (spanningReplacement)
@@ -190531,8 +190623,21 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
190531
190623
  author: currentAuthor,
190532
190624
  date: input.txTimestamp,
190533
190625
  rsidDel: input.rsid,
190534
- inner: { runOpen: token.inner.runOpen, runClose: token.inner.runClose, rPr: token.inner.rPr, text: removed }
190626
+ inner: { runOpen: token.inner.runOpen, runClose: token.inner.runClose, rPr: token.inner.rPr, text: removed },
190627
+ ...token.commentedBoundary ? {
190628
+ innerXml: renderDelRun(removed, token.inner.runOpen, token.inner.runClose, token.inner.rPr) + token.commentedBoundary.endXml + token.commentedBoundary.referenceRunXml
190629
+ } : {}
190535
190630
  });
190631
+ if (token.commentedBoundary) {
190632
+ if (!childIns || tail.length > 0)
190633
+ return null;
190634
+ pieces.push(token.commentedBoundary.startXml + token.openTag + nestedHead + childDel + token.closeTag + childIns);
190635
+ replacementInserted = true;
190636
+ if (!emittedRsids.includes(input.insRsid))
190637
+ emittedRsids.push(input.insRsid);
190638
+ emitDeletionRsid();
190639
+ continue;
190640
+ }
190536
190641
  if (childIns) {
190537
190642
  const headIns = head.length > 0 ? buildInsWrapperXml({
190538
190643
  openTag: token.openTag,
@@ -191893,6 +191998,55 @@ function isSupportedStructuralDeletionChild(piece) {
191893
191998
  }
191894
191999
  return piece.containerKind === "unknown" && (piece.localName === "smartTag" || piece.localName === "dir" || piece.localName === "bdo");
191895
192000
  }
192001
+ function readCommentedInsertionBoundary(piece) {
192002
+ const markers = piece.children.filter((child) => child.kind === "marker");
192003
+ if (markers.length !== 3 || piece.children.length !== 4)
192004
+ return null;
192005
+ const [start, end, reference] = markers;
192006
+ if (start.markerKind !== "commentRangeStart" || end.markerKind !== "commentRangeEnd" || reference.markerKind !== "commentReference" || !start.id || start.id !== end.id || start.id !== reference.id || start.runCharStart != null || end.runCharStart != null)
192007
+ return null;
192008
+ const text = piece.children.find((child) => child.kind === "text");
192009
+ if (!text || !text.decodedText)
192010
+ return null;
192011
+ const source = piece.rawXml;
192012
+ const base2 = piece.charStart;
192013
+ const openEnd = findXmlTagEnd(source, 1);
192014
+ const closeStart = source.lastIndexOf(`</${piece.qname}>`);
192015
+ const outsideBindings = Object.fromEntries(piece.namespaceBindings);
192016
+ for (const attribute of scanRawXmlAttributes(source.slice(0, openEnd))) {
192017
+ if (attribute.name === "xmlns" || attribute.prefix === "xmlns") {
192018
+ delete outsideBindings[attribute.name === "xmlns" ? "" : attribute.localName];
192019
+ }
192020
+ }
192021
+ const startXml = closeNamespaceScope(start.rawXml, Object.fromEntries(start.namespaceBindings), outsideBindings, { referencedOnly: true });
192022
+ if (startXml == null)
192023
+ return null;
192024
+ const textRunStart = text.runCharStart - base2;
192025
+ const textRunEnd = text.runCharEnd - base2;
192026
+ const startStart = start.charStart - base2;
192027
+ const startEnd = start.charEnd - base2;
192028
+ const endStart = end.charStart - base2;
192029
+ const endEnd = end.charEnd - base2;
192030
+ if (openEnd < 0 || closeStart < 0 || startStart < openEnd || startEnd > textRunStart || textRunEnd > endStart || endEnd > reference.charStart - base2 || [
192031
+ source.slice(openEnd, startStart),
192032
+ source.slice(startEnd, textRunStart),
192033
+ source.slice(textRunEnd, endStart)
192034
+ ].some((gap) => gap.trim().length > 0))
192035
+ return null;
192036
+ const textRun = source.slice(textRunStart, textRunEnd);
192037
+ if (textRun.replace(text.runOpenTag, "").replace(text.runProperties ?? "", "").replace(text.rawXml, "").replace(text.runCloseTag, "").trim().length > 0)
192038
+ return null;
192039
+ const referenceRunXml = source.slice(endEnd, closeStart).trim();
192040
+ const referenceOpenEnd = findXmlTagEnd(referenceRunXml, 1);
192041
+ const referenceCloseStart = referenceRunXml.lastIndexOf("</");
192042
+ if (referenceOpenEnd < 0 || referenceCloseStart < referenceOpenEnd || !/^<([\w.-]+:)?r(?=[\s>])/.test(referenceRunXml) || !/<\/[\w.-]+:r>$/.test(referenceRunXml) || referenceRunXml.slice(referenceOpenEnd, referenceCloseStart).trim() !== reference.rawXml)
192043
+ return null;
192044
+ return {
192045
+ startXml,
192046
+ endXml: end.rawXml,
192047
+ referenceRunXml
192048
+ };
192049
+ }
191896
192050
  function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStructuralDeletionPassThrough) {
191897
192051
  const visibleStart = spanStart(piece);
191898
192052
  const visibleEnd = spanEnd(piece);
@@ -191932,6 +192086,7 @@ function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStr
191932
192086
  rawXml: child.rawXml
191933
192087
  })).sort((a, b) => a.localOffset - b.localOffset);
191934
192088
  }
192089
+ const commentedBoundary = piece.trackedKind === "ins" && !nestedDeletions ? readCommentedInsertionBoundary(piece) : null;
191935
192090
  if (allowComplexDeletions && piece.trackedKind === "del") {
191936
192091
  const unsupportedChild = piece.children.find((child) => child.kind !== "text" && !(child.kind === "marker" && isCommentMarker2(child.markerKind)) && !(allowStructuralDeletionPassThrough && isSupportedStructuralDeletionChild(child)));
191937
192092
  if (unsupportedChild) {
@@ -191994,7 +192149,7 @@ function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStr
191994
192149
  }
191995
192150
  };
191996
192151
  }
191997
- const foreignChild = piece.children.find((child) => child.kind === "container" || child.kind === "unknown" || child.kind === "noteReference" || child.kind === "control" && !(piece.trackedKind === "ins" && child.control === "tab") || child.kind === "marker");
192152
+ const foreignChild = piece.children.find((child) => child.kind === "container" || child.kind === "unknown" || child.kind === "noteReference" || child.kind === "control" && !(piece.trackedKind === "ins" && child.control === "tab") || child.kind === "marker" && !commentedBoundary);
191998
192153
  if (foreignChild) {
191999
192154
  if (foreignChild.kind === "container") {
192000
192155
  return unsupported2(containerRejectionReason(foreignChild.containerKind), `overlap compiler: ${foreignChild.containerKind} inside w:${piece.trackedKind}`);
@@ -192059,7 +192214,13 @@ function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStr
192059
192214
  },
192060
192215
  ...innerRuns.length > 1 ? { innerRuns } : {}
192061
192216
  };
192062
- return piece.trackedKind === "ins" ? { kind: "ins", ...base2, rsidR, ...nestedDeletions ? { nestedDeletions } : {} } : { kind: "del", ...base2, rsidDel };
192217
+ return piece.trackedKind === "ins" ? {
192218
+ kind: "ins",
192219
+ ...base2,
192220
+ rsidR,
192221
+ ...nestedDeletions ? { nestedDeletions } : {},
192222
+ ...commentedBoundary ? { commentedBoundary } : {}
192223
+ } : { kind: "del", ...base2, rsidDel };
192063
192224
  }
192064
192225
  function isCommentMarker2(markerKind) {
192065
192226
  return markerKind === "commentRangeStart" || markerKind === "commentRangeEnd" || markerKind === "commentReference";
@@ -192110,6 +192271,7 @@ var init_overlap_edit_compiler = __esm(() => {
192110
192271
  init_word_text_payload();
192111
192272
  init_xml_text();
192112
192273
  init_xml_sax();
192274
+ init_xml_namespace_scope();
192113
192275
  init_word_text_names();
192114
192276
  init_paragraph_inline_model();
192115
192277
  init_comment_cascade();
@@ -206730,6 +206892,8 @@ function collectRetiredTrackedChangeCommentMetadataIds(op, logicalCatalog) {
206730
206892
  return ids;
206731
206893
  }
206732
206894
  function applyRetiredTrackedChangeCommentMetadataCleanup(args) {
206895
+ if (args.perPart[COMMENTS_PART_URI]?.kind === "remove")
206896
+ return [];
206733
206897
  const commentsBytes = args.state.packageBytes.get(COMMENTS_PART_URI);
206734
206898
  if (!commentsBytes || args.retiredSourceIds.size === 0)
206735
206899
  return [];
@@ -206831,16 +206995,45 @@ function applyCommentCascade(args) {
206831
206995
  const next = removeCommentCatalogEntries(TD46.decode(commentsBytes), wids);
206832
206996
  if (!next.ok)
206833
206997
  return { ok: false, reason: next.reason };
206834
- const patch = {
206835
- kind: "replaceBytes",
206836
- order: 0,
206837
- startByte: 0,
206838
- endByte: commentsBytes.length,
206839
- bytes: TE23.encode(next.xml)
206840
- };
206841
- args.perPart[COMMENTS_PART_URI] = { kind: "modify", patches: [patch] };
206842
- args.preconditions.push(byteWindowPrecondition4(COMMENTS_PART_URI, commentsBytes));
206843
- args.patchMaps[COMMENTS_PART_URI] = { partUri: COMMENTS_PART_URI, ranges: [] };
206998
+ const parsedComments = collectXmlElementRanges(next.xml);
206999
+ const commentsRoot = parsedComments.ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "comments");
207000
+ const emptyComments = parsedComments.errors.length === 0 && commentsRoot && next.xml.slice(commentsRoot.openEnd, commentsRoot.closeStart).trim().length === 0;
207001
+ const hasCommentSidecars = args.state.packageBytes.has(COMMENTS_EXTENDED_PART_URI) || args.state.packageBytes.has(COMMENTS_IDS_PART_URI);
207002
+ if (emptyComments && !hasCommentSidecars) {
207003
+ args.perPart[COMMENTS_PART_URI] = { kind: "remove" };
207004
+ args.preconditions.push(byteWindowPrecondition4(COMMENTS_PART_URI, commentsBytes));
207005
+ args.patchMaps[COMMENTS_PART_URI] = { partUri: COMMENTS_PART_URI, ranges: [] };
207006
+ const relsBytes = args.state.packageBytes.get(DOCUMENT_RELS_PART_URI3);
207007
+ if (relsBytes) {
207008
+ const relsXml = currentPartXml(args.state, args.perPart, DOCUMENT_RELS_PART_URI3, XML_TD) ?? XML_TD.decode(relsBytes);
207009
+ const removed = removeRelationshipForPartAndTypeXml({
207010
+ relsXml,
207011
+ ownerPartUri: DOCUMENT_PART_URI,
207012
+ partUri: COMMENTS_PART_URI,
207013
+ relationshipType: COMMENTS_RELATIONSHIP_TYPE
207014
+ });
207015
+ if (!removed.ok)
207016
+ return { ok: false, reason: "unsupported-context" };
207017
+ if (removed.value.xml !== relsXml) {
207018
+ setWholePartReplacement(args, DOCUMENT_RELS_PART_URI3, relsBytes, removed.value.xml);
207019
+ }
207020
+ }
207021
+ const contentTypesBytes = args.state.packageBytes.get(CONTENT_TYPES_PART_URI4);
207022
+ if (contentTypesBytes) {
207023
+ const contentTypesXml = currentPartXml(args.state, args.perPart, CONTENT_TYPES_PART_URI4, XML_TD) ?? XML_TD.decode(contentTypesBytes);
207024
+ const removed = removeContentTypeOverrideXml({
207025
+ contentTypesXml,
207026
+ partName: COMMENTS_PART_URI
207027
+ });
207028
+ if (!removed.ok)
207029
+ return { ok: false, reason: "unsupported-context" };
207030
+ if (removed.value.xml !== contentTypesXml) {
207031
+ setWholePartReplacement(args, CONTENT_TYPES_PART_URI4, contentTypesBytes, removed.value.xml);
207032
+ }
207033
+ }
207034
+ } else {
207035
+ setWholePartReplacement(args, COMMENTS_PART_URI, commentsBytes, next.xml);
207036
+ }
206844
207037
  }
206845
207038
  const extendedBytes = args.state.packageBytes.get(COMMENTS_EXTENDED_PART_URI);
206846
207039
  if (extendedBytes && removedParaIds.size > 0) {
@@ -209038,7 +209231,7 @@ function dedupeRemappedRefs(refs) {
209038
209231
  function refKey(ref3) {
209039
209232
  return `${ref3.kind}|${ref3.entityId ?? ref3.commentId ?? ""}|${ref3.storyId ?? ""}`;
209040
209233
  }
209041
- var TD46, XML_TD, TE23, DOCUMENT_PART_URI = "/word/document.xml", DOCUMENT_RELS_PART_URI3 = "/word/_rels/document.xml.rels", NUMBERING_PART_URI2 = "/word/numbering.xml", CONTENT_TYPES_PART_URI4 = "/[Content_Types].xml", NUMBERING_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering", NOTE_LIFECYCLE_PROVENANCE_NS = "urn:superdoc:note-lifecycle", NOTE_FAMILY_PROFILE, INDEXABLE_STRUCTURAL_KINDS, TRACKED_TABLE_COLUMN_RESTORE_NS = "urn:superdoc:tracked-table-column";
209234
+ var TD46, XML_TD, TE23, DOCUMENT_PART_URI = "/word/document.xml", DOCUMENT_RELS_PART_URI3 = "/word/_rels/document.xml.rels", NUMBERING_PART_URI2 = "/word/numbering.xml", CONTENT_TYPES_PART_URI4 = "/[Content_Types].xml", NUMBERING_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering", COMMENTS_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments", NOTE_LIFECYCLE_PROVENANCE_NS = "urn:superdoc:note-lifecycle", NOTE_FAMILY_PROFILE, INDEXABLE_STRUCTURAL_KINDS, TRACKED_TABLE_COLUMN_RESTORE_NS = "urn:superdoc:tracked-table-column";
209042
209235
  var init_compile_tracked_change_decide = __esm(() => {
209043
209236
  init_empty_paragraph_move();
209044
209237
  init_native_sdt_ownership();
@@ -220127,6 +220320,18 @@ function collectProtectedReferenceTokens(xml, referenceSteps) {
220127
220320
  const tokens = [];
220128
220321
  for (let index = 0;index < referenceSteps.length; index += 1) {
220129
220322
  const step2 = referenceSteps[index];
220323
+ if (step2.kind === "bookmark-range" || step2.kind === "complex-field") {
220324
+ const fragment2 = step2.beforeFragmentXml;
220325
+ if (!fragment2)
220326
+ return null;
220327
+ const start = xml.indexOf(fragment2);
220328
+ if (start >= 0) {
220329
+ if (xml.indexOf(fragment2, start + fragment2.length) >= 0)
220330
+ return null;
220331
+ tokens.push({ key: `reference:${index}`, start, end: start + fragment2.length, xml: fragment2 });
220332
+ }
220333
+ continue;
220334
+ }
220130
220335
  const expectedLocalName = step2.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
220131
220336
  const parsed = collectXmlElementRanges(xml, {
220132
220337
  fragment: true,
@@ -221281,7 +221486,7 @@ function validateLocalizedMainDocumentReplay(state, sectionSteps, referenceSteps
221281
221486
  function uniqueLocalizedReplayStart(xml, step2, documentRanges, allowRootNamespaceDeclarations = false) {
221282
221487
  const ranges = documentRanges ?? collectXmlElementRanges(xml).ranges;
221283
221488
  if (allowRootNamespaceDeclarations && isReferenceReplayStep(step2)) {
221284
- const expectedLocalName = step2.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
221489
+ const expectedLocalName = referenceReplayElementName(step2);
221285
221490
  const starts2 = ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === expectedLocalName && replayCarrierXmlEquals(xml.slice(range.start, range.end), step2.beforeXml)).map((range) => range.start);
221286
221491
  return starts2.length === 1 ? starts2[0] : null;
221287
221492
  }
@@ -221302,7 +221507,7 @@ function isInsideWordElement(ranges, position, localName2) {
221302
221507
  return ranges.some((range) => range.tag.namespaceUri === NS_W && range.tag.localName === localName2 && range.start < position && range.end > position);
221303
221508
  }
221304
221509
  function localizedReplayContext(xml, step2, start, documentRanges, allowRootNamespaceDeclarations = false) {
221305
- const expectedLocalName = isReferenceReplayStep(step2) ? step2.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple" : "sectPr";
221510
+ const expectedLocalName = isReferenceReplayStep(step2) ? referenceReplayElementName(step2) : "sectPr";
221306
221511
  const ranges = documentRanges ?? collectXmlElementRanges(xml).ranges;
221307
221512
  const before = ranges.find((range) => range.start === start && range.tag.namespaceUri === NS_W && range.tag.localName === expectedLocalName && (range.end === start + step2.beforeXml.length || allowRootNamespaceDeclarations && isReferenceReplayStep(step2) && replayCarrierXmlEquals(xml.slice(range.start, range.end), step2.beforeXml)));
221308
221513
  if (!before)
@@ -221336,8 +221541,55 @@ function isSafeReferenceReplayStep(step2, context) {
221336
221541
  const afterName = readResolvedReplayAttribute(context.after, "name");
221337
221542
  return afterName !== null && readResolvedReplayAttribute(context.before, "id") === readResolvedReplayAttribute(context.after, "id") && replaceReplayAttribute(step2.beforeXml, context.before, "name", afterName) === step2.afterXml;
221338
221543
  }
221339
- const instruction = readResolvedReplayAttribute(context.after, "instr");
221340
- return instruction !== null && /^\s*REF\s+[A-Za-z_][A-Za-z0-9_.]*(?:\s|$)/i.test(instruction) && replaceReplayAttribute(step2.beforeXml, context.before, "instr", instruction) === step2.afterXml;
221544
+ if (step2.kind === "simple-field") {
221545
+ const instruction = readResolvedReplayAttribute(context.after, "instr");
221546
+ return instruction !== null && /^\s*REF\s+[A-Za-z_][A-Za-z0-9_.]*(?:\s|$)/i.test(instruction) && replaceReplayAttribute(step2.beforeXml, context.before, "instr", instruction) === step2.afterXml;
221547
+ }
221548
+ const beforeFragment = step2.beforeFragmentXml;
221549
+ const afterFragment = step2.afterFragmentXml;
221550
+ if (!beforeFragment || !afterFragment || replaceUniqueReplayFragment(step2.beforeXml, beforeFragment, afterFragment) !== step2.afterXml)
221551
+ return false;
221552
+ if (step2.kind === "bookmark-range") {
221553
+ return isBalancedAddedBookmarkFragment(afterFragment, beforeFragment, context.parentNamespaceBindings);
221554
+ }
221555
+ return isCompleteAddedRefFieldFragment(afterFragment, beforeFragment, context.parentNamespaceBindings);
221556
+ }
221557
+ function referenceReplayElementName(step2) {
221558
+ return step2.kind === "bookmark-start" ? "bookmarkStart" : step2.kind === "simple-field" ? "fldSimple" : "p";
221559
+ }
221560
+ function replaceUniqueReplayFragment(xml, before, after) {
221561
+ const start = xml.indexOf(before);
221562
+ if (start < 0 || xml.indexOf(before, start + before.length) >= 0)
221563
+ return null;
221564
+ return xml.slice(0, start) + after + xml.slice(start + before.length);
221565
+ }
221566
+ function isBalancedAddedBookmarkFragment(xml, expectedContent, namespaceBindings) {
221567
+ const parsed = collectXmlElementRanges(xml, { fragment: true, additionalNamespaces: { ...namespaceBindings } });
221568
+ if (parsed.errors.length > 0)
221569
+ return false;
221570
+ const starts = parsed.ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "bookmarkStart");
221571
+ const ends = parsed.ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "bookmarkEnd");
221572
+ if (starts.length !== 1 || ends.length !== 1 || !starts[0].tag.isSelfClosing || !ends[0].tag.isSelfClosing)
221573
+ return false;
221574
+ const id = readResolvedReplayAttribute(starts[0], "id");
221575
+ return id !== null && readResolvedReplayAttribute(starts[0], "name") !== null && readResolvedReplayAttribute(ends[0], "id") === id && xml.slice(starts[0].end, ends[0].start) === expectedContent;
221576
+ }
221577
+ function isCompleteAddedRefFieldFragment(xml, expectedResult, namespaceBindings) {
221578
+ const parsed = collectXmlElementRanges(xml, { fragment: true, additionalNamespaces: { ...namespaceBindings } });
221579
+ if (parsed.errors.length > 0)
221580
+ return false;
221581
+ const chars = parsed.ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "fldChar");
221582
+ const types4 = chars.map((range) => readResolvedReplayAttribute(range, "fldCharType"));
221583
+ if (types4.length !== 3 || types4.join(",") !== "begin,separate,end")
221584
+ return false;
221585
+ const instruction = parsed.ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "instrText" && range.start > chars[0].end && range.end < chars[1].start).map((range) => xml.slice(range.start, range.end).replace(/^<[^>]+>|<\/[^>]+>$/g, "")).join("");
221586
+ return /^\s*REF\s+[A-Za-z_][A-Za-z0-9_.]*(?:\s|$)/i.test(instruction) && xml.slice(parentRunEnd(parsed.ranges, chars[1]), parentRunStart(parsed.ranges, chars[2])) === expectedResult;
221587
+ }
221588
+ function parentRunStart(ranges, child) {
221589
+ return ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "r" && range.start <= child.start && range.end >= child.end)?.start ?? -1;
221590
+ }
221591
+ function parentRunEnd(ranges, child) {
221592
+ return ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "r" && range.start <= child.start && range.end >= child.end)?.end ?? -1;
221341
221593
  }
221342
221594
  function isReferenceReplayStep(step2) {
221343
221595
  return "kind" in step2;
@@ -221455,6 +221707,13 @@ function renderTrackedSectionReplay(step2, stamp, context) {
221455
221707
  return step2.afterXml.slice(0, close) + marker + step2.afterXml.slice(close);
221456
221708
  }
221457
221709
  function renderTrackedReferenceReplay(step2, stamps, context) {
221710
+ if (step2.kind === "bookmark-range" || step2.kind === "complex-field") {
221711
+ const beforeFragment = step2.beforeFragmentXml;
221712
+ const afterFragment = step2.afterFragmentXml;
221713
+ const deletionXml2 = renameWordTextElementsToDeletedText(beforeFragment, context.parentNamespaceBindings);
221714
+ const tracked = renderTrackedWrapper("del", deletionXml2, stamps.del, context) + renderTrackedWrapper("ins", afterFragment, stamps.ins, context);
221715
+ return replaceUniqueReplayFragment(step2.beforeXml, beforeFragment, tracked) ?? step2.beforeXml;
221716
+ }
221458
221717
  const deletionXml = step2.kind === "simple-field" ? renameWordTextElementsToDeletedText(step2.beforeXml, context.parentNamespaceBindings) : step2.beforeXml;
221459
221718
  return renderTrackedWrapper("del", deletionXml, stamps.del, context) + renderTrackedWrapper("ins", step2.afterXml, stamps.ins, context);
221460
221719
  }
@@ -221544,7 +221803,7 @@ function validateTableTopologySteps(state, changeMode, steps) {
221544
221803
  if (deletedRows.has(key2) || step2.rowsXml.length !== 1 || table.rows.length <= 1)
221545
221804
  return false;
221546
221805
  const rowXml = TD58.decode(partBytes.subarray(anchor.row.rowByteStart, anchor.row.rowByteEnd));
221547
- if (step2.rowsXml[0] !== rowXml || !isSafeExactTableRowPayload(rowXml, namespaceBindings))
221806
+ if (step2.rowsXml[0] !== rowXml || !isNamespaceSafeWordprocessingRow(rowXml, namespaceBindings))
221548
221807
  return false;
221549
221808
  const parsed = collectXmlElementRanges(rowXml, { fragment: true, additionalNamespaces: { ...namespaceBindings } });
221550
221809
  if (parsed.errors.length || parsed.ranges.some((range) => range.tag.namespaceUri === NS_W && [
@@ -221555,7 +221814,6 @@ function validateTableTopologySteps(state, changeMode, steps) {
221555
221814
  "commentReference",
221556
221815
  "footnoteReference",
221557
221816
  "endnoteReference",
221558
- "sdt",
221559
221817
  "fldSimple",
221560
221818
  "fldChar"
221561
221819
  ].includes(range.tag.localName)))
@@ -228918,7 +229176,7 @@ function planParagraphSplice(input) {
228918
229176
  };
228919
229177
  return input.trackedStamp ? renderTrackedPasteParagraphBlock(spec, input.trackedStamp) : renderPasteParagraphBlock(spec);
228920
229178
  };
228921
- const mergeable = (block) => block.kind === "paragraph" && !block.styleRef && !block.numbering && !block.listIntent && !block.paragraphProps && !block.pPrXml && !block.complete;
229179
+ const mergeable = (block) => block.kind === "paragraph" && !block.styleRef && !block.numbering && !block.listIntent && !block.paragraphProps && !block.pPrXml && !block.blockSdts?.length && !block.complete;
228922
229180
  const paragraphs = [];
228923
229181
  if (blocks.length === 1 && mergeable(blocks[0])) {
228924
229182
  if (anchorParaId)
@@ -228937,10 +229195,13 @@ function planParagraphSplice(input) {
228937
229195
  paragraphs.push(headOpen + headPPr + split.headXml + (mergeFirst ? runsOf(blocks[0]) : "") + envelope.close);
228938
229196
  }
228939
229197
  const middleStart = mergeFirst ? 1 : 0;
229198
+ const middleXml = [];
228940
229199
  for (let middleIndex = 0;middleIndex < middleBlocks.length; middleIndex += 1) {
228941
229200
  const block = middleBlocks[middleIndex];
228942
- paragraphs.push(standalone(block, middleStart + middleIndex, mergeable(block) ? envelope.pPr : undefined));
229201
+ middleXml.push(standalone(block, middleStart + middleIndex, mergeable(block) ? envelope.pPr : undefined));
228943
229202
  }
229203
+ if (middleXml.length)
229204
+ paragraphs.push(renderBlockSdtSequence(middleBlocks, middleXml, input.blockSdtIds ?? new Map));
228944
229205
  const tailRunsXml = (mergeLast ? runsOf(blocks[blocks.length - 1]) : "") + split.tailXml;
228945
229206
  if (mergeLast || split.tailXml.length > 0) {
228946
229207
  const tailOpen = relocateEmptyAnchor ? envelope.open : withParaId(envelope.open, mintId(), input.partBytes, input.paragraphStartByte);
@@ -231243,6 +231504,10 @@ function preflightFragmentApply(input) {
231243
231504
  const target = resolveFragmentApplyTarget(state, op);
231244
231505
  if (!target.ok)
231245
231506
  return target;
231507
+ if (isTextTarget2(op.target) && op.formattingMode === "match-destination" && op.fragment.blocks.some((block) => (block.kind === "paragraph" || block.kind === "heading") && block.blockSdts?.length))
231508
+ return { ok: false, reason: "paste-structure-unsupported" };
231509
+ if ((op.changeMode ?? "direct") === "tracked" && op.fragment.blocks.some((block) => (block.kind === "paragraph" || block.kind === "heading") && block.blockSdts?.length))
231510
+ return { ok: false, reason: "paste-tracked-structural-unsupported" };
231246
231511
  if ((op.changeMode ?? "direct") === "tracked" && target.target.kind === "block" && target.target.blockKind === "table" && !trackedWholeTableRowsAreDirectChildren(state, target.target)) {
231247
231512
  return { ok: false, reason: "paste-tracked-structural-unsupported" };
231248
231513
  }
@@ -231507,6 +231772,8 @@ function validateFragmentRangeIntegrity(input) {
231507
231772
  }
231508
231773
  function fragmentRequiresCommit5Materialization(blocks) {
231509
231774
  for (const block of blocks) {
231775
+ if ((block.kind === "paragraph" || block.kind === "heading") && block.blockSdts?.length)
231776
+ return true;
231510
231777
  if (block.kind === "heading" || block.kind === "horizontalRule")
231511
231778
  return true;
231512
231779
  if (block.kind === "table") {
@@ -231526,7 +231793,7 @@ function fragmentRequiresCommit5Materialization(blocks) {
231526
231793
  return false;
231527
231794
  }
231528
231795
  function paragraphHasFutureInline(block) {
231529
- return block.paragraphProps !== undefined || block.listIntent?.start !== undefined || (block.inlines?.some((inline) => inline.kind === "lineBreak" || inline.kind === "hyperlink" || inline.kind === "footnoteRef" || inline.kind === "endnoteRef") ?? false);
231796
+ return block.paragraphProps !== undefined || block.listIntent?.start !== undefined || (block.inlines?.some((inline) => inline.kind === "lineBreak" || inline.kind === "hyperlink" || inline.kind === "sdt" || inline.kind === "footnoteRef" || inline.kind === "endnoteRef") ?? false);
231530
231797
  }
231531
231798
  function collectFragmentSourcePaths(blocks) {
231532
231799
  const paths = [];
@@ -232037,6 +232304,7 @@ function runWholeStoryReplaceCommit(input, target, blocks, extras, inlineExtras)
232037
232304
  state,
232038
232305
  blocks,
232039
232306
  inlineSpecs: inlineExtras.inlineSpecs,
232307
+ blockSdtIds: inlineExtras.blockSdtIds,
232040
232308
  partBytes: bytes,
232041
232309
  insertionByte: target.byteStart,
232042
232310
  ordinalSeed: 0,
@@ -232275,7 +232543,7 @@ function renderCompletePasteFragment(input) {
232275
232543
  renderedBlocks.push(xml);
232276
232544
  }
232277
232545
  return {
232278
- xml: renderedBlocks.join(""),
232546
+ xml: renderBlockSdtSequence(input.blocks, renderedBlocks, input.blockSdtIds),
232279
232547
  createdParaIds,
232280
232548
  destinationParaIdsByBlock
232281
232549
  };
@@ -232307,6 +232575,7 @@ function runParagraphBlockPasteCommit(input, target, blocks, extras, inlineExtra
232307
232575
  state,
232308
232576
  blocks,
232309
232577
  inlineSpecs: inlineExtras.inlineSpecs,
232578
+ blockSdtIds: inlineExtras.blockSdtIds,
232310
232579
  partBytes: bytes,
232311
232580
  insertionByte: target.placement === "after" ? anchor.byteEnd : anchor.byteStart,
232312
232581
  ordinalSeed: anchor.ordinal,
@@ -232475,6 +232744,7 @@ function runWholeTableReplaceCommit(input, target, blocks, extras, inlineExtras)
232475
232744
  state,
232476
232745
  blocks,
232477
232746
  inlineSpecs: inlineExtras.inlineSpecs,
232747
+ blockSdtIds: inlineExtras.blockSdtIds,
232478
232748
  partBytes: bytes,
232479
232749
  insertionByte: target.byteStart,
232480
232750
  ordinalSeed: replacedParagraphs[0]?.ordinal ?? 0,
@@ -232644,6 +232914,7 @@ function runCrossParagraphTrackedPasteCommit(input, target, blocks, extras, inli
232644
232914
  paragraphStartByte: start.byteStart,
232645
232915
  mintParaId: () => mintParaId(state, state.packageRevision, start.ordinal + 1 + (mintSalt += 1)),
232646
232916
  inlineSpecs: inlineExtras.inlineSpecs,
232917
+ blockSdtIds: inlineExtras.blockSdtIds,
232647
232918
  trackedStamp: stamp,
232648
232919
  wrapAllTracked: true,
232649
232920
  ...extras.commentIdMap ? { commentIdMap: extras.commentIdMap } : {},
@@ -232772,6 +233043,7 @@ function runCrossParagraphPasteCommit(input, target, blocks, extras, inlineExtra
232772
233043
  paragraphStartByte: start.byteStart,
232773
233044
  mintParaId: () => mintParaId(state, state.packageRevision, start.ordinal + 1 + (mintSalt += 1)),
232774
233045
  inlineSpecs: inlineExtras.inlineSpecs,
233046
+ blockSdtIds: inlineExtras.blockSdtIds,
232775
233047
  ...extras.commentIdMap ? { commentIdMap: extras.commentIdMap } : {},
232776
233048
  ...extras.noteIdMap ? { noteIdMap: extras.noteIdMap } : {}
232777
233049
  });
@@ -233418,6 +233690,7 @@ function runPasteSpliceCommit(input, target, resolvedBlocks, listExtras, imageEx
233418
233690
  paragraphStartByte,
233419
233691
  mintParaId: mintParaId3,
233420
233692
  inlineSpecs: imageExtras.inlineSpecs,
233693
+ blockSdtIds: imageExtras.blockSdtIds,
233421
233694
  ...stamp ? { trackedStamp: stamp, wrapAllTracked } : {},
233422
233695
  ...listExtras.commentIdMap ? { commentIdMap: listExtras.commentIdMap } : {},
233423
233696
  ...listExtras.noteIdMap ? { noteIdMap: listExtras.noteIdMap } : {}
@@ -233654,6 +233927,8 @@ function validateImageFragment(fragment2) {
233654
233927
  continue;
233655
233928
  if (inline.kind === "footnoteRef" || inline.kind === "endnoteRef")
233656
233929
  continue;
233930
+ if (inline.kind === "sdt")
233931
+ continue;
233657
233932
  if (inline.kind === "hyperlink") {
233658
233933
  if (inline.inlines.some((child) => child.kind === "image")) {
233659
233934
  return { ok: false, reason: "paste-structure-unsupported" };
@@ -233674,7 +233949,7 @@ function validateImageFragment(fragment2) {
233674
233949
  function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMode, relationshipHyperlinks, hyperlinkCharacterStyleId, preparedPackageXml) {
233675
233950
  const inlineParagraphs = [];
233676
233951
  for (const block of blocks) {
233677
- if (block.kind === "paragraph" && block.inlines)
233952
+ if (block.kind === "paragraph" && (block.inlines || block.blockSdts?.length))
233678
233953
  inlineParagraphs.push(block);
233679
233954
  if (block.kind === "table") {
233680
233955
  for (const row of block.rows) {
@@ -233687,7 +233962,7 @@ function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMo
233687
233962
  }
233688
233963
  }
233689
233964
  if (inlineParagraphs.length === 0) {
233690
- return { ok: true, value: { inlineSpecs: new Map, partUris: [], perPart: {}, preconditions: [], patchMaps: {} } };
233965
+ return { ok: true, value: { inlineSpecs: new Map, blockSdtIds: new Map, partUris: [], perPart: {}, preconditions: [], patchMaps: {} } };
233691
233966
  }
233692
233967
  const assets = new Map((fragment2.assets ?? []).map((asset) => [asset.id, asset]));
233693
233968
  const hasImages = inlineParagraphs.some((paragraph) => paragraph.inlines?.some((inline) => inline.kind === "image"));
@@ -233782,6 +234057,43 @@ function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMo
233782
234057
  }
233783
234058
  let docPrId = allocateDocPrId(state, storyId);
233784
234059
  const inlineSpecs = new Map;
234060
+ const hasControls = inlineParagraphs.some((paragraph) => paragraph.blockSdts?.length || paragraph.inlines?.some((inline) => inline.kind === "sdt"));
234061
+ const usedControlIds = new Set;
234062
+ if (hasControls) {
234063
+ for (const [uri, bytes] of state.packageBytes) {
234064
+ if (!uri.endsWith(".xml"))
234065
+ continue;
234066
+ const xml = TD64.decode(bytes);
234067
+ for (const match of xml.matchAll(/<w:sdtPr\b[^>]*>[\s\S]*?<w:id\b[^>]*\bw:val=(['"])(\d+)\1/g)) {
234068
+ const id = Number(match[2]);
234069
+ if (Number.isSafeInteger(id) && id > 0)
234070
+ usedControlIds.add(id);
234071
+ }
234072
+ }
234073
+ }
234074
+ let nextControlId = 1002;
234075
+ for (const id of usedControlIds)
234076
+ if (id >= nextControlId)
234077
+ nextControlId = id + 1;
234078
+ const mintControlId = () => {
234079
+ if (nextControlId > 2147483647)
234080
+ nextControlId = 1;
234081
+ while (usedControlIds.has(nextControlId)) {
234082
+ nextControlId += 1;
234083
+ if (nextControlId > 2147483647)
234084
+ nextControlId = 1;
234085
+ }
234086
+ const id = nextControlId++;
234087
+ usedControlIds.add(id);
234088
+ return String(id);
234089
+ };
234090
+ const blockSdtIds = new Map;
234091
+ for (const paragraph of inlineParagraphs) {
234092
+ for (const wrapper of paragraph.blockSdts ?? []) {
234093
+ if (!blockSdtIds.has(wrapper.sourceId))
234094
+ blockSdtIds.set(wrapper.sourceId, mintControlId());
234095
+ }
234096
+ }
233785
234097
  const leafSpec = (inline) => {
233786
234098
  if (inline.kind === "text")
233787
234099
  return toPasteTextRunSpec(inline);
@@ -233802,37 +234114,68 @@ function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMo
233802
234114
  })
233803
234115
  };
233804
234116
  };
234117
+ const sdtSpec = (inline) => ({
234118
+ kind: "sdt",
234119
+ id: mintControlId(),
234120
+ controlType: inline.controlType,
234121
+ ...inline.tag !== undefined ? { tag: inline.tag } : {},
234122
+ ...inline.alias !== undefined ? { alias: inline.alias } : {},
234123
+ ...inline.lockMode !== undefined ? { lockMode: inline.lockMode } : {},
234124
+ ...inline.multiline !== undefined ? { multiline: inline.multiline } : {},
234125
+ runs: inline.inlines.map((child) => child.kind === "sdt" ? sdtSpec(child) : leafSpec(child))
234126
+ });
234127
+ const inlineSpec = (inline) => {
234128
+ if (inline.kind === "sdt")
234129
+ return sdtSpec(inline);
234130
+ if (inline.kind === "footnoteRef" || inline.kind === "endnoteRef") {
234131
+ return { kind: inline.kind, noteId: inline.noteId };
234132
+ }
234133
+ if (inline.kind !== "hyperlink")
234134
+ return leafSpec(inline);
234135
+ const target = inline.target.kind === "external" ? {
234136
+ kind: "external",
234137
+ url: inline.target.url,
234138
+ ...relationshipHyperlinks ? { relationshipId: hyperlinkRelationshipIds.get(inline) } : {}
234139
+ } : { kind: "anchor", anchor: inline.target.anchor };
234140
+ return {
234141
+ kind: "hyperlink",
234142
+ target,
234143
+ ...inline.tooltip ? { tooltip: inline.tooltip } : {},
234144
+ ...hyperlinkCharacterStyleId ? { characterStyleId: hyperlinkCharacterStyleId } : {},
234145
+ runs: inline.inlines.map(leafSpec)
234146
+ };
234147
+ };
233805
234148
  for (const paragraph of inlineParagraphs) {
233806
- inlineSpecs.set(paragraph, (paragraph.inlines ?? []).map((inline) => {
233807
- if (inline.kind === "footnoteRef" || inline.kind === "endnoteRef") {
233808
- return { kind: inline.kind, noteId: inline.noteId };
233809
- }
233810
- if (inline.kind !== "hyperlink")
233811
- return leafSpec(inline);
233812
- const target = inline.target.kind === "external" ? {
233813
- kind: "external",
233814
- url: inline.target.url,
233815
- ...relationshipHyperlinks ? { relationshipId: hyperlinkRelationshipIds.get(inline) } : {}
233816
- } : { kind: "anchor", anchor: inline.target.anchor };
233817
- return {
233818
- kind: "hyperlink",
233819
- target,
233820
- ...inline.tooltip ? { tooltip: inline.tooltip } : {},
233821
- ...hyperlinkCharacterStyleId ? { characterStyleId: hyperlinkCharacterStyleId } : {},
233822
- runs: inline.inlines.map(leafSpec)
233823
- };
233824
- }));
234149
+ if (paragraph.inlines)
234150
+ inlineSpecs.set(paragraph, paragraph.inlines.map(inlineSpec));
233825
234151
  }
233826
234152
  const preconditions = [];
233827
234153
  if (relsUri && relsBytes && (hasImages || hasExternalLinks))
233828
234154
  preconditions.push(byteWindowPrecondition7(relsUri, relsBytes));
233829
234155
  if (contentTypesChanged && contentTypesBytes)
233830
234156
  preconditions.push(byteWindowPrecondition7(CONTENT_TYPES_PART_URI11, contentTypesBytes));
233831
- return { ok: true, value: { inlineSpecs, partUris: packagePartUris, perPart, preconditions, patchMaps: {} } };
234157
+ return { ok: true, value: { inlineSpecs, blockSdtIds, partUris: packagePartUris, perPart, preconditions, patchMaps: {} } };
233832
234158
  }
233833
234159
  function validateBlocks(blocks) {
233834
234160
  const budget = { totalChars: 0, hyperlinkRefs: 0, sourcePaths: new Set };
234161
+ const closedControls = new Set;
234162
+ let activeControls = [];
233835
234163
  for (const block of blocks) {
234164
+ const controls = block.kind === "paragraph" || block.kind === "heading" ? block.blockSdts ?? [] : [];
234165
+ if (controls.length && (block.kind !== "paragraph" && block.kind !== "heading" || !block.complete || controls.length > 16)) {
234166
+ return { ok: false, reason: "paste-structure-unsupported" };
234167
+ }
234168
+ let common = 0;
234169
+ while (common < controls.length && common < activeControls.length && controls[common].sourceId === activeControls[common].sourceId && controls[common].controlType === activeControls[common].controlType && controls[common].tag === activeControls[common].tag && controls[common].alias === activeControls[common].alias)
234170
+ common += 1;
234171
+ for (const wrapper of activeControls.slice(common))
234172
+ closedControls.add(wrapper.sourceId);
234173
+ for (const wrapper of controls.slice(common)) {
234174
+ if (!/^[0-9]{1,10}$/.test(wrapper.sourceId) || closedControls.has(wrapper.sourceId) || controls.filter((candidate2) => candidate2.sourceId === wrapper.sourceId).length !== 1 || wrapper.controlType !== "text" && wrapper.controlType !== "richText" || !isValidOptionalXmlAttributeText(wrapper.tag, 255) || !isValidOptionalXmlAttributeText(wrapper.alias, 255)) {
234175
+ return { ok: false, reason: "paste-structure-unsupported" };
234176
+ }
234177
+ }
234178
+ activeControls = controls;
233836
234179
  if (!recordSourcePath(block.sourcePath, budget.sourcePaths)) {
233837
234180
  return { ok: false, reason: "paste-structure-unsupported" };
233838
234181
  }
@@ -233896,6 +234239,9 @@ function validateBlocks(blocks) {
233896
234239
  if (cellBlock.kind !== "paragraph") {
233897
234240
  return { ok: false, reason: "paste-structure-unsupported" };
233898
234241
  }
234242
+ if (cellBlock.blockSdts?.length) {
234243
+ return { ok: false, reason: "paste-structure-unsupported" };
234244
+ }
233899
234245
  if (!recordSourcePath(cellBlock.sourcePath, budget.sourcePaths)) {
233900
234246
  return { ok: false, reason: "paste-structure-unsupported" };
233901
234247
  }
@@ -234029,9 +234375,14 @@ function validateParagraphBlock(block, budget) {
234029
234375
  }
234030
234376
  return { ok: true };
234031
234377
  }
234032
- function validatePasteInline(inline, budget) {
234378
+ function validatePasteInline(inline, budget, depth = 0) {
234379
+ if (depth > 32)
234380
+ return false;
234033
234381
  if (!recordSourcePath(inline.sourcePath, budget.sourcePaths))
234034
234382
  return false;
234383
+ if (inline.kind === "sdt") {
234384
+ return (inline.controlType === "text" || inline.controlType === "richText") && isValidOptionalXmlAttributeText(inline.tag, 255) && isValidOptionalXmlAttributeText(inline.alias, 255) && Array.isArray(inline.inlines) && inline.inlines.length > 0 && inline.inlines.every((child) => child.kind === "text" || child.kind === "lineBreak" || child.kind === "sdt") && inline.inlines.every((child) => validatePasteInline(child, budget, depth + 1));
234385
+ }
234035
234386
  if (inline.kind === "text") {
234036
234387
  return typeof inline.text === "string" && !hasUnsupportedControlChar(inline.text) && isValidPasteRunMarks(inline.marks);
234037
234388
  }
@@ -234057,7 +234408,7 @@ function validatePasteInline(inline, budget) {
234057
234408
  } else if (inline.target.kind !== "anchor" || typeof inline.target.anchor !== "string" || inline.target.anchor.length === 0 || inline.target.anchor.length > MAX_HYPERLINK_ANCHOR_CHARS || hasUnsupportedXmlAttributeChar(inline.target.anchor)) {
234058
234409
  return false;
234059
234410
  }
234060
- return inline.inlines.every((child) => validatePasteInline(child, budget));
234411
+ return inline.inlines.every((child) => validatePasteInline(child, budget, depth + 1));
234061
234412
  }
234062
234413
  function rewritePastePPrStyleRef(pPrXml, styleId) {
234063
234414
  const escaped = styleId.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -234069,7 +234420,13 @@ function rewritePastePPrStyleRef(pPrXml, styleId) {
234069
234420
  function hasMatchingInlineTextProjection(block) {
234070
234421
  if (!Array.isArray(block.inlines))
234071
234422
  return false;
234072
- const textInlines = block.inlines.flatMap((inline) => inline.kind === "text" ? [inline] : inline.kind === "hyperlink" ? inline.inlines.flatMap((child) => child.kind === "text" ? [child] : []) : []);
234423
+ const textInlines = block.inlines.flatMap(function project(inline) {
234424
+ if (inline.kind === "text")
234425
+ return [inline];
234426
+ if (inline.kind === "hyperlink" || inline.kind === "sdt")
234427
+ return inline.inlines.flatMap(project);
234428
+ return [];
234429
+ });
234073
234430
  return textInlines.length === block.runs.length && textInlines.every((inline, index) => {
234074
234431
  const run = block.runs[index];
234075
234432
  return inline.text === run.text && haveEqualRunMarks(inline.marks, run.marks);
@@ -247145,12 +247502,13 @@ function prepareHistoryReplayMutation(state, entry, direction, patchGroup) {
247145
247502
  const controlBytes = controlOwner && state.packageBytes.get(controlOwner.partUri);
247146
247503
  const controlPatch = controlOwner && patchGroup.perPart[controlOwner.partUri];
247147
247504
  const contentControlRewrite = patchGroup.source.kind === "storySurfaceRewrite" && patchGroup.source.contentControlRewrite && controlBytes && controlPatch ? prepareContentControlRewrite(controlBytes, controlPatch) : null;
247505
+ const declaredStorySurfaceTargetBlockIds = patchGroup.source.kind === "storySurfaceRewrite" ? [...new Set(patchGroup.source.targetBlockIds)] : [];
247148
247506
  const targetBlockIds2 = contentControlRewrite ? [...new Set([
247149
247507
  ...contentControlRewrite.removedBlockIds,
247150
247508
  ...contentControlRewrite.addedBlockIds,
247151
247509
  ...contentControlRewrite.contentBlockIds,
247152
247510
  ...contentControlRewrite.leadingBlockIds
247153
- ])] : patchGroup.source.kind === "compareApply" ? [...new Set(patchGroup.source.bodySteps.map((step2) => step2.blockId))] : collectPatchGroupTargetBlockIds(patchGroup);
247511
+ ])] : patchGroup.source.kind === "compareApply" ? [...new Set(patchGroup.source.bodySteps.map((step2) => step2.blockId))] : declaredStorySurfaceTargetBlockIds.length > 0 ? declaredStorySurfaceTargetBlockIds : collectPatchGroupTargetBlockIds(patchGroup);
247154
247512
  const currentBaseRevisions = {
247155
247513
  package: state.packageRevision,
247156
247514
  perPart: {}
@@ -291329,7 +291687,7 @@ class V2SelectionMutationAdapter {
291329
291687
  const buildResolutionMs = nowMs6() - buildResolutionStartedAt;
291330
291688
  let replacements = resolved.coordinateSpace !== "tracked" && resolved.startOffset < resolved.endOffset && selectedText.length === resolved.endOffset - resolved.startOffset && isPlannableTextPayload(request.text) ? planTrackedRewriteReplacement(selectedText, request.text) : [{ start: 0, end: resolved.endOffset - resolved.startOffset, text: request.text }];
291331
291689
  let structuralRewrite = false;
291332
- if (options?.changeMode === "tracked" && isRewrite && replacements.length > 1) {
291690
+ if (options?.changeMode === "tracked" && replacements.length > 1) {
291333
291691
  const paragraphBytes = getSDDocumentSessionInternalPartRange(this.context.session, resolved.partUri, resolved.paragraphByteStart ?? resolved.startByte, resolved.paragraphByteEnd ?? resolved.endByte);
291334
291692
  if (paragraphBytes) {
291335
291693
  const scopedReplacements = planTrackedRewriteReplacement(selectedText, request.text, {
@@ -326296,8 +326654,8 @@ function lowerInline(inline, path7, images) {
326296
326654
  }
326297
326655
  function lowerHyperlinkChild(inline, path7, images) {
326298
326656
  const lowered = lowerInline(inline, path7, images);
326299
- if (lowered.kind === "hyperlink")
326300
- unsupported4(path7, "Nested hyperlinks are not supported.");
326657
+ if (lowered.kind === "hyperlink" || lowered.kind === "sdt")
326658
+ unsupported4(path7, "Nested hyperlinks and content controls are not supported.");
326301
326659
  if (lowered.kind === "footnoteRef" || lowered.kind === "endnoteRef") {
326302
326660
  unsupported4(path7, "Note references cannot be nested inside hyperlinks.");
326303
326661
  }
@@ -345491,40 +345849,63 @@ class V2ImagesAdapter {
345491
345849
  return trackedTarget;
345492
345850
  return this.createTrackedBodyImage(input, options, trackedTarget, decoded, requestedSize);
345493
345851
  }
345494
- const target = this.resolveCreateImageTarget(input, options);
345495
- if (!target.success)
345496
- return target;
345497
- const inserted = this.insertInline({
345498
- storyId: target.storyId,
345499
- paraId: target.paraId,
345500
- offset: target.offset,
345501
- binary: decoded.binary,
345502
- format: decoded.format,
345503
- widthPx: requestedSize.widthPx,
345504
- heightPx: requestedSize.heightPx,
345505
- name: input.alt ?? input.title
345506
- });
345507
- if (!inserted.success) {
345508
- return createImageFailure(inserted.failure.code, inserted.failure.message);
345509
- }
345510
- let entry = this.findInsertedImageEntry(target.storyId, target.paraId, inserted.imageNodeId);
345511
- if (entry && (input.title || input.alt)) {
345512
- const metadata2 = this.setAltText({
345513
- imageId: entry.publicId,
345514
- description: input.title ?? input.alt ?? ""
345852
+ const abortCapture = Symbol("create.image.abort");
345853
+ const imageFormat = decoded.format;
345854
+ let capturedTxIds = [];
345855
+ let failure8 = null;
345856
+ let published = null;
345857
+ try {
345858
+ published = this.context.captureCommittedHistory((txIds) => {
345859
+ capturedTxIds = txIds;
345860
+ const resolved = this.resolveCreateImageTarget(input, options);
345861
+ if (!resolved.success) {
345862
+ failure8 = resolved;
345863
+ throw abortCapture;
345864
+ }
345865
+ const inserted = this.insertInline({
345866
+ storyId: resolved.storyId,
345867
+ paraId: resolved.paraId,
345868
+ offset: resolved.offset,
345869
+ binary: decoded.binary,
345870
+ format: imageFormat,
345871
+ widthPx: requestedSize.widthPx,
345872
+ heightPx: requestedSize.heightPx,
345873
+ name: input.alt ?? input.title
345874
+ });
345875
+ if (!inserted.success) {
345876
+ failure8 = createImageFailure(inserted.failure.code, inserted.failure.message);
345877
+ throw abortCapture;
345878
+ }
345879
+ let entry = this.findInsertedImageEntry(resolved.storyId, resolved.paraId, inserted.imageNodeId);
345880
+ if (entry && (input.title || input.alt)) {
345881
+ const metadata2 = this.setAltText({
345882
+ imageId: entry.publicId,
345883
+ description: input.title ?? input.alt ?? ""
345884
+ });
345885
+ if (!metadata2.success) {
345886
+ failure8 = createImageFailure(metadata2.failure.code, metadata2.failure.message);
345887
+ throw abortCapture;
345888
+ }
345889
+ entry = readBodyImageEntryById(this.context, entry.publicId) ?? entry;
345890
+ }
345891
+ return { imageNodeId: inserted.imageNodeId, entry };
345515
345892
  });
345516
- if (!metadata2.success)
345517
- return createImageFailure(metadata2.failure.code, metadata2.failure.message);
345518
- entry = readBodyImageEntryById(this.context, entry.publicId) ?? entry;
345893
+ } catch (error48) {
345894
+ rollbackCapturedHistory(this.context, capturedTxIds);
345895
+ if (error48 === abortCapture && failure8)
345896
+ return failure8;
345897
+ throw error48;
345519
345898
  }
345899
+ const txId = publishImageCreateHistory(this.context, capturedTxIds);
345520
345900
  return {
345521
345901
  success: true,
345522
- image: entry?.summary.address ?? {
345902
+ image: published?.entry?.summary.address ?? {
345523
345903
  kind: "inline",
345524
345904
  nodeType: "image",
345525
- nodeId: inserted.imageNodeId ?? "(unknown)",
345905
+ nodeId: published?.imageNodeId ?? "(unknown)",
345526
345906
  placement: "inline"
345527
- }
345907
+ },
345908
+ ...txId ? { txId } : {}
345528
345909
  };
345529
345910
  }
345530
345911
  delete(input, options = {}) {
@@ -347542,6 +347923,16 @@ function validateImageDeleteTarget(context, entry) {
347542
347923
  }
347543
347924
  return null;
347544
347925
  }
347926
+ function publishImageCreateHistory(context, txIds) {
347927
+ const uniqueTxIds = [...new Set(txIds)];
347928
+ if (uniqueTxIds.length === 0)
347929
+ return;
347930
+ const logicalGroup = { kind: "image-create", id: uniqueTxIds[0] };
347931
+ for (const txId of uniqueTxIds) {
347932
+ recordCommittedTxIdForHistory(context, txId, { logicalGroup });
347933
+ }
347934
+ return uniqueTxIds[uniqueTxIds.length - 1];
347935
+ }
347545
347936
  function createImageFailure(code2, message) {
347546
347937
  return { success: false, failure: { code: code2, message } };
347547
347938
  }
@@ -349286,6 +349677,7 @@ var init_adapter17 = __esm(() => {
349286
349677
  init_document_reader();
349287
349678
  init_section_xml();
349288
349679
  init_adapter();
349680
+ init_composite();
349289
349681
  init_stories();
349290
349682
  init_common();
349291
349683
  init_tracked_pr_change();
@@ -353085,13 +353477,14 @@ function buildCompareSourceObservation(context) {
353085
353477
  if (!bytes)
353086
353478
  continue;
353087
353479
  const xml = TD98.decode(bytes);
353480
+ const canonicalizeBookmarkIds = bookmarkIdsAreBalanced(xml);
353088
353481
  for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
353089
353482
  const name = tagAttr(match[1] ?? "", "name");
353090
353483
  if (!name || name === "_GoBack")
353091
353484
  continue;
353092
353485
  acc["fields-references"].present = true;
353093
353486
  acc["fields-references"].entityCount += 1;
353094
- acc["fields-references"].fingerprintParts.push(`bookmark:${source.partUri}:${name}:${tagAttr(match[1] ?? "", "id") ?? ""}`);
353487
+ acc["fields-references"].fingerprintParts.push(`bookmark:${source.partUri}:${name}` + (canonicalizeBookmarkIds ? "" : `:unbalanced-id:${tagAttr(match[1] ?? "", "id") ?? ""}`));
353095
353488
  }
353096
353489
  for (const match of xml.matchAll(/<[\w.-]+:permStart\b([^>]*)>/g)) {
353097
353490
  const id = tagAttr(match[1] ?? "", "id");
@@ -353133,6 +353526,24 @@ function buildCompareSourceObservation(context) {
353133
353526
  }));
353134
353527
  return { parts, familySignals };
353135
353528
  }
353529
+ function bookmarkIdsAreBalanced(xml) {
353530
+ const starts = new Map;
353531
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
353532
+ const id = tagAttr(match[1] ?? "", "id");
353533
+ if (id === null || starts.has(id))
353534
+ return false;
353535
+ starts.set(id, match.index);
353536
+ }
353537
+ const ends = new Set;
353538
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkEnd\b([^>]*)>/g)) {
353539
+ const id = tagAttr(match[1] ?? "", "id");
353540
+ const start = id === null ? undefined : starts.get(id);
353541
+ if (id === null || start === undefined || ends.has(id) || match.index < start)
353542
+ return false;
353543
+ ends.add(id);
353544
+ }
353545
+ return starts.size === ends.size;
353546
+ }
353136
353547
  function captureCompareSourceInventory(context) {
353137
353548
  return buildCompareSourceInventory(buildCompareSourceObservation(context));
353138
353549
  }
@@ -353741,6 +354152,7 @@ function projectFieldsReferences(context) {
353741
354152
  if (!bytes)
353742
354153
  continue;
353743
354154
  const xml = TD100.decode(bytes);
354155
+ const canonicalizeBookmarkIds = bookmarkIdsAreBalanced2(xml);
353744
354156
  for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
353745
354157
  const name = tagAttr3(match[1] ?? "", "name");
353746
354158
  if (!name || name === "_GoBack")
@@ -353751,7 +354163,7 @@ function projectFieldsReferences(context) {
353751
354163
  key: uniqueEntityKey(baseKey, keySeen),
353752
354164
  stableKey: `bookmark:${name}`,
353753
354165
  semanticKey: `bookmark:${name}`,
353754
- contentSignature: digest3(`bookmark:${name}#id:${id ?? ""}#part:${source.partUri}`),
354166
+ contentSignature: digest3(`bookmark:${name}#part:${source.partUri}` + (canonicalizeBookmarkIds ? "" : `#unbalanced-id:${id ?? ""}`)),
353755
354167
  ordinal: ordinal++,
353756
354168
  label: source.scopeKey === "body" ? `bookmark ${name}` : `bookmark ${name} (${source.scopeKey})`
353757
354169
  });
@@ -353759,6 +354171,24 @@ function projectFieldsReferences(context) {
353759
354171
  }
353760
354172
  return entries;
353761
354173
  }
354174
+ function bookmarkIdsAreBalanced2(xml) {
354175
+ const starts = new Map;
354176
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
354177
+ const id = tagAttr3(match[1] ?? "", "id");
354178
+ if (id === null || starts.has(id))
354179
+ return false;
354180
+ starts.set(id, match.index);
354181
+ }
354182
+ const ends = new Set;
354183
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkEnd\b([^>]*)>/g)) {
354184
+ const id = tagAttr3(match[1] ?? "", "id");
354185
+ const start = id === null ? undefined : starts.get(id);
354186
+ if (id === null || start === undefined || ends.has(id) || match.index < start)
354187
+ return false;
354188
+ ends.add(id);
354189
+ }
354190
+ return starts.size === ends.size;
354191
+ }
353762
354192
  function projectContentControls(context) {
353763
354193
  const entries = [];
353764
354194
  let ordinal = 0;
@@ -354619,8 +355049,12 @@ function projectStories(context, slots) {
354619
355049
  if (family === "footnotes" || family === "endnotes")
354620
355050
  requiredPartBytes(context, story.partUri);
354621
355051
  const paragraphs = readStoryParagraphs(context, story.storyId).filter(isWordprocessingParagraph);
355052
+ const runXmls = paragraphs.map((p) => extractParagraphRunXml(p.rawXml, p.namespaceBindings ?? {}));
355053
+ const runSignatures = computeRunSignatures(runXmls);
355054
+ const runXmlByOrdinal = new Map(paragraphs.map((p, i2) => [p.ordinal, runXmls[i2]]));
355055
+ const runSignatureByOrdinal = new Map(paragraphs.map((p, i2) => [p.ordinal, runSignatures[i2]]));
354622
355056
  const projectEntry2 = (p) => {
354623
- const runXml = extractParagraphRunXml(p.rawXml, p.namespaceBindings ?? {});
355057
+ const runXml = runXmlByOrdinal.get(p.ordinal);
354624
355058
  const paragraphPropertiesXml = extractParagraphPropertiesXml(p.rawXml, p.namespaceBindings ?? {});
354625
355059
  const blockId = family === "header-footer-parts" && isStructurallyBlankStoryParagraph({ text: p.text, runXml }) ? `${slotKey}::blank:${p.ordinal}` : p.blockId;
354626
355060
  return {
@@ -354629,7 +355063,7 @@ function projectStories(context, slots) {
354629
355063
  text: p.text,
354630
355064
  headingLevel: p.headingLevel,
354631
355065
  styleId: p.styleId,
354632
- runSignature: computeRunSignature(runXml),
355066
+ runSignature: runSignatureByOrdinal.get(p.ordinal),
354633
355067
  runXml,
354634
355068
  trackedChangeAnnotationKeys: trackedChangeAnnotationKeysForParagraph(context, p),
354635
355069
  paragraphPropertiesXml,
@@ -354702,8 +355136,10 @@ function bodyParagraphStructuralScopes(context, paragraphs) {
354702
355136
  function projectBody(context) {
354703
355137
  const paragraphs = readBodyParagraphs(context).filter(isWordprocessingParagraph);
354704
355138
  const scopes = bodyParagraphStructuralScopes(context, paragraphs);
355139
+ const runXmls = paragraphs.map((p) => extractParagraphRunXml(p.rawXml, p.namespaceBindings ?? {}));
355140
+ const runSignatures = computeRunSignatures(runXmls);
354705
355141
  return paragraphs.map((p, ordinal) => {
354706
- const runXml = extractParagraphRunXml(p.rawXml, p.namespaceBindings ?? {});
355142
+ const runXml = runXmls[ordinal];
354707
355143
  const paragraphPropertiesXml = extractParagraphPropertiesXml(p.rawXml, p.namespaceBindings ?? {});
354708
355144
  return {
354709
355145
  blockId: p.blockId,
@@ -354712,7 +355148,7 @@ function projectBody(context) {
354712
355148
  text: p.text,
354713
355149
  headingLevel: p.headingLevel,
354714
355150
  styleId: p.styleId,
354715
- runSignature: computeRunSignature(runXml),
355151
+ runSignature: runSignatures[ordinal],
354716
355152
  runXml,
354717
355153
  trackedChangeAnnotationKeys: trackedChangeAnnotationKeysForParagraph(context, p),
354718
355154
  paragraphPropertiesXml,
@@ -358567,6 +359003,119 @@ var init_main_document_revision_closure = __esm(() => {
358567
359003
  });
358568
359004
 
358569
359005
  // ../../../v2/document-api-v2-adapter/dist/diff/tracked-table-row-replay.js
359006
+ function rowReplayCoversContentControls(baseAnnotations, targetAnnotations, steps, baseXml, targetXml) {
359007
+ if (steps.length === 0)
359008
+ return false;
359009
+ const base3 = baseAnnotations?.find((group) => group.family === "content-controls")?.entries;
359010
+ const target = targetAnnotations?.find((group) => group.family === "content-controls")?.entries;
359011
+ if (!base3 || !target)
359012
+ return false;
359013
+ const inserted = new Set;
359014
+ const deleted = new Set;
359015
+ for (const step3 of steps) {
359016
+ const identities = step3.kind === "insertRows" ? inserted : deleted;
359017
+ for (const rowXml of step3.rowsXml) {
359018
+ for (const match of rowXml.matchAll(/<[\w.-]+:sdtPr\b[^>]*>([\s\S]*?)<\/[\w.-]+:sdtPr>/g)) {
359019
+ const properties = match[1];
359020
+ if (/<[\w.-]+:dataBinding\b/.test(properties))
359021
+ return false;
359022
+ const tag = /<[\w.-]+:tag\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(properties)?.[1];
359023
+ const alias = /<[\w.-]+:alias\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(properties)?.[1];
359024
+ const id = /<[\w.-]+:id\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(properties)?.[1];
359025
+ const identity2 = tag ?? alias ?? id;
359026
+ const key2 = identity2 ? `sdt:${identity2}` : null;
359027
+ if (!key2 || identities.has(key2))
359028
+ return false;
359029
+ identities.add(key2);
359030
+ }
359031
+ }
359032
+ }
359033
+ const baseByKey = new Map(base3.map((entry) => [entry.key, entry]));
359034
+ const targetByKey = new Map(target.map((entry) => [entry.key, entry]));
359035
+ if (baseByKey.size !== base3.length || targetByKey.size !== target.length)
359036
+ return false;
359037
+ const survivingBaseKeys = base3.filter((entry) => targetByKey.has(entry.key)).map((entry) => entry.key);
359038
+ const survivingTargetKeys = target.filter((entry) => baseByKey.has(entry.key)).map((entry) => entry.key);
359039
+ if (survivingBaseKeys.some((key2, index2) => key2 !== survivingTargetKeys[index2]))
359040
+ return false;
359041
+ if ([...inserted, ...deleted].some((key2) => baseByKey.has(key2) && targetByKey.has(key2)))
359042
+ return false;
359043
+ const baseShapes = contentControlShapes(baseXml);
359044
+ const targetShapes = contentControlShapes(targetXml);
359045
+ if (!baseShapes || !targetShapes)
359046
+ return false;
359047
+ if (survivingBaseKeys.some((key2) => key2.startsWith("sdt:") && (!baseShapes.has(key2) || baseShapes.get(key2) !== targetShapes.get(key2))))
359048
+ return false;
359049
+ for (const entry of base3) {
359050
+ const other = targetByKey.get(entry.key);
359051
+ if (other) {
359052
+ if (entry.contentSignature !== other.contentSignature || entry.semanticKey !== other.semanticKey)
359053
+ return false;
359054
+ } else if (!deleted.has(entry.key))
359055
+ return false;
359056
+ }
359057
+ for (const entry of target) {
359058
+ if (!baseByKey.has(entry.key) && !inserted.has(entry.key))
359059
+ return false;
359060
+ }
359061
+ return [...inserted].every((key2) => targetByKey.has(key2) && !baseByKey.has(key2)) && [...deleted].every((key2) => baseByKey.has(key2) && !targetByKey.has(key2));
359062
+ }
359063
+ function contentControlShapes(xml) {
359064
+ const parsed = collectXmlElementRanges(xml);
359065
+ if (parsed.errors.length > 0)
359066
+ return null;
359067
+ const ranges = [...parsed.ranges].sort((first, second) => first.start - second.start || second.end - first.end);
359068
+ const parents = new Map;
359069
+ const paths = new Map;
359070
+ const siblingCounts = new Map;
359071
+ const stack = [];
359072
+ for (const range of ranges) {
359073
+ while (stack.length && stack[stack.length - 1].end <= range.start)
359074
+ stack.pop();
359075
+ const parent = stack[stack.length - 1] ?? null;
359076
+ if (parent && range.end > parent.end)
359077
+ return null;
359078
+ const siblingIndex = siblingCounts.get(parent) ?? 0;
359079
+ siblingCounts.set(parent, siblingIndex + 1);
359080
+ parents.set(range, parent);
359081
+ paths.set(range, `${parent ? paths.get(parent) : ""}/${range.tag.namespaceUri}:${range.tag.localName}[${siblingIndex}]`);
359082
+ stack.push(range);
359083
+ }
359084
+ const shapes = new Map;
359085
+ const identityCounts = new Map;
359086
+ for (const properties of ranges.filter((range) => range.tag.namespaceUri === WORDPROCESSINGML_NS4 && range.tag.localName === "sdtPr")) {
359087
+ const propertyXml = xml.slice(properties.start, properties.end);
359088
+ const tag = /<[^>]*:tag\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(propertyXml)?.[1];
359089
+ const alias = /<[^>]*:alias\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(propertyXml)?.[1];
359090
+ const id = /<[^>]*:id\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(propertyXml)?.[1];
359091
+ const key2 = tag ?? alias ?? id;
359092
+ if (!key2)
359093
+ return null;
359094
+ const control = parents.get(properties);
359095
+ if (!control || control.tag.namespaceUri !== WORDPROCESSINGML_NS4 || control.tag.localName !== "sdt")
359096
+ return null;
359097
+ const parent = parents.get(control);
359098
+ const content3 = ranges.find((range) => parents.get(range) === control && range.tag.namespaceUri === WORDPROCESSINGML_NS4 && range.tag.localName === "sdtContent");
359099
+ const firstChild = content3 && ranges.find((range) => parents.get(range) === content3);
359100
+ if (!parent || !content3 || !firstChild)
359101
+ return null;
359102
+ const identity2 = `sdt:${key2}`;
359103
+ const count = (identityCounts.get(identity2) ?? 0) + 1;
359104
+ identityCounts.set(identity2, count);
359105
+ const uniqueIdentity = count === 1 ? identity2 : `${identity2}#${count}`;
359106
+ let ancestor = parent;
359107
+ let insideRow = false;
359108
+ while (ancestor) {
359109
+ if (ancestor.tag.namespaceUri === WORDPROCESSINGML_NS4 && ancestor.tag.localName === "tr") {
359110
+ insideRow = true;
359111
+ break;
359112
+ }
359113
+ ancestor = parents.get(ancestor) ?? null;
359114
+ }
359115
+ shapes.set(uniqueIdentity, `${parent.tag.namespaceUri}:${parent.tag.localName}|${firstChild.tag.namespaceUri}:${firstChild.tag.localName}|${insideRow ? "row" : paths.get(control)}`);
359116
+ }
359117
+ return shapes;
359118
+ }
358570
359119
  function buildTrackedTableRowReplayPlan(input) {
358571
359120
  if (input.base.xml === input.target.xml) {
358572
359121
  return { steps: [], bodyOperations: input.bodyOperations, unsafeReasons: [] };
@@ -358584,30 +359133,36 @@ function buildTrackedTableRowReplayPlan(input) {
358584
359133
  }
358585
359134
  const ownedBlockIds = new Set;
358586
359135
  const steps = [];
359136
+ const usedParagraphIds = new Set([
359137
+ ...input.base.xml.matchAll(/\bparaId="([0-9A-Fa-f]{8})"/g),
359138
+ ...input.target.xml.matchAll(/\bparaId="([0-9A-Fa-f]{8})"/g)
359139
+ ].map((match) => match[1].toUpperCase()));
359140
+ const baseParagraphIds = new Set([...input.base.xml.matchAll(/\bparaId="([0-9A-Fa-f]{8})"/g)].map((match) => match[1].toUpperCase()));
358587
359141
  for (let tableIndex = 0;tableIndex < baseCatalog.tables.length; tableIndex += 1) {
358588
359142
  const baseTable = baseCatalog.tables[tableIndex];
358589
359143
  const targetTable = targetCatalog.tables[tableIndex];
358590
- if (!isSimpleTable(baseTable) || !isSimpleTable(targetTable)) {
359144
+ if (!isSimpleTable(baseTable, baseBytes, baseRoot) || !isSimpleTable(targetTable, targetBytes, targetRoot)) {
358591
359145
  return unsafe(`table ${baseTable.tableNodeId} has columns, merges, nesting, or unsupported cell content`);
358592
359146
  }
358593
359147
  if (baseTable.gridColumnCount !== targetTable.gridColumnCount) {
358594
359148
  return unsafe(`table ${baseTable.tableNodeId} changes its column grid`);
358595
359149
  }
358596
- const baseRowsById = uniqueRows(baseTable.rows.map((row) => row.rowNodeId));
358597
- const targetRowsById = uniqueRows(targetTable.rows.map((row) => row.rowNodeId));
358598
- if (!baseRowsById || !targetRowsById)
358599
- return unsafe(`table ${baseTable.tableNodeId} has ambiguous row identities`);
358600
- const targetPositions = baseTable.rows.flatMap((row) => {
358601
- const position2 = targetRowsById.get(row.rowNodeId);
358602
- return position2 === undefined ? [] : [position2];
358603
- });
358604
- if (targetPositions.length === 0)
359150
+ const rowKey = (row, bytes) => sliceBytes(bytes, row.rowByteStart, row.rowByteEnd).replace(/\s+w14:paraId="[0-9A-Fa-f]{8}"/g, "");
359151
+ const baseKeys = baseTable.rows.map((row) => rowKey(row, baseBytes));
359152
+ const targetKeys = targetTable.rows.map((row) => rowKey(row, targetBytes));
359153
+ const baseIsShorter = baseKeys.length <= targetKeys.length;
359154
+ const matched = matchRowSubsequence(baseIsShorter ? baseKeys : targetKeys, baseIsShorter ? targetKeys : baseKeys, (baseIsShorter ? baseTable.rows : targetTable.rows).map((row) => row.rowNodeId), (baseIsShorter ? targetTable.rows : baseTable.rows).map((row) => row.rowNodeId));
359155
+ if (!matched)
359156
+ return unsafe(`table ${baseTable.tableNodeId} modifies, reorders, or ambiguously matches rows`, new Set(baseKeys).size < baseKeys.length || new Set(targetKeys).size < targetKeys.length);
359157
+ const baseIndexByTarget = new Map([...matched].map(([shortIndex, longIndex]) => baseIsShorter ? [longIndex, shortIndex] : [shortIndex, longIndex]));
359158
+ if (baseIndexByTarget.size === 0)
358605
359159
  return unsafe(`table ${baseTable.tableNodeId} has no surviving row`);
358606
- if (targetPositions.some((position2, index3) => index3 > 0 && position2 <= targetPositions[index3 - 1])) {
358607
- return unsafe(`table ${baseTable.tableNodeId} reorders rows`);
359160
+ if (hasInconsistentRowIdentity(baseTable.rows, targetTable.rows, baseIndexByTarget)) {
359161
+ return unsafe(`table ${baseTable.tableNodeId} reassigns a row identity outside its original position`, true);
358608
359162
  }
358609
- for (const baseRow of baseTable.rows) {
358610
- const targetPosition = targetRowsById.get(baseRow.rowNodeId);
359163
+ const targetIndexByBase = new Map([...baseIndexByTarget].map(([targetIndex, baseIndex]) => [baseIndex, targetIndex]));
359164
+ for (const [baseIndex, baseRow] of baseTable.rows.entries()) {
359165
+ const targetPosition = targetIndexByBase.get(baseIndex);
358611
359166
  if (targetPosition === undefined) {
358612
359167
  const rowXml = sliceBytes(baseBytes, baseRow.rowByteStart, baseRow.rowByteEnd);
358613
359168
  if (containsWordprocessingRevisionMarkup(rowXml, baseRoot.namespaceBindings)) {
@@ -358631,26 +359186,27 @@ function buildTrackedTableRowReplayPlan(input) {
358631
359186
  }
358632
359187
  const targetRow = targetTable.rows[targetPosition];
358633
359188
  if (sliceBytes(baseBytes, baseRow.rowByteStart, baseRow.rowByteEnd) !== sliceBytes(targetBytes, targetRow.rowByteStart, targetRow.rowByteEnd)) {
358634
- return unsafe(`table ${baseTable.tableNodeId} modifies an existing row`);
359189
+ if (baseKeys[baseIndex] !== targetKeys[targetPosition]) {
359190
+ return unsafe(`table ${baseTable.tableNodeId} modifies an existing row`);
359191
+ }
358635
359192
  }
358636
359193
  }
358637
359194
  if (tableSkeleton(baseBytes, baseTable) !== tableSkeleton(targetBytes, targetTable)) {
358638
359195
  return unsafe(`table ${baseTable.tableNodeId} changes non-row table structure`);
358639
359196
  }
358640
- const baseIds = new Set(baseTable.rows.map((row) => row.rowNodeId));
358641
359197
  let index2 = 0;
358642
359198
  while (index2 < targetTable.rows.length) {
358643
- if (baseIds.has(targetTable.rows[index2].rowNodeId)) {
359199
+ if (baseIndexByTarget.has(index2)) {
358644
359200
  index2 += 1;
358645
359201
  continue;
358646
359202
  }
358647
359203
  const start = index2;
358648
- while (index2 < targetTable.rows.length && !baseIds.has(targetTable.rows[index2].rowNodeId))
359204
+ while (index2 < targetTable.rows.length && !baseIndexByTarget.has(index2))
358649
359205
  index2 += 1;
358650
359206
  const inserted = targetTable.rows.slice(start, index2);
358651
- const nextBaseRow = targetTable.rows[index2];
358652
- const previousBaseRow = [...targetTable.rows.slice(0, start)].reverse().find((row) => baseIds.has(row.rowNodeId));
358653
- const anchor = nextBaseRow ?? previousBaseRow;
359207
+ const nextBaseIndex = baseIndexByTarget.get(index2);
359208
+ const previousBaseIndex = [...baseIndexByTarget.entries()].reverse().find(([targetIndex]) => targetIndex < start)?.[1];
359209
+ const anchor = baseTable.rows[nextBaseIndex ?? previousBaseIndex ?? -1];
358654
359210
  if (!anchor)
358655
359211
  return unsafe(`table ${baseTable.tableNodeId} has no stable insertion anchor`);
358656
359212
  for (const row of inserted) {
@@ -358672,20 +359228,34 @@ function buildTrackedTableRowReplayPlan(input) {
358672
359228
  const rowXml = transplantableRowPayload(sourceRowXml, baseRoot, targetRoot);
358673
359229
  if (!rowXml)
358674
359230
  return unsafe("inserted row uses a namespace that cannot be transplanted safely");
358675
- rowsXml.push(rowXml);
359231
+ const identifiedRowXml = ensureInsertedParagraphIds(rowXml, baseRoot, baseParagraphIds, usedParagraphIds);
359232
+ if (!identifiedRowXml)
359233
+ return unsafe("inserted row cannot be assigned safe paragraph identities");
359234
+ rowsXml.push(identifiedRowXml);
358676
359235
  }
358677
359236
  steps.push({
358678
359237
  kind: "insertRows",
358679
359238
  storyId: STORY_ID2,
358680
359239
  tableNodeId: baseTable.tableNodeId,
358681
359240
  anchorRowNodeId: anchor.rowNodeId,
358682
- placement: nextBaseRow ? "before" : "after",
359241
+ placement: nextBaseIndex !== undefined ? "before" : "after",
358683
359242
  rowsXml
358684
359243
  });
358685
359244
  }
359245
+ if (steps.some((step3) => step3.tableNodeId === baseTable.tableNodeId)) {
359246
+ for (const entry of [...input.baseBody ?? [], ...input.targetBody]) {
359247
+ if (entry.structuralScope?.startsWith(`table:${baseTable.tableNodeId}:cell:`) || entry.structuralScope?.startsWith(`table:${targetTable.tableNodeId}:cell:`)) {
359248
+ ownedBlockIds.add(entry.blockId);
359249
+ }
359250
+ }
359251
+ }
358686
359252
  }
358687
359253
  if (steps.length === 0)
358688
359254
  return unsafe("table topology changed without an inserted or deleted row");
359255
+ const ambiguousBodyOperation = input.bodyOperations.find((operation) => ownedBlockIds.has(operation.blockId) && (operation.kind === "insert" || operation.kind === "insertAtStart" ? input.targetBody.some((entry) => entry.blockId === operation.blockId && !entry.structuralScope?.startsWith("table:")) : input.baseBody?.some((entry) => entry.blockId === operation.blockId && !entry.structuralScope?.startsWith("table:"))));
359256
+ if (ambiguousBodyOperation) {
359257
+ return unsafe(`body operation ${ambiguousBodyOperation.blockId} shares a table-cell identity`, true);
359258
+ }
358689
359259
  const bodyOperations = input.bodyOperations.filter((operation) => !ownedBlockIds.has(operation.blockId));
358690
359260
  const unownedStructuralOperation = bodyOperations.find((operation) => {
358691
359261
  const target = input.targetBody.find((entry) => entry.blockId === operation.blockId);
@@ -358715,21 +359285,135 @@ function containsWordprocessingRevisionMarkup(xml, namespaceBindings) {
358715
359285
  }
358716
359286
  return containsRevision;
358717
359287
  }
358718
- function unsafe(reason) {
358719
- return { steps: [], bodyOperations: [], unsafeReasons: [reason] };
359288
+ function unsafe(reason, refuseBlockFallback = false) {
359289
+ return { steps: [], bodyOperations: [], unsafeReasons: [reason], refuseBlockFallback };
358720
359290
  }
358721
- function uniqueRows(ids) {
358722
- const result = new Map;
358723
- for (let index2 = 0;index2 < ids.length; index2 += 1) {
358724
- const id = ids[index2];
358725
- if (result.has(id))
359291
+ function matchRowSubsequence(shortKeys, longKeys, shortIds, longIds) {
359292
+ const remainingMatches = (shortStart, longStart) => {
359293
+ for (let shortIndex = shortStart;shortIndex < shortKeys.length; shortIndex += 1) {
359294
+ while (longStart < longKeys.length && longKeys[longStart] !== shortKeys[shortIndex])
359295
+ longStart += 1;
359296
+ if (longStart === longKeys.length)
359297
+ return false;
359298
+ longStart += 1;
359299
+ }
359300
+ return true;
359301
+ };
359302
+ const matches = new Map;
359303
+ let nextLongIndex = 0;
359304
+ for (let shortIndex = 0;shortIndex < shortKeys.length; shortIndex += 1) {
359305
+ const candidates = [];
359306
+ for (let longIndex = nextLongIndex;longIndex < longKeys.length; longIndex += 1) {
359307
+ if (shortKeys[shortIndex] === longKeys[longIndex] && remainingMatches(shortIndex + 1, longIndex + 1))
359308
+ candidates.push(longIndex);
359309
+ }
359310
+ if (candidates.length === 0)
358726
359311
  return null;
358727
- result.set(id, index2);
359312
+ const sameIdentity = candidates.filter((index2) => shortIds[shortIndex] === longIds[index2]);
359313
+ if (candidates.length > 1 && sameIdentity.length !== 1)
359314
+ return null;
359315
+ const selected = sameIdentity[0] ?? candidates[0];
359316
+ matches.set(shortIndex, selected);
359317
+ nextLongIndex = selected + 1;
358728
359318
  }
358729
- return result;
359319
+ for (const [shortIndex, longIndex] of matches) {
359320
+ const key2 = shortKeys[shortIndex];
359321
+ if (shortKeys.indexOf(key2) === shortKeys.lastIndexOf(key2) && longKeys.indexOf(key2) === longKeys.lastIndexOf(key2))
359322
+ continue;
359323
+ const sharedLongIndex = longIds.findIndex((id, index2) => id === shortIds[shortIndex] && longKeys[index2] === key2);
359324
+ const sharedShortIndex = shortIds.findIndex((id, index2) => id === longIds[longIndex] && shortKeys[index2] === key2);
359325
+ if (sharedLongIndex >= 0 && sharedLongIndex !== longIndex || sharedShortIndex >= 0 && sharedShortIndex !== shortIndex)
359326
+ return null;
359327
+ }
359328
+ return matches;
359329
+ }
359330
+ function hasInconsistentRowIdentity(baseRows, targetRows, baseIndexByTarget) {
359331
+ const baseIndexById = new Map(baseRows.map((row, index2) => [row.rowNodeId, index2]));
359332
+ for (const [targetIndex, targetRow] of targetRows.entries()) {
359333
+ if (targetRow.identityProvenance !== "para-id")
359334
+ continue;
359335
+ const baseIndex = baseIndexById.get(targetRow.rowNodeId);
359336
+ if (baseIndex === undefined || baseRows[baseIndex].identityProvenance !== "para-id")
359337
+ continue;
359338
+ if (baseIndex !== targetIndex && baseIndexByTarget.get(targetIndex) !== baseIndex)
359339
+ return true;
359340
+ }
359341
+ return false;
359342
+ }
359343
+ function isSimpleTable(table, bytes, root) {
359344
+ return !table.containsNestedTable && !table.hasGridSpan && !table.hasVMerge && table.isRectangular && table.rows.every((row) => row.cells.every((cell) => !cell.containsUnsupportedContent || isSafeContentControlCell(sliceBytes(bytes, cell.bodyByteStart, cell.bodyByteEnd), root)));
359345
+ }
359346
+ function isSafeContentControlCell(xml, root) {
359347
+ let foundControl = false;
359348
+ let safe = true;
359349
+ const parser = createXmlSaxParser({
359350
+ namespaceAware: true,
359351
+ fragment: true,
359352
+ additionalNamespaces: { ...root.namespaceBindings }
359353
+ });
359354
+ parser.on("error", () => {
359355
+ safe = false;
359356
+ });
359357
+ parser.on("opentag", (tag) => {
359358
+ if (tag.namespaceUri !== WORDPROCESSINGML_NS4)
359359
+ return;
359360
+ if (tag.localName === "sdt")
359361
+ foundControl = true;
359362
+ if (["drawing", "object", "oMath", "oMathPara", "tbl"].includes(tag.localName) || tag.attributes.some((attribute2) => isRelationshipNamespace(attribute2.namespaceUri)))
359363
+ safe = false;
359364
+ });
359365
+ try {
359366
+ parser.write(xml);
359367
+ parser.close();
359368
+ } catch {
359369
+ return false;
359370
+ }
359371
+ return foundControl && safe;
358730
359372
  }
358731
- function isSimpleTable(table) {
358732
- return !table.containsNestedTable && !table.hasGridSpan && !table.hasVMerge && table.isRectangular && table.rows.every((row) => row.cells.every((cell) => !cell.containsUnsupportedContent));
359373
+ function ensureInsertedParagraphIds(xml, root, baseIds, usedIds) {
359374
+ const parsed = collectXmlElementRanges(xml, {
359375
+ fragment: true,
359376
+ additionalNamespaces: { ...root.namespaceBindings }
359377
+ });
359378
+ if (parsed.errors.length > 0)
359379
+ return null;
359380
+ const insertions = [];
359381
+ for (const paragraph4 of parsed.ranges.filter((range) => range.tag.namespaceUri === WORDPROCESSINGML_NS4 && range.tag.localName === "p")) {
359382
+ const existingId = paragraph4.tag.attributes.find((attribute2) => attribute2.namespaceUri === WORDPROCESSINGML_2010_NS && attribute2.localName === "paraId");
359383
+ if (existingId && !baseIds.has(existingId.value.toUpperCase()))
359384
+ continue;
359385
+ let identifier;
359386
+ for (let candidate2 = 1879048192;candidate2 > 0; candidate2 -= 1) {
359387
+ const value2 = candidate2.toString(16).toUpperCase().padStart(8, "0");
359388
+ if (!usedIds.has(value2)) {
359389
+ identifier = value2;
359390
+ usedIds.add(value2);
359391
+ break;
359392
+ }
359393
+ }
359394
+ if (!identifier)
359395
+ return null;
359396
+ if (existingId) {
359397
+ const opening = xml.slice(paragraph4.start, paragraph4.openEnd);
359398
+ const attribute2 = new RegExp(`\\b${existingId.name}="${existingId.value}"`);
359399
+ const match = attribute2.exec(opening);
359400
+ if (!match)
359401
+ return null;
359402
+ insertions.push({
359403
+ offset: paragraph4.start + match.index,
359404
+ text: `${existingId.name}="${identifier}"`,
359405
+ end: paragraph4.start + match.index + match[0].length
359406
+ });
359407
+ } else {
359408
+ const localDeclaration = paragraph4.namespaceBindings.w14 === WORDPROCESSINGML_2010_NS ? "" : ` xmlns:w14="${WORDPROCESSINGML_2010_NS}"`;
359409
+ const opening = xml.slice(paragraph4.start, paragraph4.openEnd);
359410
+ insertions.push({
359411
+ offset: paragraph4.openEnd - (opening.endsWith("/>") ? 2 : 1),
359412
+ text: `${localDeclaration} w14:paraId="${identifier}"`
359413
+ });
359414
+ }
359415
+ }
359416
+ return insertions.reverse().reduce((result, insertion) => result.slice(0, insertion.offset) + insertion.text + result.slice(insertion.end ?? insertion.offset), xml);
358733
359417
  }
358734
359418
  function tableSkeleton(bytes, table) {
358735
359419
  let cursor = table.tableByteStart;
@@ -360034,10 +360718,16 @@ function projectMainDocumentBodyEntries(source, options = {}) {
360034
360718
  });
360035
360719
  const scopes = mapBodyParagraphStructuralScopes(tableCatalog, paragraphIndex.entries);
360036
360720
  const namespaceBindingsByByteStart = scanParagraphNamespaceBindingsByByteStart(source.xml);
360037
- return paragraphIndex.entries.map((entry) => {
360038
- const rawXml = TD107.decode(bytes.subarray(entry.byteStart, entry.byteEnd));
360721
+ const rawXmls = paragraphIndex.entries.map((entry) => TD107.decode(bytes.subarray(entry.byteStart, entry.byteEnd)));
360722
+ const runXmls = paragraphIndex.entries.map((entry, index2) => {
360723
+ const namespaceBindings = namespaceBindingsByByteStart.get(entry.byteStart) ?? {};
360724
+ return extractParagraphRunXml(rawXmls[index2], namespaceBindings);
360725
+ });
360726
+ const runSignatures = computeRunSignatures(runXmls);
360727
+ return paragraphIndex.entries.map((entry, index2) => {
360728
+ const rawXml = rawXmls[index2];
360039
360729
  const namespaceBindings = namespaceBindingsByByteStart.get(entry.byteStart) ?? {};
360040
- const runXml = extractParagraphRunXml(rawXml, namespaceBindings);
360730
+ const runXml = runXmls[index2];
360041
360731
  const paragraphPropertiesXml = extractParagraphPropertiesXml(rawXml, namespaceBindings);
360042
360732
  return {
360043
360733
  blockId: entry.blockId,
@@ -360046,7 +360736,7 @@ function projectMainDocumentBodyEntries(source, options = {}) {
360046
360736
  text: readVisibleParagraphText(rawXml),
360047
360737
  headingLevel: paragraphHeadingLevel(rawXml, { paragraphStyleOutlineLevels }),
360048
360738
  styleId: paragraphStyleId(rawXml),
360049
- runSignature: computeRunSignature(runXml),
360739
+ runSignature: runSignatures[index2],
360050
360740
  runXml,
360051
360741
  paragraphPropertiesXml,
360052
360742
  paragraphPropertiesSignature: computeParagraphPropertiesSignature2(paragraphPropertiesXml)
@@ -361391,7 +362081,32 @@ function paragraphOpeningTag2(paragraphXml) {
361391
362081
  return paragraphXml.slice(0, paragraphXml.indexOf(">") + 1);
361392
362082
  }
361393
362083
  function ownedStructuresEqual(leftXml, rightXml) {
361394
- return arraysEqual2(collectElements(leftXml, "sectPr"), collectElements(rightXml, "sectPr")) && arraysEqual2(collectEmptyElements(leftXml, "bookmarkStart"), collectEmptyElements(rightXml, "bookmarkStart")) && arraysEqual2(collectEmptyElements(leftXml, "bookmarkEnd"), collectEmptyElements(rightXml, "bookmarkEnd")) && arraysEqual2(collectElements(leftXml, "fldSimple"), collectElements(rightXml, "fldSimple")) && arraysEqual2(collectElements(leftXml, "instrText"), collectElements(rightXml, "instrText")) && arraysEqual2(collectEmptyElements(leftXml, "fldChar"), collectEmptyElements(rightXml, "fldChar"));
362084
+ return arraysEqual2(collectElements(leftXml, "sectPr"), collectElements(rightXml, "sectPr")) && bookmarkStructuresEqual(leftXml, rightXml) && arraysEqual2(collectElements(leftXml, "fldSimple"), collectElements(rightXml, "fldSimple")) && arraysEqual2(collectElements(leftXml, "instrText"), collectElements(rightXml, "instrText")) && arraysEqual2(collectEmptyElements(leftXml, "fldChar"), collectEmptyElements(rightXml, "fldChar"));
362085
+ }
362086
+ function bookmarkStructuresEqual(leftXml, rightXml) {
362087
+ const summarize = (xml) => {
362088
+ if (!bookmarkRangesArePaired(xml))
362089
+ return null;
362090
+ const nameById = new Map(collectWordElements(xml, "bookmarkStart", true).map((element) => {
362091
+ const id = readAttribute2(element, "id");
362092
+ const name = readAttribute2(element, "name");
362093
+ return [id, name];
362094
+ }));
362095
+ const starts = collectWordElements(xml, "bookmarkStart", true).map((element) => ({
362096
+ position: element.range.start,
362097
+ value: `start:${replaceAttribute(element, "id", "__bookmark_id__")}`
362098
+ }));
362099
+ const ends = collectWordElements(xml, "bookmarkEnd", true).map((element) => ({
362100
+ position: element.range.start,
362101
+ value: `end:${replaceAttribute(element, "id", `__bookmark_${nameById.get(readAttribute2(element, "id")) ?? "missing"}__`)}`
362102
+ }));
362103
+ return [...starts, ...ends].sort((a, b) => a.position - b.position).map((event) => event.value);
362104
+ };
362105
+ const left = summarize(leftXml);
362106
+ const right = summarize(rightXml);
362107
+ if (!left || !right || left.length !== right.length)
362108
+ return false;
362109
+ return left.every((value2, index2) => value2 === right[index2]);
361395
362110
  }
361396
362111
  function arraysEqual2(left, right) {
361397
362112
  return left.length === right.length && left.every((value2, index2) => value2 === right[index2]);
@@ -361427,39 +362142,44 @@ function buildSectionSteps(baseXml, targetXml, unsafeReasons) {
361427
362142
  function buildReferenceSteps(baseXml, targetXml, unsafeReasons) {
361428
362143
  const baseBookmarks = collectWordElements(baseXml, "bookmarkStart", true);
361429
362144
  const targetBookmarks = collectWordElements(targetXml, "bookmarkStart", true);
361430
- if (baseBookmarks.length !== targetBookmarks.length) {
361431
- unsafeReasons.push("bookmark topology changed");
361432
- return [];
361433
- }
361434
362145
  if (!bookmarkRangesArePaired(baseXml) || !bookmarkRangesArePaired(targetXml)) {
361435
362146
  unsafeReasons.push("bookmark topology is unbalanced or duplicated");
361436
362147
  return [];
361437
362148
  }
361438
362149
  const renamedBookmarks = new Map;
361439
362150
  const steps = [];
361440
- for (let index2 = 0;index2 < baseBookmarks.length; index2 += 1) {
361441
- const before = baseBookmarks[index2];
361442
- const after = targetBookmarks[index2];
361443
- const beforeXml = before.xml;
361444
- const afterXml = after.xml;
361445
- if (beforeXml === afterXml)
361446
- continue;
361447
- const beforeId = readAttribute2(before, "id");
361448
- const afterId = readAttribute2(after, "id");
361449
- const beforeName = readAttribute2(before, "name");
361450
- const afterName = readAttribute2(after, "name");
361451
- if (!beforeId || beforeId !== afterId || !beforeName || !afterName || replaceAttribute(before, "name", afterName) !== afterXml) {
361452
- unsafeReasons.push(`bookmark ${index2} changed beyond a name-only rename`);
361453
- continue;
362151
+ if (baseBookmarks.length === targetBookmarks.length) {
362152
+ for (let index2 = 0;index2 < baseBookmarks.length; index2 += 1) {
362153
+ const before = baseBookmarks[index2];
362154
+ const after = targetBookmarks[index2];
362155
+ const beforeXml = before.xml;
362156
+ const afterXml = after.xml;
362157
+ if (beforeXml === afterXml)
362158
+ continue;
362159
+ const beforeId = readAttribute2(before, "id");
362160
+ const afterId = readAttribute2(after, "id");
362161
+ const beforeName = readAttribute2(before, "name");
362162
+ const afterName = readAttribute2(after, "name");
362163
+ if (beforeId && afterId && beforeName && beforeName === afterName && replaceAttribute(after, "id", beforeId) === beforeXml) {
362164
+ continue;
362165
+ }
362166
+ if (!beforeId || beforeId !== afterId || !beforeName || !afterName || replaceAttribute(before, "name", afterName) !== afterXml) {
362167
+ unsafeReasons.push(`bookmark ${index2} changed beyond a name-only rename or ID allocation`);
362168
+ continue;
362169
+ }
362170
+ renamedBookmarks.set(beforeName, afterName);
362171
+ steps.push({
362172
+ kind: "bookmark-start",
362173
+ beforeXml,
362174
+ afterXml,
362175
+ beforeNamespaceBindings: { ...before.range.namespaceBindings },
362176
+ afterNamespaceBindings: { ...after.range.namespaceBindings }
362177
+ });
361454
362178
  }
361455
- renamedBookmarks.set(beforeName, afterName);
361456
- steps.push({
361457
- kind: "bookmark-start",
361458
- beforeXml,
361459
- afterXml,
361460
- beforeNamespaceBindings: { ...before.range.namespaceBindings },
361461
- afterNamespaceBindings: { ...after.range.namespaceBindings }
361462
- });
362179
+ } else if (targetBookmarks.length > baseBookmarks.length) {
362180
+ steps.push(...buildAddedBookmarkSteps(baseXml, targetXml, baseBookmarks, targetBookmarks, unsafeReasons));
362181
+ } else {
362182
+ unsafeReasons.push("bookmark removal is not supported");
361463
362183
  }
361464
362184
  const baseFields = collectWordElements(baseXml, "fldSimple");
361465
362185
  const targetFields = collectWordElements(targetXml, "fldSimple");
@@ -361490,8 +362210,125 @@ function buildReferenceSteps(baseXml, targetXml, unsafeReasons) {
361490
362210
  afterNamespaceBindings: { ...after.range.namespaceBindings }
361491
362211
  });
361492
362212
  }
362213
+ steps.push(...buildAddedComplexFieldSteps(baseXml, targetXml, unsafeReasons));
362214
+ return steps;
362215
+ }
362216
+ function buildAddedBookmarkSteps(baseXml, targetXml, baseBookmarks, targetBookmarks, unsafeReasons) {
362217
+ const baseNames = new Set(baseBookmarks.map((element) => readAttribute2(element, "name")));
362218
+ const targetNames = targetBookmarks.map((element) => readAttribute2(element, "name"));
362219
+ if (baseNames.has(null) || targetNames.some((name) => name === null) || new Set(targetNames).size !== targetNames.length || [...baseNames].some((name) => !targetNames.includes(name))) {
362220
+ unsafeReasons.push("bookmark addition cannot be matched unambiguously by name");
362221
+ return [];
362222
+ }
362223
+ const baseParagraphs = collectWordElements(baseXml, "p");
362224
+ const targetParagraphs = collectWordElements(targetXml, "p");
362225
+ if (baseParagraphs.length !== targetParagraphs.length) {
362226
+ unsafeReasons.push("bookmark addition changed paragraph topology");
362227
+ return [];
362228
+ }
362229
+ const targetEnds = collectWordElements(targetXml, "bookmarkEnd", true);
362230
+ const steps = [];
362231
+ for (const start of targetBookmarks.filter((element) => !baseNames.has(readAttribute2(element, "name")))) {
362232
+ const id = readAttribute2(start, "id");
362233
+ const end = targetEnds.find((candidate2) => readAttribute2(candidate2, "id") === id);
362234
+ const paragraphIndex = targetParagraphs.findIndex((paragraph4) => paragraph4.range.start < start.range.start && paragraph4.range.end > (end?.range.end ?? Number.MAX_SAFE_INTEGER));
362235
+ if (!id || !end || paragraphIndex < 0) {
362236
+ unsafeReasons.push("added bookmark range is not contained in one paragraph");
362237
+ continue;
362238
+ }
362239
+ const before = baseParagraphs[paragraphIndex];
362240
+ const after = targetParagraphs[paragraphIndex];
362241
+ const beforeFragmentXml = targetXml.slice(start.range.end, end.range.start);
362242
+ const afterFragmentXml = targetXml.slice(start.range.start, end.range.end);
362243
+ if (replaceUnique(after.xml, afterFragmentXml, beforeFragmentXml) !== before.xml) {
362244
+ unsafeReasons.push("added bookmark range changes content beyond a balanced wrapper");
362245
+ continue;
362246
+ }
362247
+ steps.push({
362248
+ kind: "bookmark-range",
362249
+ beforeXml: before.xml,
362250
+ afterXml: after.xml,
362251
+ beforeFragmentXml,
362252
+ afterFragmentXml,
362253
+ beforeNamespaceBindings: { ...before.range.namespaceBindings },
362254
+ afterNamespaceBindings: { ...after.range.namespaceBindings }
362255
+ });
362256
+ }
361493
362257
  return steps;
361494
362258
  }
362259
+ function buildAddedComplexFieldSteps(baseXml, targetXml, unsafeReasons) {
362260
+ const baseParagraphs = collectWordElements(baseXml, "p");
362261
+ const targetParagraphs = collectWordElements(targetXml, "p");
362262
+ if (baseParagraphs.length !== targetParagraphs.length)
362263
+ return [];
362264
+ const targetBookmarkNameCounts = new Map;
362265
+ for (const bookmark of collectWordElements(targetXml, "bookmarkStart", true)) {
362266
+ const name = readAttribute2(bookmark, "name");
362267
+ if (name !== null) {
362268
+ targetBookmarkNameCounts.set(name, (targetBookmarkNameCounts.get(name) ?? 0) + 1);
362269
+ }
362270
+ }
362271
+ const steps = [];
362272
+ for (let index2 = 0;index2 < targetParagraphs.length; index2 += 1) {
362273
+ const before = baseParagraphs[index2];
362274
+ const after = targetParagraphs[index2];
362275
+ if (before.xml === after.xml)
362276
+ continue;
362277
+ const candidate2 = addedComplexRefField(after, before.xml);
362278
+ if (!candidate2)
362279
+ continue;
362280
+ const targetCount = targetBookmarkNameCounts.get(candidate2.referenceName) ?? 0;
362281
+ if (targetCount === 0) {
362282
+ unsafeReasons.push(`complex REF field ${index2} targets a missing bookmark`);
362283
+ continue;
362284
+ }
362285
+ if (targetCount !== 1) {
362286
+ unsafeReasons.push(`complex REF field ${index2} targets an ambiguous bookmark`);
362287
+ continue;
362288
+ }
362289
+ steps.push({
362290
+ kind: "complex-field",
362291
+ beforeXml: before.xml,
362292
+ afterXml: after.xml,
362293
+ beforeFragmentXml: candidate2.resultXml,
362294
+ afterFragmentXml: candidate2.fieldXml,
362295
+ beforeNamespaceBindings: { ...before.range.namespaceBindings },
362296
+ afterNamespaceBindings: { ...after.range.namespaceBindings }
362297
+ });
362298
+ }
362299
+ return steps;
362300
+ }
362301
+ function addedComplexRefField(targetParagraph, expectedBaseParagraphXml) {
362302
+ const fields = collectWordElements(targetParagraph.xml, "fldChar", true, targetParagraph.range.namespaceBindings);
362303
+ for (let beginIndex = 0;beginIndex < fields.length; beginIndex += 1) {
362304
+ const begin = fields[beginIndex];
362305
+ if (readAttribute2(begin, "fldCharType") !== "begin")
362306
+ continue;
362307
+ const separate = fields.slice(beginIndex + 1).find((field) => readAttribute2(field, "fldCharType") === "separate");
362308
+ const end = separate && fields.find((field) => field.range.start > separate.range.start && readAttribute2(field, "fldCharType") === "end");
362309
+ if (!separate || !end)
362310
+ continue;
362311
+ const runs = collectWordElements(targetParagraph.xml, "r", false, targetParagraph.range.namespaceBindings);
362312
+ const beginRun = runs.find((run) => run.range.start <= begin.range.start && run.range.end >= begin.range.end);
362313
+ const separateRun = runs.find((run) => run.range.start <= separate.range.start && run.range.end >= separate.range.end);
362314
+ const endRun = runs.find((run) => run.range.start <= end.range.start && run.range.end >= end.range.end);
362315
+ if (!beginRun || !separateRun || !endRun)
362316
+ continue;
362317
+ const instruction = collectWordElements(targetParagraph.xml, "instrText", false, targetParagraph.range.namespaceBindings).filter((element) => element.range.start > begin.range.end && element.range.end < separate.range.start).map((element) => element.xml.replace(/^<[^>]+>|<\/[^>]+>$/g, "")).join("");
362318
+ const parsed = parseRefInstruction(instruction);
362319
+ if (!parsed)
362320
+ continue;
362321
+ const fieldXml = targetParagraph.xml.slice(beginRun.range.start, endRun.range.end);
362322
+ const resultXml = targetParagraph.xml.slice(separateRun.range.end, endRun.range.start);
362323
+ const fieldChars = fields.filter((field) => field.range.start >= begin.range.start && field.range.end <= end.range.end);
362324
+ if (fieldChars.length !== 3 || fieldChars.map((field) => readAttribute2(field, "fldCharType")).join(",") !== "begin,separate,end")
362325
+ continue;
362326
+ if (!resultXml || replaceUnique(targetParagraph.xml, fieldXml, resultXml) !== expectedBaseParagraphXml)
362327
+ continue;
362328
+ return { fieldXml, resultXml, referenceName: parsed.name };
362329
+ }
362330
+ return null;
362331
+ }
361495
362332
  function bookmarkRangesArePaired(xml) {
361496
362333
  const startsById = new Map;
361497
362334
  for (const element of collectWordElements(xml, "bookmarkStart", true)) {
@@ -361520,8 +362357,10 @@ function collectEmptyElements(xml, localName2) {
361520
362357
  function collectElements(xml, localName2) {
361521
362358
  return collectWordElements(xml, localName2).map((element) => element.xml);
361522
362359
  }
361523
- function collectWordElements(xml, localName2, selfClosingOnly = false) {
361524
- const parsed = collectXmlElementRanges(xml, { additionalNamespaces: { w: WORDPROCESSINGML_NS6 } });
362360
+ function collectWordElements(xml, localName2, selfClosingOnly = false, additionalNamespaces = {}) {
362361
+ const parsed = collectXmlElementRanges(xml, {
362362
+ additionalNamespaces: { w: WORDPROCESSINGML_NS6, ...additionalNamespaces }
362363
+ });
361525
362364
  if (parsed.errors.length > 0)
361526
362365
  return [];
361527
362366
  return parsed.ranges.filter((range) => range.tag.namespaceUri === WORDPROCESSINGML_NS6 && range.tag.localName === localName2 && (!selfClosingOnly || range.tag.isSelfClosing)).sort((left, right) => left.start - right.start).map((range) => ({ xml: xml.slice(range.start, range.end), range }));
@@ -361676,7 +362515,11 @@ class V2DiffAdapter {
361676
362515
  try {
361677
362516
  const engine = createV2CompareEngine(this.context);
361678
362517
  const base3 = captureCompareSnapshotSync(this.context);
361679
- const analysis = engine.analyzeSnapshots(base3, targetPayload.compareSnapshot);
362518
+ const baseMainDocument = projectDiffMainDocumentSource(this.context);
362519
+ const targetMainDocument = targetPayload.mainDocument ?? EMPTY_MAIN_DOCUMENT_SOURCE;
362520
+ const rawAnalysis = engine.analyzeSnapshots(base3, targetPayload.compareSnapshot);
362521
+ const comparisonTarget = paragraphIdentityOnlyComparisonTarget(base3, targetPayload.compareSnapshot, baseMainDocument, targetMainDocument, rawAnalysis.result);
362522
+ const analysis = comparisonTarget ? engine.analyzeSnapshots(base3, comparisonTarget) : rawAnalysis;
361680
362523
  if (!analysis.plan) {
361681
362524
  throw diffValidationError("INVALID_INPUT", "diff.compare: compare engine did not return a semantic analysis plan.");
361682
362525
  }
@@ -361693,8 +362536,6 @@ class V2DiffAdapter {
361693
362536
  const baseNumbering = projectDiffNumberingSource(this.context);
361694
362537
  const targetNumbering = targetPayload.numbering ?? { xml: null };
361695
362538
  const numberingPlan = buildNumberingApplyPlan(baseNumbering, targetNumbering);
361696
- const baseMainDocument = projectDiffMainDocumentSource(this.context);
361697
- const targetMainDocument = targetPayload.mainDocument ?? EMPTY_MAIN_DOCUMENT_SOURCE;
361698
362539
  const sectionsReferencesPlan = buildSectionsReferencesReplayPlan(baseMainDocument, targetMainDocument);
361699
362540
  const adapterApplySupportedFamilies = new Set;
361700
362541
  if (sectionsReferencesPlan.unsafeReasons.length === 0) {
@@ -361967,8 +362808,9 @@ class V2DiffAdapter {
361967
362808
  unsupportedReason: "section-reference-replay-unsafe"
361968
362809
  });
361969
362810
  }
361970
- const localizedBodyOperations = sectionsReferencesPlan.hasUnownedMainDocumentChanges ? buildLocalizedBodyOperations(payload.base.body, payload.target.body, sectionsReferencesPlan.sectionSteps, sectionsReferencesPlan.referenceSteps, { base: payload.base.definitions, target: payload.target.definitions }, canonicalBodyOperations) : [];
361971
- if (localizedBodyOperations.length > 0) {
362811
+ const usesLocalizedMainDocumentReplay = sectionsReferencesChanged && (sectionsReferencesPlan.sectionSteps.length > 0 || sectionsReferencesPlan.referenceSteps.length > 0);
362812
+ const localizedBodyOperations = usesLocalizedMainDocumentReplay ? buildLocalizedBodyOperations(payload.base.body, payload.target.body, sectionsReferencesPlan.sectionSteps, sectionsReferencesPlan.referenceSteps, { base: payload.base.definitions, target: payload.target.definitions }, canonicalBodyOperations) : [];
362813
+ if (usesLocalizedMainDocumentReplay) {
361972
362814
  setRuntimeBodyOperationCounts(operationCounts, payload.applyPlan, localizedBodyOperations);
361973
362815
  }
361974
362816
  const localizedMainDocumentCovered = !sectionsReferencesPlan.hasUnownedMainDocumentChanges || localizedMainDocumentChangesAreCovered({
@@ -362014,19 +362856,23 @@ class V2DiffAdapter {
362014
362856
  }
362015
362857
  }
362016
362858
  const mainDocumentPlan = tablesChanged && !reviewMarkup ? buildMainDocumentApplyPlan(liveBaseMainDocument, payloadMainDocument.target) : { steps: [], unsafeReasons: [] };
362017
- const trackedTableBodyOperations = !tablesChanged || !reviewMarkup ? payload.applyPlan.bodyOperations : localizedBodyOperations.length > 0 ? localizedBodyOperations : mergeCanonicalRevisionReplay(computeCompareBodyOperations(payload.base.body, payload.target.body, {
362859
+ const trackedTableBodyOperations = !tablesChanged || !reviewMarkup ? payload.applyPlan.bodyOperations : usesLocalizedMainDocumentReplay ? localizedBodyOperations : mergeCanonicalRevisionReplay(computeCompareBodyOperations(payload.base.body, payload.target.body, {
362018
362860
  base: payload.base.definitions,
362019
362861
  target: payload.target.definitions
362020
362862
  }), canonicalBodyOperations);
362021
362863
  const trackedTablePropertyPlan = tablesChanged && reviewMarkup ? buildTrackedTablePropertyReplayPlan(liveBaseMainDocument, payloadMainDocument.target) : null;
362022
- let trackedTableBlockPlan = tablesChanged && reviewMarkup && !trackedTablePropertyPlan ? buildTrackedTableBlockReplayPlan({ base: liveBaseMainDocument, target: payloadMainDocument.target, bodyOperations: trackedTableBodyOperations }) : null;
362023
- let trackedTablePlan = tablesChanged && reviewMarkup && !trackedTableBlockPlan && !trackedTablePropertyPlan ? buildTrackedTableRowReplayPlan({
362864
+ let trackedTablePlan = tablesChanged && reviewMarkup && !trackedTablePropertyPlan ? buildTrackedTableRowReplayPlan({
362024
362865
  base: liveBaseMainDocument,
362025
362866
  target: payloadMainDocument.target,
362867
+ baseBody: payload.base.body,
362026
362868
  targetBody: payload.target.body,
362027
362869
  bodyOperations: trackedTableBodyOperations
362028
- }) : { steps: [], bodyOperations: trackedTableBlockPlan?.bodyOperations ?? (trackedTablePropertyPlan ? trackedTableBodyOperations : payload.applyPlan.bodyOperations), unsafeReasons: [] };
362029
- if (trackedTablePlan.unsafeReasons.length > 0) {
362870
+ }) : { steps: [], bodyOperations: trackedTablePropertyPlan ? trackedTableBodyOperations : payload.applyPlan.bodyOperations, unsafeReasons: [] };
362871
+ let trackedTableBlockPlan = tablesChanged && reviewMarkup && !trackedTablePropertyPlan && !trackedTablePlan.refuseBlockFallback && (trackedTablePlan.unsafeReasons.length > 0 || trackedTablePlan.steps.length === 0) ? buildTrackedTableBlockReplayPlan({ base: liveBaseMainDocument, target: payloadMainDocument.target, bodyOperations: trackedTableBodyOperations }) : null;
362872
+ if (trackedTableBlockPlan) {
362873
+ trackedTablePlan = { steps: [], bodyOperations: trackedTableBlockPlan.bodyOperations, unsafeReasons: [] };
362874
+ }
362875
+ if (trackedTablePlan.unsafeReasons.length > 0 && !trackedTablePlan.refuseBlockFallback) {
362030
362876
  const replacement = buildTrackedTableBlockReplayPlan({
362031
362877
  base: liveBaseMainDocument,
362032
362878
  target: payloadMainDocument.target,
@@ -362081,7 +362927,7 @@ class V2DiffAdapter {
362081
362927
  if (mainDocumentPlan.unsafeReasons.length > 0) {
362082
362928
  throw diffValidationError("CAPABILITY_UNSUPPORTED", `diff.apply: main document replay is unsafe — [${mainDocumentPlan.unsafeReasons.join("; ")}]. ` + "No mutation was performed (full payloads are never partially applied).", { changedFamilies: ["tables"], unsupportedReason: "main-document-replay-unsafe" });
362083
362929
  }
362084
- const runtimeApplyPlan = localizedBodyOperations.length > 0 ? {
362930
+ const runtimeApplyPlan = usesLocalizedMainDocumentReplay ? {
362085
362931
  ...runtimeApplyPlanBase,
362086
362932
  applyPlan: {
362087
362933
  ...runtimeApplyPlanBase.applyPlan,
@@ -362113,6 +362959,13 @@ class V2DiffAdapter {
362113
362959
  handledFamilies.add("body");
362114
362960
  }
362115
362961
  }
362962
+ const rowControlsCovered = trackedTablePlan.steps.length > 0 && rowReplayCoversContentControls(payload.base.annotations, payload.target.annotations, trackedTablePlan.steps, liveBaseMainDocument.xml, payloadMainDocument.target.xml);
362963
+ if (trackedTablePlan.steps.length > 0 && !rowControlsCovered) {
362964
+ throw diffValidationError("CAPABILITY_UNSUPPORTED", "diff.apply: row replay cannot cover content-control structure. No mutation was performed.", { changedFamilies: ["content-controls"], unsupportedReason: "content-control-replay-unsafe" });
362965
+ }
362966
+ if (changedPolicies.some((policy) => policy.family === "content-controls") && rowControlsCovered) {
362967
+ handledFamilies.add("content-controls");
362968
+ }
362116
362969
  if (mainRevisionChangesHandled)
362117
362970
  handledFamilies.add("tracked-changes");
362118
362971
  if (mainDocumentPlan.steps.length > 0)
@@ -362289,6 +363142,23 @@ function buildCompareApplyEligibility(context, payload, plans) {
362289
363142
  direct.add("fields-references");
362290
363143
  tracked.add("fields-references");
362291
363144
  }
363145
+ const usesLocalizedMainDocumentReplay = plans.sectionsReferencesPlan.sectionSteps.length > 0 || plans.sectionsReferencesPlan.referenceSteps.length > 0;
363146
+ if (usesLocalizedMainDocumentReplay) {
363147
+ const localizedBodyOperations = buildLocalizedBodyOperations(payload.base.body, payload.target.body, plans.sectionsReferencesPlan.sectionSteps, plans.sectionsReferencesPlan.referenceSteps, { base: payload.base.definitions, target: payload.target.definitions }, payload.applyPlan.bodyOperations);
363148
+ const localizedMainDocumentCovered = !plans.sectionsReferencesPlan.hasUnownedMainDocumentChanges || localizedMainDocumentChangesAreCovered({
363149
+ base: payload.mainDocument?.base ?? EMPTY_MAIN_DOCUMENT_SOURCE,
363150
+ target: payload.mainDocument?.target ?? EMPTY_MAIN_DOCUMENT_SOURCE,
363151
+ baseBody: payload.base.body,
363152
+ targetBody: payload.target.body,
363153
+ sectionSteps: plans.sectionsReferencesPlan.sectionSteps,
363154
+ referenceSteps: plans.sectionsReferencesPlan.referenceSteps,
363155
+ bodyOperations: localizedBodyOperations
363156
+ });
363157
+ if (localizedMainDocumentCovered) {
363158
+ direct.add("body");
363159
+ tracked.add("body");
363160
+ }
363161
+ }
362292
363162
  }
362293
363163
  const slotsChanged = plans.physicalPlan.hasReferenceIdChanges || payload.familyPolicy.some((policy) => policy.family === "header-footer-slots" && policy.applyRequired);
362294
363164
  const slotPlan = slotsChanged ? buildHeaderFooterSlotPackageRewrite(context, payload.base.slots ?? [], payload.target.slots ?? [], plans.physicalPlan.refByPart) : { rewrites: [], unsafeReasons: [], operationCount: 0 };
@@ -362350,13 +363220,19 @@ function buildCompareApplyEligibility(context, payload, plans) {
362350
363220
  const trackedTablePlan = buildTrackedTableRowReplayPlan({
362351
363221
  base: mainDocument.base,
362352
363222
  target: mainDocument.target,
363223
+ baseBody: payload.base.body,
362353
363224
  targetBody: payload.target.body,
362354
- bodyOperations: payload.applyPlan.bodyOperations
363225
+ bodyOperations: payload.applyPlan.bodyOperations.length > 0 ? payload.applyPlan.bodyOperations : computeCompareBodyOperations(payload.base.body, payload.target.body, {
363226
+ base: payload.base.definitions,
363227
+ target: payload.target.definitions
363228
+ })
362355
363229
  });
362356
363230
  if (trackedTablePlan.unsafeReasons.length === 0 && trackedTablePlan.steps.length > 0) {
362357
363231
  tracked.add("tables");
362358
363232
  if (bodyDeferredToTableReplay)
362359
363233
  tracked.add("body");
363234
+ if (rowReplayCoversContentControls(payload.base.annotations, payload.target.annotations, trackedTablePlan.steps, mainDocument.base.xml, mainDocument.target.xml))
363235
+ tracked.add("content-controls");
362360
363236
  }
362361
363237
  }
362362
363238
  const packageGraphCoveredByComments = direct.has("comments") && packageGraphKeysNotCoveredByComments(context, payload.base, payload.target, plans.commentPlan.steps).length === 0;
@@ -362414,7 +363290,16 @@ function buildCompareApplyEligibility(context, payload, plans) {
362414
363290
  });
362415
363291
  }
362416
363292
  for (const mode of ["direct", "tracked"]) {
362417
- const runtimePlan = buildHeaderFooterRuntimeApplyPlan(payload, slotPlan, plans.physicalPlan, mode === "tracked");
363293
+ let runtimePlan = buildHeaderFooterRuntimeApplyPlan(payload, slotPlan, plans.physicalPlan, mode === "tracked");
363294
+ if (plans.sectionsReferencesPlan.unsafeReasons.length === 0 && (plans.sectionsReferencesPlan.sectionSteps.length > 0 || plans.sectionsReferencesPlan.referenceSteps.length > 0)) {
363295
+ runtimePlan = {
363296
+ ...runtimePlan,
363297
+ applyPlan: {
363298
+ ...runtimePlan.applyPlan,
363299
+ bodyOperations: buildLocalizedBodyOperations(payload.base.body, payload.target.body, plans.sectionsReferencesPlan.sectionSteps, plans.sectionsReferencesPlan.referenceSteps, { base: payload.base.definitions, target: payload.target.definitions }, payload.applyPlan.bodyOperations)
363300
+ }
363301
+ };
363302
+ }
362418
363303
  const unsupportedFamilies = findStructurallyUnsupportedOperationFamilies(context, runtimePlan, payload.mainDocument?.base ?? EMPTY_MAIN_DOCUMENT_SOURCE, mode === "tracked", plans.sectionsReferencesPlan.referenceSteps);
362419
363304
  if (unsupportedFamilies.length > 0) {
362420
363305
  hazards[mode].push({
@@ -362958,7 +363843,8 @@ function buildLocalizedBodyOperations(base3, target, sectionSteps, referenceStep
362958
363843
  throwLocalizedBodyCorrespondenceError(sectionSteps, referenceSteps);
362959
363844
  const neutralTarget = target.map((entry) => {
362960
363845
  const baseEntry = correspondence.baseByTargetId.get(entry.blockId);
362961
- const neutralRunXml = baseEntry?.runXml && entry.runXml ? referenceSteps.reduce((xml, step3) => reverseReferenceAttribute(xml, step3), entry.runXml) : entry.runXml;
363846
+ const reversedRunXml = baseEntry?.runXml && entry.runXml ? referenceSteps.reduce((xml, step3) => reverseReferenceAttribute(xml, step3), entry.runXml) : entry.runXml;
363847
+ const neutralRunXml = baseEntry?.runXml && reversedRunXml && stripReplayNamespaceDeclarations(reversedRunXml) === stripReplayNamespaceDeclarations(baseEntry.runXml) ? baseEntry.runXml : reversedRunXml;
362962
363848
  const neutralParagraphPropertiesXml = baseEntry?.paragraphPropertiesXml !== undefined && entry.paragraphPropertiesXml !== undefined ? sectionSteps.reduce((xml, step3) => reverseSectionProperties(xml, baseEntry.paragraphPropertiesXml, step3), entry.paragraphPropertiesXml) : entry.paragraphPropertiesXml;
362963
363849
  if (neutralRunXml === entry.runXml && neutralParagraphPropertiesXml === entry.paragraphPropertiesXml)
362964
363850
  return entry;
@@ -363025,6 +363911,17 @@ function reverseSectionProperties(targetParagraphPropertiesXml, baseParagraphPro
363025
363911
  return targetParagraphPropertiesXml.slice(0, targetStart) + step3.beforeXml + targetParagraphPropertiesXml.slice(targetStart + step3.afterXml.length);
363026
363912
  }
363027
363913
  function reverseReferenceAttribute(xml, step3) {
363914
+ if (step3.kind === "bookmark-range" || step3.kind === "complex-field") {
363915
+ if (step3.beforeFragmentXml === undefined || step3.afterFragmentXml === undefined)
363916
+ return xml;
363917
+ const normalizedXml = stripReplayNamespaceDeclarations(xml);
363918
+ const after = stripReplayNamespaceDeclarations(step3.afterFragmentXml);
363919
+ const before = stripReplayNamespaceDeclarations(step3.beforeFragmentXml);
363920
+ const start = normalizedXml.indexOf(after);
363921
+ if (start < 0 || normalizedXml.indexOf(after, start + after.length) >= 0)
363922
+ return xml;
363923
+ return normalizedXml.slice(0, start) + before + normalizedXml.slice(start + after.length);
363924
+ }
363028
363925
  const elementName = step3.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
363029
363926
  const localName2 = step3.kind === "bookmark-start" ? "name" : "instr";
363030
363927
  const afterValue = readReplayAttribute(step3.afterXml, localName2, step3.afterNamespaceBindings);
@@ -363094,7 +363991,9 @@ function validateFullDiffCanonicalArtifacts(engine, payload) {
363094
363991
  const problems = [];
363095
363992
  let canonicalAnalysis;
363096
363993
  try {
363097
- canonicalAnalysis = engine.analyzeSnapshots(payload.base, payload.target);
363994
+ const rawAnalysis = engine.analyzeSnapshots(payload.base, payload.target);
363995
+ const comparisonTarget = paragraphIdentityOnlyComparisonTarget(payload.base, payload.target, payload.mainDocument?.base ?? EMPTY_MAIN_DOCUMENT_SOURCE, payload.mainDocument?.target ?? EMPTY_MAIN_DOCUMENT_SOURCE, rawAnalysis.result);
363996
+ canonicalAnalysis = comparisonTarget ? engine.analyzeSnapshots(payload.base, comparisonTarget) : rawAnalysis;
363098
363997
  } catch (error48) {
363099
363998
  const message = error48 instanceof Error ? error48.message : String(error48);
363100
363999
  return { problems: [`payload.base/target could not be re-analyzed (${message})`] };
@@ -363114,6 +364013,25 @@ function validateFullDiffCanonicalArtifacts(engine, payload) {
363114
364013
  }
363115
364014
  return { problems };
363116
364015
  }
364016
+ function paragraphIdentityOnlyComparisonTarget(base3, target, baseDocument, targetDocument, rawAnalysis) {
364017
+ if (!baseDocument.xml || !targetDocument.xml || baseDocument.xml === targetDocument.xml)
364018
+ return null;
364019
+ const withoutParagraphIds = (xml) => xml.includes("<!DOCTYPE") ? xml : xml.replace(/<!\[CDATA\[[\s\S]*?\]\]>|<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<[A-Za-z_](?:[^>"']|"[^"]*"|'[^']*')*>/g, (token) => token.startsWith("<!") || token.startsWith("<?") ? token : token.replace(/("[^"]*"|'[^']*')|(\s+w14:paraId="[0-9A-Fa-f]{8}")/g, (part, quotedValue) => quotedValue ?? "").replace(/\s+\/>$/, "/>"));
364020
+ if (withoutParagraphIds(baseDocument.xml) !== withoutParagraphIds(targetDocument.xml))
364021
+ return null;
364022
+ if (validateMainDocumentSourceAgainstSnapshotPart(baseDocument, base3, "base").length > 0 || validateMainDocumentSourceAgainstSnapshotPart(targetDocument, target, "target").length > 0)
364023
+ return null;
364024
+ const levels = (source) => new Map(Object.entries(source.paragraphStyleOutlineLevels ?? {}));
364025
+ if (validateMainDocumentSourceAgainstBodySnapshot(baseDocument, base3.body, { paragraphStyleOutlineLevels: levels(baseDocument) }).length > 0 || validateMainDocumentSourceAgainstBodySnapshot(targetDocument, target.body, { paragraphStyleOutlineLevels: levels(targetDocument) }).length > 0)
364026
+ return null;
364027
+ const otherParts = (snapshot2) => snapshot2.sourceInventory?.parts.filter((part) => part.partUri !== DOCUMENT_PART_URI6);
364028
+ if (!base3.sourceInventory || !target.sourceInventory || !artifactEquals(otherParts(base3), otherParts(target)))
364029
+ return null;
364030
+ const changedFamilies3 = rawAnalysis.families.filter((family) => family.state === "changed-supported");
364031
+ if (!changedFamilies3.some((family) => family.family === "tables") || changedFamilies3.some((family) => family.family !== "body" && family.family !== "tables"))
364032
+ return null;
364033
+ return { ...base3, identity: target.identity };
364034
+ }
363117
364035
  function artifactEquals(left, right) {
363118
364036
  return JSON.stringify(normalizeArtifactForCompare(left)) === JSON.stringify(normalizeArtifactForCompare(right));
363119
364037
  }
@@ -363293,8 +364211,14 @@ function containsStructuralParagraphTag(xml, blockKind) {
363293
364211
  return errors4.length === 0 ? found : null;
363294
364212
  }
363295
364213
  function findUniqueReplayCarrier(xml, step3) {
364214
+ if (step3.kind === "bookmark-range" || step3.kind === "complex-field") {
364215
+ const fragment3 = step3.afterFragmentXml;
364216
+ if (!fragment3)
364217
+ return null;
364218
+ const start = xml.indexOf(fragment3);
364219
+ return start >= 0 && xml.indexOf(fragment3, start + fragment3.length) < 0 ? { start, end: start + fragment3.length } : null;
364220
+ }
363296
364221
  const expectedName = step3.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
363297
- const localName2 = step3.kind === "bookmark-start" ? "name" : "instr";
363298
364222
  const parsed = collectXmlElementRanges(xml, {
363299
364223
  fragment: true,
363300
364224
  additionalNamespaces: { ...step3.beforeNamespaceBindings }
@@ -377122,13 +378046,7 @@ function normalizeParagraphBlock(value2, path7, version2, allowHeading) {
377122
378046
  return normalized;
377123
378047
  inlines.push(normalized);
377124
378048
  }
377125
- const projected = inlines.flatMap((inline) => {
377126
- if (inline.kind === "text")
377127
- return [inline];
377128
- if (inline.kind === "hyperlink")
377129
- return inline.inlines.filter((child) => child.kind === "text");
377130
- return [];
377131
- });
378049
+ const projected = inlines.flatMap(projectInlineText);
377132
378050
  if (projected.length !== runs.length || projected.some((inline, runIndex) => inline.text !== runs[runIndex]?.text || JSON.stringify(inline.marks ?? {}) !== JSON.stringify(runs[runIndex]?.marks ?? {}))) {
377133
378051
  return structureFailure("Paragraph runs must equal the text projection of inlines.", path7);
377134
378052
  }
@@ -377153,6 +378071,12 @@ function normalizeParagraphBlock(value2, path7, version2, allowHeading) {
377153
378071
  const paragraphProps = normalizeParagraphProperties(value2.paragraphProps, [...path7, "paragraphProps"]);
377154
378072
  if (isPlanFailure(paragraphProps))
377155
378073
  return paragraphProps;
378074
+ const blockSdts = normalizeBlockSdts(value2.blockSdts, [...path7, "blockSdts"]);
378075
+ if (isPlanFailure(blockSdts))
378076
+ return blockSdts;
378077
+ if (blockSdts && (version2 !== SUPERDOC_V22_CLIPBOARD_FRAGMENT_VERSION || value2.complete !== true)) {
378078
+ return structureFailure("Complete block content controls require a v2.2 paragraph.", [...path7, "blockSdts"]);
378079
+ }
377156
378080
  const sourcePath = normalizeSourcePath(value2.sourcePath, [...path7, "sourcePath"]);
377157
378081
  if (isPlanFailure(sourcePath))
377158
378082
  return sourcePath;
@@ -377167,16 +378091,83 @@ function normalizeParagraphBlock(value2, path7, version2, allowHeading) {
377167
378091
  ...typeof continuation === "number" ? { listItemContinuationLevel: continuation } : {},
377168
378092
  ...typeof value2.pPrXml === "string" && value2.pPrXml.length > 0 ? { pPrXml: value2.pPrXml } : {},
377169
378093
  ...value2.complete === true ? { complete: true } : {},
378094
+ ...blockSdts ? { blockSdts } : {},
377170
378095
  ...sourcePath ? { sourcePath } : {}
377171
378096
  };
377172
378097
  return isHeading ? { kind: "heading", level: value2.level, ...common } : { kind: "paragraph", ...common };
377173
378098
  }
377174
- function normalizeInline(value2, path7, allowHyperlink) {
378099
+ function normalizeBlockSdts(value2, path7) {
378100
+ if (value2 === undefined)
378101
+ return;
378102
+ if (!Array.isArray(value2) || value2.length === 0 || value2.length > 16) {
378103
+ return structureFailure("Block content-control ancestry must contain 1 to 16 wrappers.", path7);
378104
+ }
378105
+ const ids = new Set;
378106
+ const result = [];
378107
+ for (const [index2, wrapper] of value2.entries()) {
378108
+ if (!isRecord15(wrapper) || typeof wrapper.sourceId !== "string" || !/^[0-9]{1,10}$/.test(wrapper.sourceId) || ids.has(wrapper.sourceId) || wrapper.controlType !== "text" && wrapper.controlType !== "richText" || !validSdtMetadata(wrapper) || wrapper.tag !== undefined && (typeof wrapper.tag !== "string" || wrapper.tag.length > 255 || !isXml10SafeAttributeValue(wrapper.tag)) || wrapper.alias !== undefined && (typeof wrapper.alias !== "string" || wrapper.alias.length > 255 || !isXml10SafeAttributeValue(wrapper.alias))) {
378109
+ return structureFailure("Invalid block content-control wrapper.", [...path7, index2]);
378110
+ }
378111
+ ids.add(wrapper.sourceId);
378112
+ result.push({
378113
+ sourceId: wrapper.sourceId,
378114
+ controlType: wrapper.controlType,
378115
+ ...wrapper.tag !== undefined ? { tag: wrapper.tag } : {},
378116
+ ...wrapper.alias !== undefined ? { alias: wrapper.alias } : {},
378117
+ ...wrapper.lockMode !== undefined ? { lockMode: wrapper.lockMode } : {},
378118
+ ...wrapper.multiline !== undefined ? { multiline: wrapper.multiline } : {}
378119
+ });
378120
+ }
378121
+ return result;
378122
+ }
378123
+ function validSdtMetadata(value2) {
378124
+ return (value2.lockMode === undefined || value2.lockMode === "unlocked" || value2.lockMode === "sdtLocked" || value2.lockMode === "contentLocked" || value2.lockMode === "sdtContentLocked") && (value2.multiline === undefined || value2.controlType === "text" && typeof value2.multiline === "boolean");
378125
+ }
378126
+ function projectInlineText(inline) {
378127
+ if (inline.kind === "text")
378128
+ return [inline];
378129
+ if (inline.kind === "sdt" || inline.kind === "hyperlink")
378130
+ return inline.inlines.flatMap(projectInlineText);
378131
+ return [];
378132
+ }
378133
+ function normalizeInline(value2, path7, allowHyperlink, depth = 0) {
377175
378134
  if (!isRecord15(value2))
377176
378135
  return structureFailure("Paste inlines must be objects.", path7);
378136
+ if (depth > 32)
378137
+ return structureFailure("Paste inline nesting exceeds the allowed depth.", path7);
377177
378138
  const sourcePath = normalizeSourcePath(value2.sourcePath, [...path7, "sourcePath"]);
377178
378139
  if (isPlanFailure(sourcePath))
377179
378140
  return sourcePath;
378141
+ if (value2.kind === "sdt") {
378142
+ if (value2.controlType !== "text" && value2.controlType !== "richText" || !validSdtMetadata(value2) || !Array.isArray(value2.inlines) || value2.inlines.length === 0) {
378143
+ return structureFailure("Content controls require a supported type and nonempty inlines.", path7);
378144
+ }
378145
+ for (const key2 of ["tag", "alias"]) {
378146
+ if (value2[key2] !== undefined && (typeof value2[key2] !== "string" || value2[key2].length > 255 || !isXml10SafeAttributeValue(value2[key2]))) {
378147
+ return structureFailure(`Content control ${key2} must be XML-safe and at most 255 characters.`, [...path7, key2]);
378148
+ }
378149
+ }
378150
+ const inlines = [];
378151
+ for (const [index2, child] of value2.inlines.entries()) {
378152
+ const normalized = normalizeInline(child, [...path7, "inlines", index2], false, depth + 1);
378153
+ if (isPlanFailure(normalized))
378154
+ return normalized;
378155
+ if (normalized.kind !== "text" && normalized.kind !== "lineBreak" && normalized.kind !== "sdt") {
378156
+ return structureFailure("Unsupported content inside a content control.", [...path7, "inlines", index2]);
378157
+ }
378158
+ inlines.push(normalized);
378159
+ }
378160
+ return {
378161
+ kind: "sdt",
378162
+ controlType: value2.controlType,
378163
+ inlines,
378164
+ ...typeof value2.tag === "string" ? { tag: value2.tag } : {},
378165
+ ...typeof value2.alias === "string" ? { alias: value2.alias } : {},
378166
+ ...value2.lockMode !== undefined ? { lockMode: value2.lockMode } : {},
378167
+ ...value2.multiline !== undefined ? { multiline: value2.multiline } : {},
378168
+ ...sourcePath ? { sourcePath } : {}
378169
+ };
378170
+ }
377180
378171
  if (value2.kind === "text" && typeof value2.text === "string" && isXml10SafeString(value2.text)) {
377181
378172
  if (value2.marks !== undefined && (!isRecord15(value2.marks) || Array.isArray(value2.marks))) {
377182
378173
  return structureFailure("Paste run marks must be an object.", [...path7, "marks"]);
@@ -377234,8 +378225,8 @@ function normalizeInline(value2, path7, allowHyperlink) {
377234
378225
  const normalized = normalizeInline(child, [...path7, "inlines", childIndex], false);
377235
378226
  if (isPlanFailure(normalized))
377236
378227
  return normalized;
377237
- if (normalized.kind === "hyperlink")
377238
- return structureFailure("Nested hyperlinks are not supported.", [...path7, "inlines", childIndex]);
378228
+ if (normalized.kind === "hyperlink" || normalized.kind === "sdt")
378229
+ return structureFailure("Nested hyperlinks and content controls are not supported.", [...path7, "inlines", childIndex]);
377239
378230
  if (normalized.kind === "footnoteRef" || normalized.kind === "endnoteRef") {
377240
378231
  return structureFailure("Note references cannot be nested inside hyperlinks.", [...path7, "inlines", childIndex]);
377241
378232
  }
@@ -377680,6 +378671,9 @@ function normalizeTableBlock(block, index2, version2) {
377680
378671
  const paragraph4 = normalizeParagraphBlock(cellBlock, ["blocks", index2, "rows", rowIndex, "cells", cellIndex, "blocks", cellBlockIndex], version2, false);
377681
378672
  if (isPlanFailure(paragraph4))
377682
378673
  return paragraph4;
378674
+ if (paragraph4.blockSdts?.length) {
378675
+ return structureFailure("Block content controls inside table cells are not supported.", ["blocks", index2, "rows", rowIndex, "cells", cellIndex, "blocks", cellBlockIndex, "blockSdts"]);
378676
+ }
377683
378677
  cellBlocks.push(paragraph4);
377684
378678
  }
377685
378679
  cells.push({
@@ -378437,7 +379431,8 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378437
379431
  code: "PRECONDITION_FAILED",
378438
379432
  message: "story part bytes are unavailable."
378439
379433
  };
378440
- const boundaryMap = buildStructuralBoundaryMap(bytes.subarray(0, endEntry.byteEnd));
379434
+ const boundaryMap = buildStructuralBoundaryMap(bytes);
379435
+ const blockSdtWrappers = scanBlockSdtWrappers(SERIALIZE_TD.decode(bytes));
378441
379436
  const hyperlinkDescriptorResolver = new SelectionHyperlinkDescriptorResolver(context, resolved.storyId);
378442
379437
  let tableCatalog;
378443
379438
  const notes = new SerializeNotes;
@@ -378500,6 +379495,7 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378500
379495
  loadCatalog: loadCommentsCatalog
378501
379496
  };
378502
379497
  const tableCellPath = boundaryMap.byBlockId(entry.blockId)?.tableCellPath ?? [];
379498
+ const blockSdtPath = boundaryMap.byBlockId(entry.blockId)?.blockSdtPath ?? [];
378503
379499
  const properties = readParagraphProperties(paragraphXml);
378504
379500
  const fullyCovered = from === 0 && to === map3.visibleLength && (options?.wholeParagraphs === true || startEntry.ordinal !== endEntry.ordinal || properties.styleRef !== undefined || properties.numbering !== undefined || hasParagraphProperties(paragraphXml));
378505
379501
  const styleModel = copyStyleModel ?? (copyStyleModel = compileCopyStyleModel(context));
@@ -378508,7 +379504,23 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378508
379504
  const semanticHeadingLevel = semanticStyleRole?.startsWith("heading-") ? Number(semanticStyleRole.slice("heading-".length)) : null;
378509
379505
  const parts = [];
378510
379506
  let objectOrdinal = 0;
378511
- for (const piece of map3.leaves()) {
379507
+ for (const piece of copySelectionPieces(map3.pieces)) {
379508
+ if (piece.kind === "container" && piece.containerKind === "sdt") {
379509
+ const sdt = from <= piece.visibleStart && piece.visibleEnd <= to ? serializeSdtPart(piece, from, to, notes, styleContext, review) : null;
379510
+ if (sdt) {
379511
+ parts.push(sdt);
379512
+ continue;
379513
+ }
379514
+ for (const leaf of copySelectionPieces(piece.children, false)) {
379515
+ if (leaf.visibleStart >= to || leaf.visibleEnd <= from)
379516
+ continue;
379517
+ notes.add("fields", "Content-control and field structure is not preserved by copy; visible text was kept.");
379518
+ const leafParts = [];
379519
+ walkPieces([leaf], from, to, leafParts, notes, styleContext, review);
379520
+ parts.push(...leafParts);
379521
+ }
379522
+ continue;
379523
+ }
378512
379524
  if (isVisibleZeroWidthObjectPiece(piece)) {
378513
379525
  const selected = fullyCovered || selectedObjectOrdinals?.has(objectOrdinal);
378514
379526
  objectOrdinal += 1;
@@ -378588,8 +379600,18 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378588
379600
  }
378589
379601
  const pPrXml = fullyCovered ? materializeEffectivePPrXml(capturedPPrXml, properties.styleRef, copyParagraphPropertyModel ?? (copyParagraphPropertyModel = compileCopyParagraphPropertyXmlModel(context)), inTable ? "table" : "full", resolveRunMarks(extractParagraphMarkRPrXml(capturedPPrXml), styleContext), numbering != null) : null;
378590
379602
  const listLevel = numbering ? Number.parseInt(numbering.ilvl, 10) : undefined;
379603
+ const blockSdts = blockSdtPath.map((ordinal) => {
379604
+ const wrapper = blockSdtWrappers.get(ordinal);
379605
+ if (!wrapper || !fullyCovered || tableCellPath.length > 0 || !wrapper.supported || index2.entries.some((candidate2) => boundaryMap.byBlockId(candidate2.blockId)?.blockSdtPath.includes(ordinal) && (candidate2.ordinal < startEntry.ordinal || candidate2.ordinal > endEntry.ordinal || candidate2.ordinal === startEntry.ordinal && startPoint.offset !== 0 || candidate2.ordinal === endEntry.ordinal && endPoint.offset !== parseParagraphInlineMap(SERIALIZE_TD.decode(bytes.subarray(endEntry.byteStart, endEntry.byteEnd))).visibleLength)))
379606
+ return null;
379607
+ return wrapper.properties;
379608
+ });
379609
+ if (blockSdtPath.length > 0 && blockSdts.some((wrapper) => wrapper === null)) {
379610
+ notes.add("block-sdt", "A partial or unsupported block content control was copied as visible text without its wrapper.");
379611
+ }
378591
379612
  blocks.push({
378592
379613
  blockId: entry.blockId,
379614
+ ...blockSdts.length > 0 && blockSdts.every((wrapper) => wrapper !== null) ? { blockSdts } : {},
378593
379615
  runs: parts.flatMap(serializedPartRuns),
378594
379616
  ...fullyCovered && properties.styleRef ? { styleRef: properties.styleRef } : {},
378595
379617
  ...fullyCovered && semanticStyleRole ? { semanticStyleRole } : {},
@@ -378639,14 +379661,16 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378639
379661
  })), notes.diagnostics());
378640
379662
  plan = buildPlan(fallbackFragment, "plain-text", notes.diagnostics(), true);
378641
379663
  } else {
378642
- const fragment3 = withPasteComments(buildFragment(grouped.map(fragmentBlockFor), notes.diagnostics()), commentsById);
379664
+ const hasPreservedControls = blocks.some((block) => !!block.blockSdts?.length || block.parts.some((part) => part.kind === "sdt"));
379665
+ const legacyDiagnostics = hasPreservedControls ? [...notes.diagnostics(), diagnostic("paste-no-faithful-representation", "The v2.1 clipboard flavor retains visible text but not content-control structure; use the v2.2 flavor to preserve controls.", "superdoc-v2-fragment", "warning")] : notes.diagnostics();
379666
+ const fragment3 = withPasteComments(buildFragment(grouped.map(fragmentBlockFor), legacyDiagnostics), commentsById);
378643
379667
  items.push({
378644
379668
  type: SUPERDOC_V2_CLIPBOARD_MIME,
378645
379669
  kind: "string",
378646
379670
  data: JSON.stringify(fragment3)
378647
379671
  });
378648
379672
  const assets = [...rich.assetsByPartUri.values()];
378649
- const hasRichInlineStructure = blocks.some((block) => block.parts.some((part) => part.kind === "hyperlink" || part.kind === "image"));
379673
+ const hasRichInlineStructure = blocks.some((block) => !!block.blockSdts?.length || block.parts.some((part) => part.kind === "hyperlink" || part.kind === "image" || part.kind === "sdt"));
378650
379674
  if (!rich.invalid && hasRichInlineStructure) {
378651
379675
  const richBlocks = grouped.map(richFragmentBlockFor);
378652
379676
  const wireFragment = withPasteComments({
@@ -378670,6 +379694,84 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378670
379694
  }
378671
379695
  };
378672
379696
  }
379697
+ function topLevelChildrenCoverXml(xml, children2) {
379698
+ let cursor = 0;
379699
+ for (const child of children2) {
379700
+ const start = xml.indexOf(child.xml, cursor);
379701
+ if (start < 0 || xml.slice(cursor, start).trim().length > 0)
379702
+ return false;
379703
+ cursor = start + child.xml.length;
379704
+ }
379705
+ return xml.slice(cursor).trim().length === 0;
379706
+ }
379707
+ function parseSupportedSdtProperties(xml) {
379708
+ const children2 = parseTopLevelChildren(xml);
379709
+ if (!topLevelChildrenCoverXml(xml, children2))
379710
+ return null;
379711
+ const properties = { controlType: "richText" };
379712
+ let seenControlType = false;
379713
+ for (const child of children2) {
379714
+ if (child.prefix !== "w")
379715
+ return null;
379716
+ if (child.localName === "id") {
379717
+ if (!/^<w:id\b[^>]*\/>$/.test(child.xml))
379718
+ return null;
379719
+ } else if (child.localName === "tag" || child.localName === "alias") {
379720
+ if (!/^<w:(?:tag|alias)\b[^>]*\/>$/.test(child.xml))
379721
+ return null;
379722
+ const value2 = readBareAttribute(child.xml, "w:val");
379723
+ if (value2 === null || value2.length > 255)
379724
+ return null;
379725
+ properties[child.localName] = value2;
379726
+ } else if (child.localName === "lock") {
379727
+ const match = /^<w:lock\s+w:val=(['"])(unlocked|sdtLocked|contentLocked|sdtContentLocked)\1\s*\/>$/.exec(child.xml);
379728
+ if (!match || properties.lockMode !== undefined)
379729
+ return null;
379730
+ properties.lockMode = match[2];
379731
+ } else if (child.localName === "text") {
379732
+ const match = /^<w:text(?:\s+w:multiLine=(['"])(1|0|true|false|on|off)\1)?\s*\/>$/.exec(child.xml);
379733
+ if (!match || seenControlType)
379734
+ return null;
379735
+ seenControlType = true;
379736
+ properties.controlType = "text";
379737
+ if (match[2] !== undefined)
379738
+ properties.multiline = match[2] === "1" || match[2] === "true" || match[2] === "on";
379739
+ } else if (child.localName === "richText") {
379740
+ if (!/^<w:richText\s*\/>$/.test(child.xml) || seenControlType)
379741
+ return null;
379742
+ seenControlType = true;
379743
+ } else {
379744
+ return null;
379745
+ }
379746
+ }
379747
+ return properties;
379748
+ }
379749
+ function scanBlockSdtWrappers(xml) {
379750
+ const wrappers = new Map;
379751
+ const stack = [];
379752
+ const tokens = /<w:p\b[^>]*>[\s\S]*?<\/w:p\s*>|<w:sdt\b[^>]*>|<\/w:sdt\s*>/g;
379753
+ let ordinal = 0;
379754
+ for (const token of xml.matchAll(tokens)) {
379755
+ if (token[0].startsWith("<w:p"))
379756
+ continue;
379757
+ if (token[0].startsWith("</")) {
379758
+ const frame = stack.pop();
379759
+ if (!frame)
379760
+ continue;
379761
+ const wrapperXml = xml.slice(frame.start, token.index + token[0].length);
379762
+ const propertiesXml = /<w:sdtPr\b[^>]*>([\s\S]*?)<\/w:sdtPr>/.exec(wrapperXml)?.[1];
379763
+ const contentXml = /<w:sdtContent\b[^>]*>([\s\S]*)<\/w:sdtContent>/.exec(wrapperXml)?.[1];
379764
+ const children2 = contentXml === undefined ? [] : parseTopLevelChildren(contentXml);
379765
+ const parsedProperties = propertiesXml === undefined ? null : parseSupportedSdtProperties(propertiesXml);
379766
+ const properties = { sourceId: String(frame.ordinal), ...parsedProperties ?? { controlType: "richText" } };
379767
+ const supported = parsedProperties !== null && contentXml !== undefined && children2.length > 0 && children2.every((child) => child.prefix === "w" && (child.localName === "p" || child.localName === "sdt")) && topLevelChildrenCoverXml(contentXml, children2);
379768
+ wrappers.set(frame.ordinal, { properties, supported });
379769
+ } else {
379770
+ stack.push({ ordinal: ordinal++, start: token.index });
379771
+ }
379772
+ }
379773
+ return wrappers;
379774
+ }
378673
379775
  function serializeSelectedDrawing(input) {
378674
379776
  const fail6 = (key2, message) => {
378675
379777
  input.notes.add(key2, message);
@@ -378842,7 +379944,7 @@ function appendSerializedParts(out, parts, hyperlink) {
378842
379944
  function serializedPartRuns(part) {
378843
379945
  if (part.kind === "run")
378844
379946
  return [part.run];
378845
- if (part.kind === "hyperlink")
379947
+ if (part.kind === "hyperlink" || part.kind === "sdt")
378846
379948
  return part.parts.flatMap(serializedPartRuns);
378847
379949
  return [];
378848
379950
  }
@@ -378868,6 +379970,48 @@ function noteContainingStructures(piece, notes, preservesHyperlink) {
378868
379970
  }
378869
379971
  return supported;
378870
379972
  }
379973
+ function copySelectionPieces(pieces, stopAtSdt = true) {
379974
+ return pieces.flatMap((piece) => {
379975
+ if (piece.kind === "container" && piece.containerKind === "sdt" && stopAtSdt)
379976
+ return [piece];
379977
+ if (piece.kind === "container" || piece.kind === "tracked") {
379978
+ if (piece.kind === "container" && piece.policy === "atomic")
379979
+ return [piece];
379980
+ return copySelectionPieces(piece.children, stopAtSdt);
379981
+ }
379982
+ return [piece];
379983
+ });
379984
+ }
379985
+ function serializeSdtPart(piece, from, to, notes, styleContext, review) {
379986
+ const propertiesXml = /<w:sdtPr\b[^>]*>([\s\S]*?)<\/w:sdtPr>/.exec(piece.rawXml)?.[1];
379987
+ if (propertiesXml === undefined)
379988
+ return null;
379989
+ const properties = parseSupportedSdtProperties(propertiesXml);
379990
+ if (!properties)
379991
+ return null;
379992
+ const parts = [];
379993
+ for (const child of piece.children) {
379994
+ if (child.kind === "container" && child.containerKind === "sdt") {
379995
+ if (child.visibleStart < from || child.visibleEnd > to)
379996
+ return null;
379997
+ const nested = serializeSdtPart(child, from, to, notes, styleContext, review);
379998
+ if (!nested)
379999
+ return null;
380000
+ parts.push(nested);
380001
+ } else if (child.kind === "text" || child.kind === "control") {
380002
+ if (child.kind === "text" && child.textRole !== "visible")
380003
+ return null;
380004
+ const leaves = [];
380005
+ walkPieces([child], from, to, leaves, notes, styleContext, review);
380006
+ parts.push(...leaves);
380007
+ } else {
380008
+ return null;
380009
+ }
380010
+ }
380011
+ if (!parts.length)
380012
+ return null;
380013
+ return { kind: "sdt", ...properties, parts };
380014
+ }
378871
380015
 
378872
380016
  class SerializeNotes {
378873
380017
  constructor() {
@@ -379479,10 +380623,34 @@ function paragraphFragmentFor(block) {
379479
380623
  function richParagraphFragmentFor(block) {
379480
380624
  return {
379481
380625
  ...paragraphFragmentFor(block),
380626
+ ...block.blockSdts ? { blockSdts: block.blockSdts } : {},
379482
380627
  inlines: block.parts.flatMap(serializedPartInlines)
379483
380628
  };
379484
380629
  }
380630
+ function serializedSdtChildInline(part) {
380631
+ if (part.kind === "sdt")
380632
+ return {
380633
+ kind: "sdt",
380634
+ controlType: part.controlType,
380635
+ ...part.tag !== undefined ? { tag: part.tag } : {},
380636
+ ...part.alias !== undefined ? { alias: part.alias } : {},
380637
+ ...part.lockMode !== undefined ? { lockMode: part.lockMode } : {},
380638
+ ...part.multiline !== undefined ? { multiline: part.multiline } : {},
380639
+ inlines: part.parts.map(serializedSdtChildInline)
380640
+ };
380641
+ return serializedLeafPartInline(part);
380642
+ }
379485
380643
  function serializedPartInlines(part) {
380644
+ if (part.kind === "sdt")
380645
+ return [{
380646
+ kind: "sdt",
380647
+ controlType: part.controlType,
380648
+ ...part.tag !== undefined ? { tag: part.tag } : {},
380649
+ ...part.alias !== undefined ? { alias: part.alias } : {},
380650
+ ...part.lockMode !== undefined ? { lockMode: part.lockMode } : {},
380651
+ ...part.multiline !== undefined ? { multiline: part.multiline } : {},
380652
+ inlines: part.parts.map(serializedSdtChildInline)
380653
+ }];
379486
380654
  if (part.kind !== "hyperlink")
379487
380655
  return [serializedLeafPartInline(part)];
379488
380656
  return [
@@ -379540,7 +380708,7 @@ function serializedPartPlainText(part) {
379540
380708
  `;
379541
380709
  if (part.kind === "run")
379542
380710
  return part.run.text;
379543
- if (part.kind === "hyperlink")
380711
+ if (part.kind === "hyperlink" || part.kind === "sdt")
379544
380712
  return part.parts.map(serializedPartPlainText).join("");
379545
380713
  return "";
379546
380714
  }
@@ -379564,6 +380732,8 @@ function renderHtmlPart(part) {
379564
380732
  return renderHtmlRun(part.run);
379565
380733
  if (part.kind === "image")
379566
380734
  return "";
380735
+ if (part.kind === "sdt")
380736
+ return part.parts.map(renderHtmlPart).join("");
379567
380737
  const href = part.target.kind === "external" ? part.target.url : `#${part.target.anchor}`;
379568
380738
  const title = part.tooltip ? ` title="${escapeHtmlAttribute3(part.tooltip)}"` : "";
379569
380739
  return `<a href="${escapeHtmlAttribute3(href)}"${title}>${part.parts.map(renderHtmlPart).join("")}</a>`;