@superdoc/mcp 1.2.0-next.11 → 1.2.0-next.13

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 +1295 -188
  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.11";
31317
+ var SDK_VERSION = "2.15.0-next.13";
31318
31318
 
31319
31319
  // ../../packages/sdk/langs/node/src/runtime/embedded-platform.ts
31320
31320
  import { chmodSync, existsSync } from "node:fs";
@@ -188376,6 +188376,8 @@ function renderPasteParagraphBlock(spec) {
188376
188376
  return `<w:p${nsDecl} w14:paraId="${escapeXmlAttribute(spec.paraId)}">${pPr}${renderPasteRuns(spec.runs)}</w:p>`;
188377
188377
  }
188378
188378
  function renderReviewAwarePasteRun(run, stamp) {
188379
+ if (run.kind === "sdt")
188380
+ return renderPasteSdt(run, stamp);
188379
188381
  let xml = renderPasteRun(run);
188380
188382
  if (!("text" in run))
188381
188383
  return xml;
@@ -188516,6 +188518,11 @@ function renderTrackedPasteRuns(runs, stamp) {
188516
188518
  out.push(wrapTrackedInsertion(inner, stamp));
188517
188519
  };
188518
188520
  for (const run of runs) {
188521
+ if (run.kind === "sdt") {
188522
+ flushOrdinary();
188523
+ out.push(wrapTrackedInsertion(renderPasteSdt(run), stamp));
188524
+ continue;
188525
+ }
188519
188526
  if (run.kind !== "hyperlink") {
188520
188527
  if (run.kind === "footnoteRef" || run.kind === "endnoteRef") {
188521
188528
  flushOrdinary();
@@ -188581,6 +188588,8 @@ function renderPasteTableCell(cell, renderParagraph = renderPasteParagraphBlock)
188581
188588
  return `<w:tc>${tcPr}${cell.paragraphs.map(renderParagraph).join("")}</w:tc>`;
188582
188589
  }
188583
188590
  function renderPasteRun(run, options = {}) {
188591
+ if (run.kind === "sdt")
188592
+ return renderPasteSdt(run);
188584
188593
  if (run.kind === "image")
188585
188594
  return run.xml;
188586
188595
  if (run.kind === "hyperlink")
@@ -188611,6 +188620,32 @@ function renderPasteRun(run, options = {}) {
188611
188620
  }
188612
188621
  return out.join("");
188613
188622
  }
188623
+ function renderPasteSdt(run, stamp) {
188624
+ 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}/>`);
188625
+ return `<w:sdt><w:sdtPr>${properties}</w:sdtPr><w:sdtContent>${renderPasteRuns(run.runs, stamp)}</w:sdtContent></w:sdt>`;
188626
+ }
188627
+ function renderBlockSdtSequence(blocks, xml, ids) {
188628
+ let result = "";
188629
+ let previous = [];
188630
+ for (let index = 0;index <= blocks.length; index += 1) {
188631
+ const block = blocks[index];
188632
+ const current = block && "blockSdts" in block ? block.blockSdts ?? [] : [];
188633
+ let shared = 0;
188634
+ while (shared < previous.length && shared < current.length && previous[shared].sourceId === current[shared].sourceId)
188635
+ shared += 1;
188636
+ for (let depth = previous.length - 1;depth >= shared; depth -= 1)
188637
+ result += "</w:sdtContent></w:sdt>";
188638
+ for (let depth = shared;depth < current.length; depth += 1) {
188639
+ const wrapper = current[depth];
188640
+ const id = ids.get(wrapper.sourceId);
188641
+ 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>";
188642
+ }
188643
+ if (index < blocks.length)
188644
+ result += xml[index];
188645
+ previous = current;
188646
+ }
188647
+ return result;
188648
+ }
188614
188649
  function renderPasteHyperlink(run) {
188615
188650
  const targetAttribute = run.target.kind === "external" ? `r:id="${escapeXmlAttribute(run.target.relationshipId ?? "")}"` : `w:anchor="${escapeXmlAttribute(run.target.anchor)}"`;
188616
188651
  const tooltipAttribute = run.tooltip ? ` w:tooltip="${escapeXmlAttribute(run.tooltip)}"` : "";
@@ -189891,6 +189926,18 @@ function compileParsedOverlapEdit(input, tokens, descendedEditableContainers) {
189891
189926
  });
189892
189927
  token._classification = input.existingRevisionOwnership === "foreign" ? "different-user" : classifyOwnership(current, wrapperAuthor);
189893
189928
  }
189929
+ const commented = wrappers.filter((entry) => entry.token.kind === "ins" && entry.token.commentedBoundary != null);
189930
+ if (commented.length > 0) {
189931
+ const parent = commented[0].token;
189932
+ const following = touched.filter((entry) => entry.token.kind === "plain");
189933
+ 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) {
189934
+ return {
189935
+ ok: false,
189936
+ reason: "text-range-in-unsupported-tracked-wrapper",
189937
+ detail: "overlap compiler: comment-bearing insertion requires a bounded foreign spanning replacement."
189938
+ };
189939
+ }
189940
+ }
189894
189941
  if (input.newText.length > 0 && input.rangeStart < input.rangeEnd) {
189895
189942
  const spanningReplacement = tryRangeReplacementAcrossTokens(input, tokens, touched);
189896
189943
  if (spanningReplacement)
@@ -190531,8 +190578,21 @@ function authorTrackedPureDelete(input, allTokens, touched, touchedBreaks = [])
190531
190578
  author: currentAuthor,
190532
190579
  date: input.txTimestamp,
190533
190580
  rsidDel: input.rsid,
190534
- inner: { runOpen: token.inner.runOpen, runClose: token.inner.runClose, rPr: token.inner.rPr, text: removed }
190581
+ inner: { runOpen: token.inner.runOpen, runClose: token.inner.runClose, rPr: token.inner.rPr, text: removed },
190582
+ ...token.commentedBoundary ? {
190583
+ innerXml: renderDelRun(removed, token.inner.runOpen, token.inner.runClose, token.inner.rPr) + token.commentedBoundary.endXml + token.commentedBoundary.referenceRunXml
190584
+ } : {}
190535
190585
  });
190586
+ if (token.commentedBoundary) {
190587
+ if (!childIns || tail.length > 0)
190588
+ return null;
190589
+ pieces.push(token.commentedBoundary.startXml + token.openTag + nestedHead + childDel + token.closeTag + childIns);
190590
+ replacementInserted = true;
190591
+ if (!emittedRsids.includes(input.insRsid))
190592
+ emittedRsids.push(input.insRsid);
190593
+ emitDeletionRsid();
190594
+ continue;
190595
+ }
190536
190596
  if (childIns) {
190537
190597
  const headIns = head.length > 0 ? buildInsWrapperXml({
190538
190598
  openTag: token.openTag,
@@ -191893,6 +191953,55 @@ function isSupportedStructuralDeletionChild(piece) {
191893
191953
  }
191894
191954
  return piece.containerKind === "unknown" && (piece.localName === "smartTag" || piece.localName === "dir" || piece.localName === "bdo");
191895
191955
  }
191956
+ function readCommentedInsertionBoundary(piece) {
191957
+ const markers = piece.children.filter((child) => child.kind === "marker");
191958
+ if (markers.length !== 3 || piece.children.length !== 4)
191959
+ return null;
191960
+ const [start, end, reference] = markers;
191961
+ 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)
191962
+ return null;
191963
+ const text = piece.children.find((child) => child.kind === "text");
191964
+ if (!text || !text.decodedText)
191965
+ return null;
191966
+ const source = piece.rawXml;
191967
+ const base2 = piece.charStart;
191968
+ const openEnd = findXmlTagEnd(source, 1);
191969
+ const closeStart = source.lastIndexOf(`</${piece.qname}>`);
191970
+ const outsideBindings = Object.fromEntries(piece.namespaceBindings);
191971
+ for (const attribute of scanRawXmlAttributes(source.slice(0, openEnd))) {
191972
+ if (attribute.name === "xmlns" || attribute.prefix === "xmlns") {
191973
+ delete outsideBindings[attribute.name === "xmlns" ? "" : attribute.localName];
191974
+ }
191975
+ }
191976
+ const startXml = closeNamespaceScope(start.rawXml, Object.fromEntries(start.namespaceBindings), outsideBindings, { referencedOnly: true });
191977
+ if (startXml == null)
191978
+ return null;
191979
+ const textRunStart = text.runCharStart - base2;
191980
+ const textRunEnd = text.runCharEnd - base2;
191981
+ const startStart = start.charStart - base2;
191982
+ const startEnd = start.charEnd - base2;
191983
+ const endStart = end.charStart - base2;
191984
+ const endEnd = end.charEnd - base2;
191985
+ if (openEnd < 0 || closeStart < 0 || startStart < openEnd || startEnd > textRunStart || textRunEnd > endStart || endEnd > reference.charStart - base2 || [
191986
+ source.slice(openEnd, startStart),
191987
+ source.slice(startEnd, textRunStart),
191988
+ source.slice(textRunEnd, endStart)
191989
+ ].some((gap) => gap.trim().length > 0))
191990
+ return null;
191991
+ const textRun = source.slice(textRunStart, textRunEnd);
191992
+ if (textRun.replace(text.runOpenTag, "").replace(text.runProperties ?? "", "").replace(text.rawXml, "").replace(text.runCloseTag, "").trim().length > 0)
191993
+ return null;
191994
+ const referenceRunXml = source.slice(endEnd, closeStart).trim();
191995
+ const referenceOpenEnd = findXmlTagEnd(referenceRunXml, 1);
191996
+ const referenceCloseStart = referenceRunXml.lastIndexOf("</");
191997
+ if (referenceOpenEnd < 0 || referenceCloseStart < referenceOpenEnd || !/^<([\w.-]+:)?r(?=[\s>])/.test(referenceRunXml) || !/<\/[\w.-]+:r>$/.test(referenceRunXml) || referenceRunXml.slice(referenceOpenEnd, referenceCloseStart).trim() !== reference.rawXml)
191998
+ return null;
191999
+ return {
192000
+ startXml,
192001
+ endXml: end.rawXml,
192002
+ referenceRunXml
192003
+ };
192004
+ }
191896
192005
  function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStructuralDeletionPassThrough) {
191897
192006
  const visibleStart = spanStart(piece);
191898
192007
  const visibleEnd = spanEnd(piece);
@@ -191932,6 +192041,7 @@ function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStr
191932
192041
  rawXml: child.rawXml
191933
192042
  })).sort((a, b) => a.localOffset - b.localOffset);
191934
192043
  }
192044
+ const commentedBoundary = piece.trackedKind === "ins" && !nestedDeletions ? readCommentedInsertionBoundary(piece) : null;
191935
192045
  if (allowComplexDeletions && piece.trackedKind === "del") {
191936
192046
  const unsupportedChild = piece.children.find((child) => child.kind !== "text" && !(child.kind === "marker" && isCommentMarker2(child.markerKind)) && !(allowStructuralDeletionPassThrough && isSupportedStructuralDeletionChild(child)));
191937
192047
  if (unsupportedChild) {
@@ -191994,7 +192104,7 @@ function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStr
191994
192104
  }
191995
192105
  };
191996
192106
  }
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");
192107
+ 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
192108
  if (foreignChild) {
191999
192109
  if (foreignChild.kind === "container") {
192000
192110
  return unsupported2(containerRejectionReason(foreignChild.containerKind), `overlap compiler: ${foreignChild.containerKind} inside w:${piece.trackedKind}`);
@@ -192059,7 +192169,13 @@ function trackedToken(piece, spanStart, spanEnd, allowComplexDeletions, allowStr
192059
192169
  },
192060
192170
  ...innerRuns.length > 1 ? { innerRuns } : {}
192061
192171
  };
192062
- return piece.trackedKind === "ins" ? { kind: "ins", ...base2, rsidR, ...nestedDeletions ? { nestedDeletions } : {} } : { kind: "del", ...base2, rsidDel };
192172
+ return piece.trackedKind === "ins" ? {
192173
+ kind: "ins",
192174
+ ...base2,
192175
+ rsidR,
192176
+ ...nestedDeletions ? { nestedDeletions } : {},
192177
+ ...commentedBoundary ? { commentedBoundary } : {}
192178
+ } : { kind: "del", ...base2, rsidDel };
192063
192179
  }
192064
192180
  function isCommentMarker2(markerKind) {
192065
192181
  return markerKind === "commentRangeStart" || markerKind === "commentRangeEnd" || markerKind === "commentReference";
@@ -192110,6 +192226,7 @@ var init_overlap_edit_compiler = __esm(() => {
192110
192226
  init_word_text_payload();
192111
192227
  init_xml_text();
192112
192228
  init_xml_sax();
192229
+ init_xml_namespace_scope();
192113
192230
  init_word_text_names();
192114
192231
  init_paragraph_inline_model();
192115
192232
  init_comment_cascade();
@@ -206730,6 +206847,8 @@ function collectRetiredTrackedChangeCommentMetadataIds(op, logicalCatalog) {
206730
206847
  return ids;
206731
206848
  }
206732
206849
  function applyRetiredTrackedChangeCommentMetadataCleanup(args) {
206850
+ if (args.perPart[COMMENTS_PART_URI]?.kind === "remove")
206851
+ return [];
206733
206852
  const commentsBytes = args.state.packageBytes.get(COMMENTS_PART_URI);
206734
206853
  if (!commentsBytes || args.retiredSourceIds.size === 0)
206735
206854
  return [];
@@ -206831,16 +206950,45 @@ function applyCommentCascade(args) {
206831
206950
  const next = removeCommentCatalogEntries(TD46.decode(commentsBytes), wids);
206832
206951
  if (!next.ok)
206833
206952
  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: [] };
206953
+ const parsedComments = collectXmlElementRanges(next.xml);
206954
+ const commentsRoot = parsedComments.ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "comments");
206955
+ const emptyComments = parsedComments.errors.length === 0 && commentsRoot && next.xml.slice(commentsRoot.openEnd, commentsRoot.closeStart).trim().length === 0;
206956
+ const hasCommentSidecars = args.state.packageBytes.has(COMMENTS_EXTENDED_PART_URI) || args.state.packageBytes.has(COMMENTS_IDS_PART_URI);
206957
+ if (emptyComments && !hasCommentSidecars) {
206958
+ args.perPart[COMMENTS_PART_URI] = { kind: "remove" };
206959
+ args.preconditions.push(byteWindowPrecondition4(COMMENTS_PART_URI, commentsBytes));
206960
+ args.patchMaps[COMMENTS_PART_URI] = { partUri: COMMENTS_PART_URI, ranges: [] };
206961
+ const relsBytes = args.state.packageBytes.get(DOCUMENT_RELS_PART_URI3);
206962
+ if (relsBytes) {
206963
+ const relsXml = currentPartXml(args.state, args.perPart, DOCUMENT_RELS_PART_URI3, XML_TD) ?? XML_TD.decode(relsBytes);
206964
+ const removed = removeRelationshipForPartAndTypeXml({
206965
+ relsXml,
206966
+ ownerPartUri: DOCUMENT_PART_URI,
206967
+ partUri: COMMENTS_PART_URI,
206968
+ relationshipType: COMMENTS_RELATIONSHIP_TYPE
206969
+ });
206970
+ if (!removed.ok)
206971
+ return { ok: false, reason: "unsupported-context" };
206972
+ if (removed.value.xml !== relsXml) {
206973
+ setWholePartReplacement(args, DOCUMENT_RELS_PART_URI3, relsBytes, removed.value.xml);
206974
+ }
206975
+ }
206976
+ const contentTypesBytes = args.state.packageBytes.get(CONTENT_TYPES_PART_URI4);
206977
+ if (contentTypesBytes) {
206978
+ const contentTypesXml = currentPartXml(args.state, args.perPart, CONTENT_TYPES_PART_URI4, XML_TD) ?? XML_TD.decode(contentTypesBytes);
206979
+ const removed = removeContentTypeOverrideXml({
206980
+ contentTypesXml,
206981
+ partName: COMMENTS_PART_URI
206982
+ });
206983
+ if (!removed.ok)
206984
+ return { ok: false, reason: "unsupported-context" };
206985
+ if (removed.value.xml !== contentTypesXml) {
206986
+ setWholePartReplacement(args, CONTENT_TYPES_PART_URI4, contentTypesBytes, removed.value.xml);
206987
+ }
206988
+ }
206989
+ } else {
206990
+ setWholePartReplacement(args, COMMENTS_PART_URI, commentsBytes, next.xml);
206991
+ }
206844
206992
  }
206845
206993
  const extendedBytes = args.state.packageBytes.get(COMMENTS_EXTENDED_PART_URI);
206846
206994
  if (extendedBytes && removedParaIds.size > 0) {
@@ -209038,7 +209186,7 @@ function dedupeRemappedRefs(refs) {
209038
209186
  function refKey(ref3) {
209039
209187
  return `${ref3.kind}|${ref3.entityId ?? ref3.commentId ?? ""}|${ref3.storyId ?? ""}`;
209040
209188
  }
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";
209189
+ 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
209190
  var init_compile_tracked_change_decide = __esm(() => {
209043
209191
  init_empty_paragraph_move();
209044
209192
  init_native_sdt_ownership();
@@ -220127,6 +220275,18 @@ function collectProtectedReferenceTokens(xml, referenceSteps) {
220127
220275
  const tokens = [];
220128
220276
  for (let index = 0;index < referenceSteps.length; index += 1) {
220129
220277
  const step2 = referenceSteps[index];
220278
+ if (step2.kind === "bookmark-range" || step2.kind === "complex-field") {
220279
+ const fragment2 = step2.beforeFragmentXml;
220280
+ if (!fragment2)
220281
+ return null;
220282
+ const start = xml.indexOf(fragment2);
220283
+ if (start >= 0) {
220284
+ if (xml.indexOf(fragment2, start + fragment2.length) >= 0)
220285
+ return null;
220286
+ tokens.push({ key: `reference:${index}`, start, end: start + fragment2.length, xml: fragment2 });
220287
+ }
220288
+ continue;
220289
+ }
220130
220290
  const expectedLocalName = step2.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
220131
220291
  const parsed = collectXmlElementRanges(xml, {
220132
220292
  fragment: true,
@@ -221281,7 +221441,7 @@ function validateLocalizedMainDocumentReplay(state, sectionSteps, referenceSteps
221281
221441
  function uniqueLocalizedReplayStart(xml, step2, documentRanges, allowRootNamespaceDeclarations = false) {
221282
221442
  const ranges = documentRanges ?? collectXmlElementRanges(xml).ranges;
221283
221443
  if (allowRootNamespaceDeclarations && isReferenceReplayStep(step2)) {
221284
- const expectedLocalName = step2.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
221444
+ const expectedLocalName = referenceReplayElementName(step2);
221285
221445
  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
221446
  return starts2.length === 1 ? starts2[0] : null;
221287
221447
  }
@@ -221302,7 +221462,7 @@ function isInsideWordElement(ranges, position, localName2) {
221302
221462
  return ranges.some((range) => range.tag.namespaceUri === NS_W && range.tag.localName === localName2 && range.start < position && range.end > position);
221303
221463
  }
221304
221464
  function localizedReplayContext(xml, step2, start, documentRanges, allowRootNamespaceDeclarations = false) {
221305
- const expectedLocalName = isReferenceReplayStep(step2) ? step2.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple" : "sectPr";
221465
+ const expectedLocalName = isReferenceReplayStep(step2) ? referenceReplayElementName(step2) : "sectPr";
221306
221466
  const ranges = documentRanges ?? collectXmlElementRanges(xml).ranges;
221307
221467
  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
221468
  if (!before)
@@ -221336,8 +221496,55 @@ function isSafeReferenceReplayStep(step2, context) {
221336
221496
  const afterName = readResolvedReplayAttribute(context.after, "name");
221337
221497
  return afterName !== null && readResolvedReplayAttribute(context.before, "id") === readResolvedReplayAttribute(context.after, "id") && replaceReplayAttribute(step2.beforeXml, context.before, "name", afterName) === step2.afterXml;
221338
221498
  }
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;
221499
+ if (step2.kind === "simple-field") {
221500
+ const instruction = readResolvedReplayAttribute(context.after, "instr");
221501
+ 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;
221502
+ }
221503
+ const beforeFragment = step2.beforeFragmentXml;
221504
+ const afterFragment = step2.afterFragmentXml;
221505
+ if (!beforeFragment || !afterFragment || replaceUniqueReplayFragment(step2.beforeXml, beforeFragment, afterFragment) !== step2.afterXml)
221506
+ return false;
221507
+ if (step2.kind === "bookmark-range") {
221508
+ return isBalancedAddedBookmarkFragment(afterFragment, beforeFragment, context.parentNamespaceBindings);
221509
+ }
221510
+ return isCompleteAddedRefFieldFragment(afterFragment, beforeFragment, context.parentNamespaceBindings);
221511
+ }
221512
+ function referenceReplayElementName(step2) {
221513
+ return step2.kind === "bookmark-start" ? "bookmarkStart" : step2.kind === "simple-field" ? "fldSimple" : "p";
221514
+ }
221515
+ function replaceUniqueReplayFragment(xml, before, after) {
221516
+ const start = xml.indexOf(before);
221517
+ if (start < 0 || xml.indexOf(before, start + before.length) >= 0)
221518
+ return null;
221519
+ return xml.slice(0, start) + after + xml.slice(start + before.length);
221520
+ }
221521
+ function isBalancedAddedBookmarkFragment(xml, expectedContent, namespaceBindings) {
221522
+ const parsed = collectXmlElementRanges(xml, { fragment: true, additionalNamespaces: { ...namespaceBindings } });
221523
+ if (parsed.errors.length > 0)
221524
+ return false;
221525
+ const starts = parsed.ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "bookmarkStart");
221526
+ const ends = parsed.ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "bookmarkEnd");
221527
+ if (starts.length !== 1 || ends.length !== 1 || !starts[0].tag.isSelfClosing || !ends[0].tag.isSelfClosing)
221528
+ return false;
221529
+ const id = readResolvedReplayAttribute(starts[0], "id");
221530
+ return id !== null && readResolvedReplayAttribute(starts[0], "name") !== null && readResolvedReplayAttribute(ends[0], "id") === id && xml.slice(starts[0].end, ends[0].start) === expectedContent;
221531
+ }
221532
+ function isCompleteAddedRefFieldFragment(xml, expectedResult, namespaceBindings) {
221533
+ const parsed = collectXmlElementRanges(xml, { fragment: true, additionalNamespaces: { ...namespaceBindings } });
221534
+ if (parsed.errors.length > 0)
221535
+ return false;
221536
+ const chars = parsed.ranges.filter((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "fldChar");
221537
+ const types4 = chars.map((range) => readResolvedReplayAttribute(range, "fldCharType"));
221538
+ if (types4.length !== 3 || types4.join(",") !== "begin,separate,end")
221539
+ return false;
221540
+ 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("");
221541
+ 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;
221542
+ }
221543
+ function parentRunStart(ranges, child) {
221544
+ return ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "r" && range.start <= child.start && range.end >= child.end)?.start ?? -1;
221545
+ }
221546
+ function parentRunEnd(ranges, child) {
221547
+ return ranges.find((range) => range.tag.namespaceUri === NS_W && range.tag.localName === "r" && range.start <= child.start && range.end >= child.end)?.end ?? -1;
221341
221548
  }
221342
221549
  function isReferenceReplayStep(step2) {
221343
221550
  return "kind" in step2;
@@ -221455,6 +221662,13 @@ function renderTrackedSectionReplay(step2, stamp, context) {
221455
221662
  return step2.afterXml.slice(0, close) + marker + step2.afterXml.slice(close);
221456
221663
  }
221457
221664
  function renderTrackedReferenceReplay(step2, stamps, context) {
221665
+ if (step2.kind === "bookmark-range" || step2.kind === "complex-field") {
221666
+ const beforeFragment = step2.beforeFragmentXml;
221667
+ const afterFragment = step2.afterFragmentXml;
221668
+ const deletionXml2 = renameWordTextElementsToDeletedText(beforeFragment, context.parentNamespaceBindings);
221669
+ const tracked = renderTrackedWrapper("del", deletionXml2, stamps.del, context) + renderTrackedWrapper("ins", afterFragment, stamps.ins, context);
221670
+ return replaceUniqueReplayFragment(step2.beforeXml, beforeFragment, tracked) ?? step2.beforeXml;
221671
+ }
221458
221672
  const deletionXml = step2.kind === "simple-field" ? renameWordTextElementsToDeletedText(step2.beforeXml, context.parentNamespaceBindings) : step2.beforeXml;
221459
221673
  return renderTrackedWrapper("del", deletionXml, stamps.del, context) + renderTrackedWrapper("ins", step2.afterXml, stamps.ins, context);
221460
221674
  }
@@ -221544,7 +221758,7 @@ function validateTableTopologySteps(state, changeMode, steps) {
221544
221758
  if (deletedRows.has(key2) || step2.rowsXml.length !== 1 || table.rows.length <= 1)
221545
221759
  return false;
221546
221760
  const rowXml = TD58.decode(partBytes.subarray(anchor.row.rowByteStart, anchor.row.rowByteEnd));
221547
- if (step2.rowsXml[0] !== rowXml || !isSafeExactTableRowPayload(rowXml, namespaceBindings))
221761
+ if (step2.rowsXml[0] !== rowXml || !isNamespaceSafeWordprocessingRow(rowXml, namespaceBindings))
221548
221762
  return false;
221549
221763
  const parsed = collectXmlElementRanges(rowXml, { fragment: true, additionalNamespaces: { ...namespaceBindings } });
221550
221764
  if (parsed.errors.length || parsed.ranges.some((range) => range.tag.namespaceUri === NS_W && [
@@ -221555,7 +221769,6 @@ function validateTableTopologySteps(state, changeMode, steps) {
221555
221769
  "commentReference",
221556
221770
  "footnoteReference",
221557
221771
  "endnoteReference",
221558
- "sdt",
221559
221772
  "fldSimple",
221560
221773
  "fldChar"
221561
221774
  ].includes(range.tag.localName)))
@@ -228918,7 +229131,7 @@ function planParagraphSplice(input) {
228918
229131
  };
228919
229132
  return input.trackedStamp ? renderTrackedPasteParagraphBlock(spec, input.trackedStamp) : renderPasteParagraphBlock(spec);
228920
229133
  };
228921
- const mergeable = (block) => block.kind === "paragraph" && !block.styleRef && !block.numbering && !block.listIntent && !block.paragraphProps && !block.pPrXml && !block.complete;
229134
+ const mergeable = (block) => block.kind === "paragraph" && !block.styleRef && !block.numbering && !block.listIntent && !block.paragraphProps && !block.pPrXml && !block.blockSdts?.length && !block.complete;
228922
229135
  const paragraphs = [];
228923
229136
  if (blocks.length === 1 && mergeable(blocks[0])) {
228924
229137
  if (anchorParaId)
@@ -228937,10 +229150,13 @@ function planParagraphSplice(input) {
228937
229150
  paragraphs.push(headOpen + headPPr + split.headXml + (mergeFirst ? runsOf(blocks[0]) : "") + envelope.close);
228938
229151
  }
228939
229152
  const middleStart = mergeFirst ? 1 : 0;
229153
+ const middleXml = [];
228940
229154
  for (let middleIndex = 0;middleIndex < middleBlocks.length; middleIndex += 1) {
228941
229155
  const block = middleBlocks[middleIndex];
228942
- paragraphs.push(standalone(block, middleStart + middleIndex, mergeable(block) ? envelope.pPr : undefined));
229156
+ middleXml.push(standalone(block, middleStart + middleIndex, mergeable(block) ? envelope.pPr : undefined));
228943
229157
  }
229158
+ if (middleXml.length)
229159
+ paragraphs.push(renderBlockSdtSequence(middleBlocks, middleXml, input.blockSdtIds ?? new Map));
228944
229160
  const tailRunsXml = (mergeLast ? runsOf(blocks[blocks.length - 1]) : "") + split.tailXml;
228945
229161
  if (mergeLast || split.tailXml.length > 0) {
228946
229162
  const tailOpen = relocateEmptyAnchor ? envelope.open : withParaId(envelope.open, mintId(), input.partBytes, input.paragraphStartByte);
@@ -231243,6 +231459,10 @@ function preflightFragmentApply(input) {
231243
231459
  const target = resolveFragmentApplyTarget(state, op);
231244
231460
  if (!target.ok)
231245
231461
  return target;
231462
+ if (isTextTarget2(op.target) && op.formattingMode === "match-destination" && op.fragment.blocks.some((block) => (block.kind === "paragraph" || block.kind === "heading") && block.blockSdts?.length))
231463
+ return { ok: false, reason: "paste-structure-unsupported" };
231464
+ if ((op.changeMode ?? "direct") === "tracked" && op.fragment.blocks.some((block) => (block.kind === "paragraph" || block.kind === "heading") && block.blockSdts?.length))
231465
+ return { ok: false, reason: "paste-tracked-structural-unsupported" };
231246
231466
  if ((op.changeMode ?? "direct") === "tracked" && target.target.kind === "block" && target.target.blockKind === "table" && !trackedWholeTableRowsAreDirectChildren(state, target.target)) {
231247
231467
  return { ok: false, reason: "paste-tracked-structural-unsupported" };
231248
231468
  }
@@ -231507,6 +231727,8 @@ function validateFragmentRangeIntegrity(input) {
231507
231727
  }
231508
231728
  function fragmentRequiresCommit5Materialization(blocks) {
231509
231729
  for (const block of blocks) {
231730
+ if ((block.kind === "paragraph" || block.kind === "heading") && block.blockSdts?.length)
231731
+ return true;
231510
231732
  if (block.kind === "heading" || block.kind === "horizontalRule")
231511
231733
  return true;
231512
231734
  if (block.kind === "table") {
@@ -231526,7 +231748,7 @@ function fragmentRequiresCommit5Materialization(blocks) {
231526
231748
  return false;
231527
231749
  }
231528
231750
  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);
231751
+ 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
231752
  }
231531
231753
  function collectFragmentSourcePaths(blocks) {
231532
231754
  const paths = [];
@@ -232037,6 +232259,7 @@ function runWholeStoryReplaceCommit(input, target, blocks, extras, inlineExtras)
232037
232259
  state,
232038
232260
  blocks,
232039
232261
  inlineSpecs: inlineExtras.inlineSpecs,
232262
+ blockSdtIds: inlineExtras.blockSdtIds,
232040
232263
  partBytes: bytes,
232041
232264
  insertionByte: target.byteStart,
232042
232265
  ordinalSeed: 0,
@@ -232275,7 +232498,7 @@ function renderCompletePasteFragment(input) {
232275
232498
  renderedBlocks.push(xml);
232276
232499
  }
232277
232500
  return {
232278
- xml: renderedBlocks.join(""),
232501
+ xml: renderBlockSdtSequence(input.blocks, renderedBlocks, input.blockSdtIds),
232279
232502
  createdParaIds,
232280
232503
  destinationParaIdsByBlock
232281
232504
  };
@@ -232307,6 +232530,7 @@ function runParagraphBlockPasteCommit(input, target, blocks, extras, inlineExtra
232307
232530
  state,
232308
232531
  blocks,
232309
232532
  inlineSpecs: inlineExtras.inlineSpecs,
232533
+ blockSdtIds: inlineExtras.blockSdtIds,
232310
232534
  partBytes: bytes,
232311
232535
  insertionByte: target.placement === "after" ? anchor.byteEnd : anchor.byteStart,
232312
232536
  ordinalSeed: anchor.ordinal,
@@ -232475,6 +232699,7 @@ function runWholeTableReplaceCommit(input, target, blocks, extras, inlineExtras)
232475
232699
  state,
232476
232700
  blocks,
232477
232701
  inlineSpecs: inlineExtras.inlineSpecs,
232702
+ blockSdtIds: inlineExtras.blockSdtIds,
232478
232703
  partBytes: bytes,
232479
232704
  insertionByte: target.byteStart,
232480
232705
  ordinalSeed: replacedParagraphs[0]?.ordinal ?? 0,
@@ -232644,6 +232869,7 @@ function runCrossParagraphTrackedPasteCommit(input, target, blocks, extras, inli
232644
232869
  paragraphStartByte: start.byteStart,
232645
232870
  mintParaId: () => mintParaId(state, state.packageRevision, start.ordinal + 1 + (mintSalt += 1)),
232646
232871
  inlineSpecs: inlineExtras.inlineSpecs,
232872
+ blockSdtIds: inlineExtras.blockSdtIds,
232647
232873
  trackedStamp: stamp,
232648
232874
  wrapAllTracked: true,
232649
232875
  ...extras.commentIdMap ? { commentIdMap: extras.commentIdMap } : {},
@@ -232772,6 +232998,7 @@ function runCrossParagraphPasteCommit(input, target, blocks, extras, inlineExtra
232772
232998
  paragraphStartByte: start.byteStart,
232773
232999
  mintParaId: () => mintParaId(state, state.packageRevision, start.ordinal + 1 + (mintSalt += 1)),
232774
233000
  inlineSpecs: inlineExtras.inlineSpecs,
233001
+ blockSdtIds: inlineExtras.blockSdtIds,
232775
233002
  ...extras.commentIdMap ? { commentIdMap: extras.commentIdMap } : {},
232776
233003
  ...extras.noteIdMap ? { noteIdMap: extras.noteIdMap } : {}
232777
233004
  });
@@ -233418,6 +233645,7 @@ function runPasteSpliceCommit(input, target, resolvedBlocks, listExtras, imageEx
233418
233645
  paragraphStartByte,
233419
233646
  mintParaId: mintParaId3,
233420
233647
  inlineSpecs: imageExtras.inlineSpecs,
233648
+ blockSdtIds: imageExtras.blockSdtIds,
233421
233649
  ...stamp ? { trackedStamp: stamp, wrapAllTracked } : {},
233422
233650
  ...listExtras.commentIdMap ? { commentIdMap: listExtras.commentIdMap } : {},
233423
233651
  ...listExtras.noteIdMap ? { noteIdMap: listExtras.noteIdMap } : {}
@@ -233654,6 +233882,8 @@ function validateImageFragment(fragment2) {
233654
233882
  continue;
233655
233883
  if (inline.kind === "footnoteRef" || inline.kind === "endnoteRef")
233656
233884
  continue;
233885
+ if (inline.kind === "sdt")
233886
+ continue;
233657
233887
  if (inline.kind === "hyperlink") {
233658
233888
  if (inline.inlines.some((child) => child.kind === "image")) {
233659
233889
  return { ok: false, reason: "paste-structure-unsupported" };
@@ -233674,7 +233904,7 @@ function validateImageFragment(fragment2) {
233674
233904
  function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMode, relationshipHyperlinks, hyperlinkCharacterStyleId, preparedPackageXml) {
233675
233905
  const inlineParagraphs = [];
233676
233906
  for (const block of blocks) {
233677
- if (block.kind === "paragraph" && block.inlines)
233907
+ if (block.kind === "paragraph" && (block.inlines || block.blockSdts?.length))
233678
233908
  inlineParagraphs.push(block);
233679
233909
  if (block.kind === "table") {
233680
233910
  for (const row of block.rows) {
@@ -233687,7 +233917,7 @@ function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMo
233687
233917
  }
233688
233918
  }
233689
233919
  if (inlineParagraphs.length === 0) {
233690
- return { ok: true, value: { inlineSpecs: new Map, partUris: [], perPart: {}, preconditions: [], patchMaps: {} } };
233920
+ return { ok: true, value: { inlineSpecs: new Map, blockSdtIds: new Map, partUris: [], perPart: {}, preconditions: [], patchMaps: {} } };
233691
233921
  }
233692
233922
  const assets = new Map((fragment2.assets ?? []).map((asset) => [asset.id, asset]));
233693
233923
  const hasImages = inlineParagraphs.some((paragraph) => paragraph.inlines?.some((inline) => inline.kind === "image"));
@@ -233782,6 +234012,43 @@ function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMo
233782
234012
  }
233783
234013
  let docPrId = allocateDocPrId(state, storyId);
233784
234014
  const inlineSpecs = new Map;
234015
+ const hasControls = inlineParagraphs.some((paragraph) => paragraph.blockSdts?.length || paragraph.inlines?.some((inline) => inline.kind === "sdt"));
234016
+ const usedControlIds = new Set;
234017
+ if (hasControls) {
234018
+ for (const [uri, bytes] of state.packageBytes) {
234019
+ if (!uri.endsWith(".xml"))
234020
+ continue;
234021
+ const xml = TD64.decode(bytes);
234022
+ for (const match of xml.matchAll(/<w:sdtPr\b[^>]*>[\s\S]*?<w:id\b[^>]*\bw:val=(['"])(\d+)\1/g)) {
234023
+ const id = Number(match[2]);
234024
+ if (Number.isSafeInteger(id) && id > 0)
234025
+ usedControlIds.add(id);
234026
+ }
234027
+ }
234028
+ }
234029
+ let nextControlId = 1002;
234030
+ for (const id of usedControlIds)
234031
+ if (id >= nextControlId)
234032
+ nextControlId = id + 1;
234033
+ const mintControlId = () => {
234034
+ if (nextControlId > 2147483647)
234035
+ nextControlId = 1;
234036
+ while (usedControlIds.has(nextControlId)) {
234037
+ nextControlId += 1;
234038
+ if (nextControlId > 2147483647)
234039
+ nextControlId = 1;
234040
+ }
234041
+ const id = nextControlId++;
234042
+ usedControlIds.add(id);
234043
+ return String(id);
234044
+ };
234045
+ const blockSdtIds = new Map;
234046
+ for (const paragraph of inlineParagraphs) {
234047
+ for (const wrapper of paragraph.blockSdts ?? []) {
234048
+ if (!blockSdtIds.has(wrapper.sourceId))
234049
+ blockSdtIds.set(wrapper.sourceId, mintControlId());
234050
+ }
234051
+ }
233785
234052
  const leafSpec = (inline) => {
233786
234053
  if (inline.kind === "text")
233787
234054
  return toPasteTextRunSpec(inline);
@@ -233802,37 +234069,68 @@ function prepareImageExtras(state, storyId, partUri, fragment2, blocks, changeMo
233802
234069
  })
233803
234070
  };
233804
234071
  };
234072
+ const sdtSpec = (inline) => ({
234073
+ kind: "sdt",
234074
+ id: mintControlId(),
234075
+ controlType: inline.controlType,
234076
+ ...inline.tag !== undefined ? { tag: inline.tag } : {},
234077
+ ...inline.alias !== undefined ? { alias: inline.alias } : {},
234078
+ ...inline.lockMode !== undefined ? { lockMode: inline.lockMode } : {},
234079
+ ...inline.multiline !== undefined ? { multiline: inline.multiline } : {},
234080
+ runs: inline.inlines.map((child) => child.kind === "sdt" ? sdtSpec(child) : leafSpec(child))
234081
+ });
234082
+ const inlineSpec = (inline) => {
234083
+ if (inline.kind === "sdt")
234084
+ return sdtSpec(inline);
234085
+ if (inline.kind === "footnoteRef" || inline.kind === "endnoteRef") {
234086
+ return { kind: inline.kind, noteId: inline.noteId };
234087
+ }
234088
+ if (inline.kind !== "hyperlink")
234089
+ return leafSpec(inline);
234090
+ const target = inline.target.kind === "external" ? {
234091
+ kind: "external",
234092
+ url: inline.target.url,
234093
+ ...relationshipHyperlinks ? { relationshipId: hyperlinkRelationshipIds.get(inline) } : {}
234094
+ } : { kind: "anchor", anchor: inline.target.anchor };
234095
+ return {
234096
+ kind: "hyperlink",
234097
+ target,
234098
+ ...inline.tooltip ? { tooltip: inline.tooltip } : {},
234099
+ ...hyperlinkCharacterStyleId ? { characterStyleId: hyperlinkCharacterStyleId } : {},
234100
+ runs: inline.inlines.map(leafSpec)
234101
+ };
234102
+ };
233805
234103
  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
- }));
234104
+ if (paragraph.inlines)
234105
+ inlineSpecs.set(paragraph, paragraph.inlines.map(inlineSpec));
233825
234106
  }
233826
234107
  const preconditions = [];
233827
234108
  if (relsUri && relsBytes && (hasImages || hasExternalLinks))
233828
234109
  preconditions.push(byteWindowPrecondition7(relsUri, relsBytes));
233829
234110
  if (contentTypesChanged && contentTypesBytes)
233830
234111
  preconditions.push(byteWindowPrecondition7(CONTENT_TYPES_PART_URI11, contentTypesBytes));
233831
- return { ok: true, value: { inlineSpecs, partUris: packagePartUris, perPart, preconditions, patchMaps: {} } };
234112
+ return { ok: true, value: { inlineSpecs, blockSdtIds, partUris: packagePartUris, perPart, preconditions, patchMaps: {} } };
233832
234113
  }
233833
234114
  function validateBlocks(blocks) {
233834
234115
  const budget = { totalChars: 0, hyperlinkRefs: 0, sourcePaths: new Set };
234116
+ const closedControls = new Set;
234117
+ let activeControls = [];
233835
234118
  for (const block of blocks) {
234119
+ const controls = block.kind === "paragraph" || block.kind === "heading" ? block.blockSdts ?? [] : [];
234120
+ if (controls.length && (block.kind !== "paragraph" && block.kind !== "heading" || !block.complete || controls.length > 16)) {
234121
+ return { ok: false, reason: "paste-structure-unsupported" };
234122
+ }
234123
+ let common = 0;
234124
+ 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)
234125
+ common += 1;
234126
+ for (const wrapper of activeControls.slice(common))
234127
+ closedControls.add(wrapper.sourceId);
234128
+ for (const wrapper of controls.slice(common)) {
234129
+ 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)) {
234130
+ return { ok: false, reason: "paste-structure-unsupported" };
234131
+ }
234132
+ }
234133
+ activeControls = controls;
233836
234134
  if (!recordSourcePath(block.sourcePath, budget.sourcePaths)) {
233837
234135
  return { ok: false, reason: "paste-structure-unsupported" };
233838
234136
  }
@@ -233896,6 +234194,9 @@ function validateBlocks(blocks) {
233896
234194
  if (cellBlock.kind !== "paragraph") {
233897
234195
  return { ok: false, reason: "paste-structure-unsupported" };
233898
234196
  }
234197
+ if (cellBlock.blockSdts?.length) {
234198
+ return { ok: false, reason: "paste-structure-unsupported" };
234199
+ }
233899
234200
  if (!recordSourcePath(cellBlock.sourcePath, budget.sourcePaths)) {
233900
234201
  return { ok: false, reason: "paste-structure-unsupported" };
233901
234202
  }
@@ -234029,9 +234330,14 @@ function validateParagraphBlock(block, budget) {
234029
234330
  }
234030
234331
  return { ok: true };
234031
234332
  }
234032
- function validatePasteInline(inline, budget) {
234333
+ function validatePasteInline(inline, budget, depth = 0) {
234334
+ if (depth > 32)
234335
+ return false;
234033
234336
  if (!recordSourcePath(inline.sourcePath, budget.sourcePaths))
234034
234337
  return false;
234338
+ if (inline.kind === "sdt") {
234339
+ 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));
234340
+ }
234035
234341
  if (inline.kind === "text") {
234036
234342
  return typeof inline.text === "string" && !hasUnsupportedControlChar(inline.text) && isValidPasteRunMarks(inline.marks);
234037
234343
  }
@@ -234057,7 +234363,7 @@ function validatePasteInline(inline, budget) {
234057
234363
  } 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
234364
  return false;
234059
234365
  }
234060
- return inline.inlines.every((child) => validatePasteInline(child, budget));
234366
+ return inline.inlines.every((child) => validatePasteInline(child, budget, depth + 1));
234061
234367
  }
234062
234368
  function rewritePastePPrStyleRef(pPrXml, styleId) {
234063
234369
  const escaped = styleId.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -234069,7 +234375,13 @@ function rewritePastePPrStyleRef(pPrXml, styleId) {
234069
234375
  function hasMatchingInlineTextProjection(block) {
234070
234376
  if (!Array.isArray(block.inlines))
234071
234377
  return false;
234072
- const textInlines = block.inlines.flatMap((inline) => inline.kind === "text" ? [inline] : inline.kind === "hyperlink" ? inline.inlines.flatMap((child) => child.kind === "text" ? [child] : []) : []);
234378
+ const textInlines = block.inlines.flatMap(function project(inline) {
234379
+ if (inline.kind === "text")
234380
+ return [inline];
234381
+ if (inline.kind === "hyperlink" || inline.kind === "sdt")
234382
+ return inline.inlines.flatMap(project);
234383
+ return [];
234384
+ });
234073
234385
  return textInlines.length === block.runs.length && textInlines.every((inline, index) => {
234074
234386
  const run = block.runs[index];
234075
234387
  return inline.text === run.text && haveEqualRunMarks(inline.marks, run.marks);
@@ -247145,12 +247457,13 @@ function prepareHistoryReplayMutation(state, entry, direction, patchGroup) {
247145
247457
  const controlBytes = controlOwner && state.packageBytes.get(controlOwner.partUri);
247146
247458
  const controlPatch = controlOwner && patchGroup.perPart[controlOwner.partUri];
247147
247459
  const contentControlRewrite = patchGroup.source.kind === "storySurfaceRewrite" && patchGroup.source.contentControlRewrite && controlBytes && controlPatch ? prepareContentControlRewrite(controlBytes, controlPatch) : null;
247460
+ const declaredStorySurfaceTargetBlockIds = patchGroup.source.kind === "storySurfaceRewrite" ? [...new Set(patchGroup.source.targetBlockIds)] : [];
247148
247461
  const targetBlockIds2 = contentControlRewrite ? [...new Set([
247149
247462
  ...contentControlRewrite.removedBlockIds,
247150
247463
  ...contentControlRewrite.addedBlockIds,
247151
247464
  ...contentControlRewrite.contentBlockIds,
247152
247465
  ...contentControlRewrite.leadingBlockIds
247153
- ])] : patchGroup.source.kind === "compareApply" ? [...new Set(patchGroup.source.bodySteps.map((step2) => step2.blockId))] : collectPatchGroupTargetBlockIds(patchGroup);
247466
+ ])] : patchGroup.source.kind === "compareApply" ? [...new Set(patchGroup.source.bodySteps.map((step2) => step2.blockId))] : declaredStorySurfaceTargetBlockIds.length > 0 ? declaredStorySurfaceTargetBlockIds : collectPatchGroupTargetBlockIds(patchGroup);
247154
247467
  const currentBaseRevisions = {
247155
247468
  package: state.packageRevision,
247156
247469
  perPart: {}
@@ -291329,7 +291642,7 @@ class V2SelectionMutationAdapter {
291329
291642
  const buildResolutionMs = nowMs6() - buildResolutionStartedAt;
291330
291643
  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
291644
  let structuralRewrite = false;
291332
- if (options?.changeMode === "tracked" && isRewrite && replacements.length > 1) {
291645
+ if (options?.changeMode === "tracked" && replacements.length > 1) {
291333
291646
  const paragraphBytes = getSDDocumentSessionInternalPartRange(this.context.session, resolved.partUri, resolved.paragraphByteStart ?? resolved.startByte, resolved.paragraphByteEnd ?? resolved.endByte);
291334
291647
  if (paragraphBytes) {
291335
291648
  const scopedReplacements = planTrackedRewriteReplacement(selectedText, request.text, {
@@ -326296,8 +326609,8 @@ function lowerInline(inline, path7, images) {
326296
326609
  }
326297
326610
  function lowerHyperlinkChild(inline, path7, images) {
326298
326611
  const lowered = lowerInline(inline, path7, images);
326299
- if (lowered.kind === "hyperlink")
326300
- unsupported4(path7, "Nested hyperlinks are not supported.");
326612
+ if (lowered.kind === "hyperlink" || lowered.kind === "sdt")
326613
+ unsupported4(path7, "Nested hyperlinks and content controls are not supported.");
326301
326614
  if (lowered.kind === "footnoteRef" || lowered.kind === "endnoteRef") {
326302
326615
  unsupported4(path7, "Note references cannot be nested inside hyperlinks.");
326303
326616
  }
@@ -345491,40 +345804,63 @@ class V2ImagesAdapter {
345491
345804
  return trackedTarget;
345492
345805
  return this.createTrackedBodyImage(input, options, trackedTarget, decoded, requestedSize);
345493
345806
  }
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 ?? ""
345807
+ const abortCapture = Symbol("create.image.abort");
345808
+ const imageFormat = decoded.format;
345809
+ let capturedTxIds = [];
345810
+ let failure8 = null;
345811
+ let published = null;
345812
+ try {
345813
+ published = this.context.captureCommittedHistory((txIds) => {
345814
+ capturedTxIds = txIds;
345815
+ const resolved = this.resolveCreateImageTarget(input, options);
345816
+ if (!resolved.success) {
345817
+ failure8 = resolved;
345818
+ throw abortCapture;
345819
+ }
345820
+ const inserted = this.insertInline({
345821
+ storyId: resolved.storyId,
345822
+ paraId: resolved.paraId,
345823
+ offset: resolved.offset,
345824
+ binary: decoded.binary,
345825
+ format: imageFormat,
345826
+ widthPx: requestedSize.widthPx,
345827
+ heightPx: requestedSize.heightPx,
345828
+ name: input.alt ?? input.title
345829
+ });
345830
+ if (!inserted.success) {
345831
+ failure8 = createImageFailure(inserted.failure.code, inserted.failure.message);
345832
+ throw abortCapture;
345833
+ }
345834
+ let entry = this.findInsertedImageEntry(resolved.storyId, resolved.paraId, inserted.imageNodeId);
345835
+ if (entry && (input.title || input.alt)) {
345836
+ const metadata2 = this.setAltText({
345837
+ imageId: entry.publicId,
345838
+ description: input.title ?? input.alt ?? ""
345839
+ });
345840
+ if (!metadata2.success) {
345841
+ failure8 = createImageFailure(metadata2.failure.code, metadata2.failure.message);
345842
+ throw abortCapture;
345843
+ }
345844
+ entry = readBodyImageEntryById(this.context, entry.publicId) ?? entry;
345845
+ }
345846
+ return { imageNodeId: inserted.imageNodeId, entry };
345515
345847
  });
345516
- if (!metadata2.success)
345517
- return createImageFailure(metadata2.failure.code, metadata2.failure.message);
345518
- entry = readBodyImageEntryById(this.context, entry.publicId) ?? entry;
345848
+ } catch (error48) {
345849
+ rollbackCapturedHistory(this.context, capturedTxIds);
345850
+ if (error48 === abortCapture && failure8)
345851
+ return failure8;
345852
+ throw error48;
345519
345853
  }
345854
+ const txId = publishImageCreateHistory(this.context, capturedTxIds);
345520
345855
  return {
345521
345856
  success: true,
345522
- image: entry?.summary.address ?? {
345857
+ image: published?.entry?.summary.address ?? {
345523
345858
  kind: "inline",
345524
345859
  nodeType: "image",
345525
- nodeId: inserted.imageNodeId ?? "(unknown)",
345860
+ nodeId: published?.imageNodeId ?? "(unknown)",
345526
345861
  placement: "inline"
345527
- }
345862
+ },
345863
+ ...txId ? { txId } : {}
345528
345864
  };
345529
345865
  }
345530
345866
  delete(input, options = {}) {
@@ -347542,6 +347878,16 @@ function validateImageDeleteTarget(context, entry) {
347542
347878
  }
347543
347879
  return null;
347544
347880
  }
347881
+ function publishImageCreateHistory(context, txIds) {
347882
+ const uniqueTxIds = [...new Set(txIds)];
347883
+ if (uniqueTxIds.length === 0)
347884
+ return;
347885
+ const logicalGroup = { kind: "image-create", id: uniqueTxIds[0] };
347886
+ for (const txId of uniqueTxIds) {
347887
+ recordCommittedTxIdForHistory(context, txId, { logicalGroup });
347888
+ }
347889
+ return uniqueTxIds[uniqueTxIds.length - 1];
347890
+ }
347545
347891
  function createImageFailure(code2, message) {
347546
347892
  return { success: false, failure: { code: code2, message } };
347547
347893
  }
@@ -349286,6 +349632,7 @@ var init_adapter17 = __esm(() => {
349286
349632
  init_document_reader();
349287
349633
  init_section_xml();
349288
349634
  init_adapter();
349635
+ init_composite();
349289
349636
  init_stories();
349290
349637
  init_common();
349291
349638
  init_tracked_pr_change();
@@ -353085,13 +353432,14 @@ function buildCompareSourceObservation(context) {
353085
353432
  if (!bytes)
353086
353433
  continue;
353087
353434
  const xml = TD98.decode(bytes);
353435
+ const canonicalizeBookmarkIds = bookmarkIdsAreBalanced(xml);
353088
353436
  for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
353089
353437
  const name = tagAttr(match[1] ?? "", "name");
353090
353438
  if (!name || name === "_GoBack")
353091
353439
  continue;
353092
353440
  acc["fields-references"].present = true;
353093
353441
  acc["fields-references"].entityCount += 1;
353094
- acc["fields-references"].fingerprintParts.push(`bookmark:${source.partUri}:${name}:${tagAttr(match[1] ?? "", "id") ?? ""}`);
353442
+ acc["fields-references"].fingerprintParts.push(`bookmark:${source.partUri}:${name}` + (canonicalizeBookmarkIds ? "" : `:unbalanced-id:${tagAttr(match[1] ?? "", "id") ?? ""}`));
353095
353443
  }
353096
353444
  for (const match of xml.matchAll(/<[\w.-]+:permStart\b([^>]*)>/g)) {
353097
353445
  const id = tagAttr(match[1] ?? "", "id");
@@ -353133,6 +353481,24 @@ function buildCompareSourceObservation(context) {
353133
353481
  }));
353134
353482
  return { parts, familySignals };
353135
353483
  }
353484
+ function bookmarkIdsAreBalanced(xml) {
353485
+ const starts = new Map;
353486
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
353487
+ const id = tagAttr(match[1] ?? "", "id");
353488
+ if (id === null || starts.has(id))
353489
+ return false;
353490
+ starts.set(id, match.index);
353491
+ }
353492
+ const ends = new Set;
353493
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkEnd\b([^>]*)>/g)) {
353494
+ const id = tagAttr(match[1] ?? "", "id");
353495
+ const start = id === null ? undefined : starts.get(id);
353496
+ if (id === null || start === undefined || ends.has(id) || match.index < start)
353497
+ return false;
353498
+ ends.add(id);
353499
+ }
353500
+ return starts.size === ends.size;
353501
+ }
353136
353502
  function captureCompareSourceInventory(context) {
353137
353503
  return buildCompareSourceInventory(buildCompareSourceObservation(context));
353138
353504
  }
@@ -353741,6 +354107,7 @@ function projectFieldsReferences(context) {
353741
354107
  if (!bytes)
353742
354108
  continue;
353743
354109
  const xml = TD100.decode(bytes);
354110
+ const canonicalizeBookmarkIds = bookmarkIdsAreBalanced2(xml);
353744
354111
  for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
353745
354112
  const name = tagAttr3(match[1] ?? "", "name");
353746
354113
  if (!name || name === "_GoBack")
@@ -353751,7 +354118,7 @@ function projectFieldsReferences(context) {
353751
354118
  key: uniqueEntityKey(baseKey, keySeen),
353752
354119
  stableKey: `bookmark:${name}`,
353753
354120
  semanticKey: `bookmark:${name}`,
353754
- contentSignature: digest3(`bookmark:${name}#id:${id ?? ""}#part:${source.partUri}`),
354121
+ contentSignature: digest3(`bookmark:${name}#part:${source.partUri}` + (canonicalizeBookmarkIds ? "" : `#unbalanced-id:${id ?? ""}`)),
353755
354122
  ordinal: ordinal++,
353756
354123
  label: source.scopeKey === "body" ? `bookmark ${name}` : `bookmark ${name} (${source.scopeKey})`
353757
354124
  });
@@ -353759,6 +354126,24 @@ function projectFieldsReferences(context) {
353759
354126
  }
353760
354127
  return entries;
353761
354128
  }
354129
+ function bookmarkIdsAreBalanced2(xml) {
354130
+ const starts = new Map;
354131
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkStart\b([^>]*)>/g)) {
354132
+ const id = tagAttr3(match[1] ?? "", "id");
354133
+ if (id === null || starts.has(id))
354134
+ return false;
354135
+ starts.set(id, match.index);
354136
+ }
354137
+ const ends = new Set;
354138
+ for (const match of xml.matchAll(/<[\w.-]+:bookmarkEnd\b([^>]*)>/g)) {
354139
+ const id = tagAttr3(match[1] ?? "", "id");
354140
+ const start = id === null ? undefined : starts.get(id);
354141
+ if (id === null || start === undefined || ends.has(id) || match.index < start)
354142
+ return false;
354143
+ ends.add(id);
354144
+ }
354145
+ return starts.size === ends.size;
354146
+ }
353762
354147
  function projectContentControls(context) {
353763
354148
  const entries = [];
353764
354149
  let ordinal = 0;
@@ -358567,6 +358952,119 @@ var init_main_document_revision_closure = __esm(() => {
358567
358952
  });
358568
358953
 
358569
358954
  // ../../../v2/document-api-v2-adapter/dist/diff/tracked-table-row-replay.js
358955
+ function rowReplayCoversContentControls(baseAnnotations, targetAnnotations, steps, baseXml, targetXml) {
358956
+ if (steps.length === 0)
358957
+ return false;
358958
+ const base3 = baseAnnotations?.find((group) => group.family === "content-controls")?.entries;
358959
+ const target = targetAnnotations?.find((group) => group.family === "content-controls")?.entries;
358960
+ if (!base3 || !target)
358961
+ return false;
358962
+ const inserted = new Set;
358963
+ const deleted = new Set;
358964
+ for (const step3 of steps) {
358965
+ const identities = step3.kind === "insertRows" ? inserted : deleted;
358966
+ for (const rowXml of step3.rowsXml) {
358967
+ for (const match of rowXml.matchAll(/<[\w.-]+:sdtPr\b[^>]*>([\s\S]*?)<\/[\w.-]+:sdtPr>/g)) {
358968
+ const properties = match[1];
358969
+ if (/<[\w.-]+:dataBinding\b/.test(properties))
358970
+ return false;
358971
+ const tag = /<[\w.-]+:tag\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(properties)?.[1];
358972
+ const alias = /<[\w.-]+:alias\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(properties)?.[1];
358973
+ const id = /<[\w.-]+:id\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(properties)?.[1];
358974
+ const identity2 = tag ?? alias ?? id;
358975
+ const key2 = identity2 ? `sdt:${identity2}` : null;
358976
+ if (!key2 || identities.has(key2))
358977
+ return false;
358978
+ identities.add(key2);
358979
+ }
358980
+ }
358981
+ }
358982
+ const baseByKey = new Map(base3.map((entry) => [entry.key, entry]));
358983
+ const targetByKey = new Map(target.map((entry) => [entry.key, entry]));
358984
+ if (baseByKey.size !== base3.length || targetByKey.size !== target.length)
358985
+ return false;
358986
+ const survivingBaseKeys = base3.filter((entry) => targetByKey.has(entry.key)).map((entry) => entry.key);
358987
+ const survivingTargetKeys = target.filter((entry) => baseByKey.has(entry.key)).map((entry) => entry.key);
358988
+ if (survivingBaseKeys.some((key2, index2) => key2 !== survivingTargetKeys[index2]))
358989
+ return false;
358990
+ if ([...inserted, ...deleted].some((key2) => baseByKey.has(key2) && targetByKey.has(key2)))
358991
+ return false;
358992
+ const baseShapes = contentControlShapes(baseXml);
358993
+ const targetShapes = contentControlShapes(targetXml);
358994
+ if (!baseShapes || !targetShapes)
358995
+ return false;
358996
+ if (survivingBaseKeys.some((key2) => key2.startsWith("sdt:") && (!baseShapes.has(key2) || baseShapes.get(key2) !== targetShapes.get(key2))))
358997
+ return false;
358998
+ for (const entry of base3) {
358999
+ const other = targetByKey.get(entry.key);
359000
+ if (other) {
359001
+ if (entry.contentSignature !== other.contentSignature || entry.semanticKey !== other.semanticKey)
359002
+ return false;
359003
+ } else if (!deleted.has(entry.key))
359004
+ return false;
359005
+ }
359006
+ for (const entry of target) {
359007
+ if (!baseByKey.has(entry.key) && !inserted.has(entry.key))
359008
+ return false;
359009
+ }
359010
+ return [...inserted].every((key2) => targetByKey.has(key2) && !baseByKey.has(key2)) && [...deleted].every((key2) => baseByKey.has(key2) && !targetByKey.has(key2));
359011
+ }
359012
+ function contentControlShapes(xml) {
359013
+ const parsed = collectXmlElementRanges(xml);
359014
+ if (parsed.errors.length > 0)
359015
+ return null;
359016
+ const ranges = [...parsed.ranges].sort((first, second) => first.start - second.start || second.end - first.end);
359017
+ const parents = new Map;
359018
+ const paths = new Map;
359019
+ const siblingCounts = new Map;
359020
+ const stack = [];
359021
+ for (const range of ranges) {
359022
+ while (stack.length && stack[stack.length - 1].end <= range.start)
359023
+ stack.pop();
359024
+ const parent = stack[stack.length - 1] ?? null;
359025
+ if (parent && range.end > parent.end)
359026
+ return null;
359027
+ const siblingIndex = siblingCounts.get(parent) ?? 0;
359028
+ siblingCounts.set(parent, siblingIndex + 1);
359029
+ parents.set(range, parent);
359030
+ paths.set(range, `${parent ? paths.get(parent) : ""}/${range.tag.namespaceUri}:${range.tag.localName}[${siblingIndex}]`);
359031
+ stack.push(range);
359032
+ }
359033
+ const shapes = new Map;
359034
+ const identityCounts = new Map;
359035
+ for (const properties of ranges.filter((range) => range.tag.namespaceUri === WORDPROCESSINGML_NS4 && range.tag.localName === "sdtPr")) {
359036
+ const propertyXml = xml.slice(properties.start, properties.end);
359037
+ const tag = /<[^>]*:tag\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(propertyXml)?.[1];
359038
+ const alias = /<[^>]*:alias\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(propertyXml)?.[1];
359039
+ const id = /<[^>]*:id\b[^>]*\b(?:[\w.-]+:)?val="([^"]*)"/.exec(propertyXml)?.[1];
359040
+ const key2 = tag ?? alias ?? id;
359041
+ if (!key2)
359042
+ return null;
359043
+ const control = parents.get(properties);
359044
+ if (!control || control.tag.namespaceUri !== WORDPROCESSINGML_NS4 || control.tag.localName !== "sdt")
359045
+ return null;
359046
+ const parent = parents.get(control);
359047
+ const content3 = ranges.find((range) => parents.get(range) === control && range.tag.namespaceUri === WORDPROCESSINGML_NS4 && range.tag.localName === "sdtContent");
359048
+ const firstChild = content3 && ranges.find((range) => parents.get(range) === content3);
359049
+ if (!parent || !content3 || !firstChild)
359050
+ return null;
359051
+ const identity2 = `sdt:${key2}`;
359052
+ const count = (identityCounts.get(identity2) ?? 0) + 1;
359053
+ identityCounts.set(identity2, count);
359054
+ const uniqueIdentity = count === 1 ? identity2 : `${identity2}#${count}`;
359055
+ let ancestor = parent;
359056
+ let insideRow = false;
359057
+ while (ancestor) {
359058
+ if (ancestor.tag.namespaceUri === WORDPROCESSINGML_NS4 && ancestor.tag.localName === "tr") {
359059
+ insideRow = true;
359060
+ break;
359061
+ }
359062
+ ancestor = parents.get(ancestor) ?? null;
359063
+ }
359064
+ shapes.set(uniqueIdentity, `${parent.tag.namespaceUri}:${parent.tag.localName}|${firstChild.tag.namespaceUri}:${firstChild.tag.localName}|${insideRow ? "row" : paths.get(control)}`);
359065
+ }
359066
+ return shapes;
359067
+ }
358570
359068
  function buildTrackedTableRowReplayPlan(input) {
358571
359069
  if (input.base.xml === input.target.xml) {
358572
359070
  return { steps: [], bodyOperations: input.bodyOperations, unsafeReasons: [] };
@@ -358584,30 +359082,36 @@ function buildTrackedTableRowReplayPlan(input) {
358584
359082
  }
358585
359083
  const ownedBlockIds = new Set;
358586
359084
  const steps = [];
359085
+ const usedParagraphIds = new Set([
359086
+ ...input.base.xml.matchAll(/\bparaId="([0-9A-Fa-f]{8})"/g),
359087
+ ...input.target.xml.matchAll(/\bparaId="([0-9A-Fa-f]{8})"/g)
359088
+ ].map((match) => match[1].toUpperCase()));
359089
+ const baseParagraphIds = new Set([...input.base.xml.matchAll(/\bparaId="([0-9A-Fa-f]{8})"/g)].map((match) => match[1].toUpperCase()));
358587
359090
  for (let tableIndex = 0;tableIndex < baseCatalog.tables.length; tableIndex += 1) {
358588
359091
  const baseTable = baseCatalog.tables[tableIndex];
358589
359092
  const targetTable = targetCatalog.tables[tableIndex];
358590
- if (!isSimpleTable(baseTable) || !isSimpleTable(targetTable)) {
359093
+ if (!isSimpleTable(baseTable, baseBytes, baseRoot) || !isSimpleTable(targetTable, targetBytes, targetRoot)) {
358591
359094
  return unsafe(`table ${baseTable.tableNodeId} has columns, merges, nesting, or unsupported cell content`);
358592
359095
  }
358593
359096
  if (baseTable.gridColumnCount !== targetTable.gridColumnCount) {
358594
359097
  return unsafe(`table ${baseTable.tableNodeId} changes its column grid`);
358595
359098
  }
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)
359099
+ const rowKey = (row, bytes) => sliceBytes(bytes, row.rowByteStart, row.rowByteEnd).replace(/\s+w14:paraId="[0-9A-Fa-f]{8}"/g, "");
359100
+ const baseKeys = baseTable.rows.map((row) => rowKey(row, baseBytes));
359101
+ const targetKeys = targetTable.rows.map((row) => rowKey(row, targetBytes));
359102
+ const baseIsShorter = baseKeys.length <= targetKeys.length;
359103
+ 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));
359104
+ if (!matched)
359105
+ return unsafe(`table ${baseTable.tableNodeId} modifies, reorders, or ambiguously matches rows`, new Set(baseKeys).size < baseKeys.length || new Set(targetKeys).size < targetKeys.length);
359106
+ const baseIndexByTarget = new Map([...matched].map(([shortIndex, longIndex]) => baseIsShorter ? [longIndex, shortIndex] : [shortIndex, longIndex]));
359107
+ if (baseIndexByTarget.size === 0)
358605
359108
  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`);
359109
+ if (hasInconsistentRowIdentity(baseTable.rows, targetTable.rows, baseIndexByTarget)) {
359110
+ return unsafe(`table ${baseTable.tableNodeId} reassigns a row identity outside its original position`, true);
358608
359111
  }
358609
- for (const baseRow of baseTable.rows) {
358610
- const targetPosition = targetRowsById.get(baseRow.rowNodeId);
359112
+ const targetIndexByBase = new Map([...baseIndexByTarget].map(([targetIndex, baseIndex]) => [baseIndex, targetIndex]));
359113
+ for (const [baseIndex, baseRow] of baseTable.rows.entries()) {
359114
+ const targetPosition = targetIndexByBase.get(baseIndex);
358611
359115
  if (targetPosition === undefined) {
358612
359116
  const rowXml = sliceBytes(baseBytes, baseRow.rowByteStart, baseRow.rowByteEnd);
358613
359117
  if (containsWordprocessingRevisionMarkup(rowXml, baseRoot.namespaceBindings)) {
@@ -358631,26 +359135,27 @@ function buildTrackedTableRowReplayPlan(input) {
358631
359135
  }
358632
359136
  const targetRow = targetTable.rows[targetPosition];
358633
359137
  if (sliceBytes(baseBytes, baseRow.rowByteStart, baseRow.rowByteEnd) !== sliceBytes(targetBytes, targetRow.rowByteStart, targetRow.rowByteEnd)) {
358634
- return unsafe(`table ${baseTable.tableNodeId} modifies an existing row`);
359138
+ if (baseKeys[baseIndex] !== targetKeys[targetPosition]) {
359139
+ return unsafe(`table ${baseTable.tableNodeId} modifies an existing row`);
359140
+ }
358635
359141
  }
358636
359142
  }
358637
359143
  if (tableSkeleton(baseBytes, baseTable) !== tableSkeleton(targetBytes, targetTable)) {
358638
359144
  return unsafe(`table ${baseTable.tableNodeId} changes non-row table structure`);
358639
359145
  }
358640
- const baseIds = new Set(baseTable.rows.map((row) => row.rowNodeId));
358641
359146
  let index2 = 0;
358642
359147
  while (index2 < targetTable.rows.length) {
358643
- if (baseIds.has(targetTable.rows[index2].rowNodeId)) {
359148
+ if (baseIndexByTarget.has(index2)) {
358644
359149
  index2 += 1;
358645
359150
  continue;
358646
359151
  }
358647
359152
  const start = index2;
358648
- while (index2 < targetTable.rows.length && !baseIds.has(targetTable.rows[index2].rowNodeId))
359153
+ while (index2 < targetTable.rows.length && !baseIndexByTarget.has(index2))
358649
359154
  index2 += 1;
358650
359155
  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;
359156
+ const nextBaseIndex = baseIndexByTarget.get(index2);
359157
+ const previousBaseIndex = [...baseIndexByTarget.entries()].reverse().find(([targetIndex]) => targetIndex < start)?.[1];
359158
+ const anchor = baseTable.rows[nextBaseIndex ?? previousBaseIndex ?? -1];
358654
359159
  if (!anchor)
358655
359160
  return unsafe(`table ${baseTable.tableNodeId} has no stable insertion anchor`);
358656
359161
  for (const row of inserted) {
@@ -358672,20 +359177,34 @@ function buildTrackedTableRowReplayPlan(input) {
358672
359177
  const rowXml = transplantableRowPayload(sourceRowXml, baseRoot, targetRoot);
358673
359178
  if (!rowXml)
358674
359179
  return unsafe("inserted row uses a namespace that cannot be transplanted safely");
358675
- rowsXml.push(rowXml);
359180
+ const identifiedRowXml = ensureInsertedParagraphIds(rowXml, baseRoot, baseParagraphIds, usedParagraphIds);
359181
+ if (!identifiedRowXml)
359182
+ return unsafe("inserted row cannot be assigned safe paragraph identities");
359183
+ rowsXml.push(identifiedRowXml);
358676
359184
  }
358677
359185
  steps.push({
358678
359186
  kind: "insertRows",
358679
359187
  storyId: STORY_ID2,
358680
359188
  tableNodeId: baseTable.tableNodeId,
358681
359189
  anchorRowNodeId: anchor.rowNodeId,
358682
- placement: nextBaseRow ? "before" : "after",
359190
+ placement: nextBaseIndex !== undefined ? "before" : "after",
358683
359191
  rowsXml
358684
359192
  });
358685
359193
  }
359194
+ if (steps.some((step3) => step3.tableNodeId === baseTable.tableNodeId)) {
359195
+ for (const entry of [...input.baseBody ?? [], ...input.targetBody]) {
359196
+ if (entry.structuralScope?.startsWith(`table:${baseTable.tableNodeId}:cell:`) || entry.structuralScope?.startsWith(`table:${targetTable.tableNodeId}:cell:`)) {
359197
+ ownedBlockIds.add(entry.blockId);
359198
+ }
359199
+ }
359200
+ }
358686
359201
  }
358687
359202
  if (steps.length === 0)
358688
359203
  return unsafe("table topology changed without an inserted or deleted row");
359204
+ 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:"))));
359205
+ if (ambiguousBodyOperation) {
359206
+ return unsafe(`body operation ${ambiguousBodyOperation.blockId} shares a table-cell identity`, true);
359207
+ }
358689
359208
  const bodyOperations = input.bodyOperations.filter((operation) => !ownedBlockIds.has(operation.blockId));
358690
359209
  const unownedStructuralOperation = bodyOperations.find((operation) => {
358691
359210
  const target = input.targetBody.find((entry) => entry.blockId === operation.blockId);
@@ -358715,21 +359234,135 @@ function containsWordprocessingRevisionMarkup(xml, namespaceBindings) {
358715
359234
  }
358716
359235
  return containsRevision;
358717
359236
  }
358718
- function unsafe(reason) {
358719
- return { steps: [], bodyOperations: [], unsafeReasons: [reason] };
359237
+ function unsafe(reason, refuseBlockFallback = false) {
359238
+ return { steps: [], bodyOperations: [], unsafeReasons: [reason], refuseBlockFallback };
358720
359239
  }
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))
359240
+ function matchRowSubsequence(shortKeys, longKeys, shortIds, longIds) {
359241
+ const remainingMatches = (shortStart, longStart) => {
359242
+ for (let shortIndex = shortStart;shortIndex < shortKeys.length; shortIndex += 1) {
359243
+ while (longStart < longKeys.length && longKeys[longStart] !== shortKeys[shortIndex])
359244
+ longStart += 1;
359245
+ if (longStart === longKeys.length)
359246
+ return false;
359247
+ longStart += 1;
359248
+ }
359249
+ return true;
359250
+ };
359251
+ const matches = new Map;
359252
+ let nextLongIndex = 0;
359253
+ for (let shortIndex = 0;shortIndex < shortKeys.length; shortIndex += 1) {
359254
+ const candidates = [];
359255
+ for (let longIndex = nextLongIndex;longIndex < longKeys.length; longIndex += 1) {
359256
+ if (shortKeys[shortIndex] === longKeys[longIndex] && remainingMatches(shortIndex + 1, longIndex + 1))
359257
+ candidates.push(longIndex);
359258
+ }
359259
+ if (candidates.length === 0)
358726
359260
  return null;
358727
- result.set(id, index2);
359261
+ const sameIdentity = candidates.filter((index2) => shortIds[shortIndex] === longIds[index2]);
359262
+ if (candidates.length > 1 && sameIdentity.length !== 1)
359263
+ return null;
359264
+ const selected = sameIdentity[0] ?? candidates[0];
359265
+ matches.set(shortIndex, selected);
359266
+ nextLongIndex = selected + 1;
358728
359267
  }
358729
- return result;
359268
+ for (const [shortIndex, longIndex] of matches) {
359269
+ const key2 = shortKeys[shortIndex];
359270
+ if (shortKeys.indexOf(key2) === shortKeys.lastIndexOf(key2) && longKeys.indexOf(key2) === longKeys.lastIndexOf(key2))
359271
+ continue;
359272
+ const sharedLongIndex = longIds.findIndex((id, index2) => id === shortIds[shortIndex] && longKeys[index2] === key2);
359273
+ const sharedShortIndex = shortIds.findIndex((id, index2) => id === longIds[longIndex] && shortKeys[index2] === key2);
359274
+ if (sharedLongIndex >= 0 && sharedLongIndex !== longIndex || sharedShortIndex >= 0 && sharedShortIndex !== shortIndex)
359275
+ return null;
359276
+ }
359277
+ return matches;
358730
359278
  }
358731
- function isSimpleTable(table) {
358732
- return !table.containsNestedTable && !table.hasGridSpan && !table.hasVMerge && table.isRectangular && table.rows.every((row) => row.cells.every((cell) => !cell.containsUnsupportedContent));
359279
+ function hasInconsistentRowIdentity(baseRows, targetRows, baseIndexByTarget) {
359280
+ const baseIndexById = new Map(baseRows.map((row, index2) => [row.rowNodeId, index2]));
359281
+ for (const [targetIndex, targetRow] of targetRows.entries()) {
359282
+ if (targetRow.identityProvenance !== "para-id")
359283
+ continue;
359284
+ const baseIndex = baseIndexById.get(targetRow.rowNodeId);
359285
+ if (baseIndex === undefined || baseRows[baseIndex].identityProvenance !== "para-id")
359286
+ continue;
359287
+ if (baseIndex !== targetIndex && baseIndexByTarget.get(targetIndex) !== baseIndex)
359288
+ return true;
359289
+ }
359290
+ return false;
359291
+ }
359292
+ function isSimpleTable(table, bytes, root) {
359293
+ 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)));
359294
+ }
359295
+ function isSafeContentControlCell(xml, root) {
359296
+ let foundControl = false;
359297
+ let safe = true;
359298
+ const parser = createXmlSaxParser({
359299
+ namespaceAware: true,
359300
+ fragment: true,
359301
+ additionalNamespaces: { ...root.namespaceBindings }
359302
+ });
359303
+ parser.on("error", () => {
359304
+ safe = false;
359305
+ });
359306
+ parser.on("opentag", (tag) => {
359307
+ if (tag.namespaceUri !== WORDPROCESSINGML_NS4)
359308
+ return;
359309
+ if (tag.localName === "sdt")
359310
+ foundControl = true;
359311
+ if (["drawing", "object", "oMath", "oMathPara", "tbl"].includes(tag.localName) || tag.attributes.some((attribute2) => isRelationshipNamespace(attribute2.namespaceUri)))
359312
+ safe = false;
359313
+ });
359314
+ try {
359315
+ parser.write(xml);
359316
+ parser.close();
359317
+ } catch {
359318
+ return false;
359319
+ }
359320
+ return foundControl && safe;
359321
+ }
359322
+ function ensureInsertedParagraphIds(xml, root, baseIds, usedIds) {
359323
+ const parsed = collectXmlElementRanges(xml, {
359324
+ fragment: true,
359325
+ additionalNamespaces: { ...root.namespaceBindings }
359326
+ });
359327
+ if (parsed.errors.length > 0)
359328
+ return null;
359329
+ const insertions = [];
359330
+ for (const paragraph4 of parsed.ranges.filter((range) => range.tag.namespaceUri === WORDPROCESSINGML_NS4 && range.tag.localName === "p")) {
359331
+ const existingId = paragraph4.tag.attributes.find((attribute2) => attribute2.namespaceUri === WORDPROCESSINGML_2010_NS && attribute2.localName === "paraId");
359332
+ if (existingId && !baseIds.has(existingId.value.toUpperCase()))
359333
+ continue;
359334
+ let identifier;
359335
+ for (let candidate2 = 1879048192;candidate2 > 0; candidate2 -= 1) {
359336
+ const value2 = candidate2.toString(16).toUpperCase().padStart(8, "0");
359337
+ if (!usedIds.has(value2)) {
359338
+ identifier = value2;
359339
+ usedIds.add(value2);
359340
+ break;
359341
+ }
359342
+ }
359343
+ if (!identifier)
359344
+ return null;
359345
+ if (existingId) {
359346
+ const opening = xml.slice(paragraph4.start, paragraph4.openEnd);
359347
+ const attribute2 = new RegExp(`\\b${existingId.name}="${existingId.value}"`);
359348
+ const match = attribute2.exec(opening);
359349
+ if (!match)
359350
+ return null;
359351
+ insertions.push({
359352
+ offset: paragraph4.start + match.index,
359353
+ text: `${existingId.name}="${identifier}"`,
359354
+ end: paragraph4.start + match.index + match[0].length
359355
+ });
359356
+ } else {
359357
+ const localDeclaration = paragraph4.namespaceBindings.w14 === WORDPROCESSINGML_2010_NS ? "" : ` xmlns:w14="${WORDPROCESSINGML_2010_NS}"`;
359358
+ const opening = xml.slice(paragraph4.start, paragraph4.openEnd);
359359
+ insertions.push({
359360
+ offset: paragraph4.openEnd - (opening.endsWith("/>") ? 2 : 1),
359361
+ text: `${localDeclaration} w14:paraId="${identifier}"`
359362
+ });
359363
+ }
359364
+ }
359365
+ return insertions.reverse().reduce((result, insertion) => result.slice(0, insertion.offset) + insertion.text + result.slice(insertion.end ?? insertion.offset), xml);
358733
359366
  }
358734
359367
  function tableSkeleton(bytes, table) {
358735
359368
  let cursor = table.tableByteStart;
@@ -361391,7 +362024,32 @@ function paragraphOpeningTag2(paragraphXml) {
361391
362024
  return paragraphXml.slice(0, paragraphXml.indexOf(">") + 1);
361392
362025
  }
361393
362026
  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"));
362027
+ 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"));
362028
+ }
362029
+ function bookmarkStructuresEqual(leftXml, rightXml) {
362030
+ const summarize = (xml) => {
362031
+ if (!bookmarkRangesArePaired(xml))
362032
+ return null;
362033
+ const nameById = new Map(collectWordElements(xml, "bookmarkStart", true).map((element) => {
362034
+ const id = readAttribute2(element, "id");
362035
+ const name = readAttribute2(element, "name");
362036
+ return [id, name];
362037
+ }));
362038
+ const starts = collectWordElements(xml, "bookmarkStart", true).map((element) => ({
362039
+ position: element.range.start,
362040
+ value: `start:${replaceAttribute(element, "id", "__bookmark_id__")}`
362041
+ }));
362042
+ const ends = collectWordElements(xml, "bookmarkEnd", true).map((element) => ({
362043
+ position: element.range.start,
362044
+ value: `end:${replaceAttribute(element, "id", `__bookmark_${nameById.get(readAttribute2(element, "id")) ?? "missing"}__`)}`
362045
+ }));
362046
+ return [...starts, ...ends].sort((a, b) => a.position - b.position).map((event) => event.value);
362047
+ };
362048
+ const left = summarize(leftXml);
362049
+ const right = summarize(rightXml);
362050
+ if (!left || !right || left.length !== right.length)
362051
+ return false;
362052
+ return left.every((value2, index2) => value2 === right[index2]);
361395
362053
  }
361396
362054
  function arraysEqual2(left, right) {
361397
362055
  return left.length === right.length && left.every((value2, index2) => value2 === right[index2]);
@@ -361427,39 +362085,44 @@ function buildSectionSteps(baseXml, targetXml, unsafeReasons) {
361427
362085
  function buildReferenceSteps(baseXml, targetXml, unsafeReasons) {
361428
362086
  const baseBookmarks = collectWordElements(baseXml, "bookmarkStart", true);
361429
362087
  const targetBookmarks = collectWordElements(targetXml, "bookmarkStart", true);
361430
- if (baseBookmarks.length !== targetBookmarks.length) {
361431
- unsafeReasons.push("bookmark topology changed");
361432
- return [];
361433
- }
361434
362088
  if (!bookmarkRangesArePaired(baseXml) || !bookmarkRangesArePaired(targetXml)) {
361435
362089
  unsafeReasons.push("bookmark topology is unbalanced or duplicated");
361436
362090
  return [];
361437
362091
  }
361438
362092
  const renamedBookmarks = new Map;
361439
362093
  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;
362094
+ if (baseBookmarks.length === targetBookmarks.length) {
362095
+ for (let index2 = 0;index2 < baseBookmarks.length; index2 += 1) {
362096
+ const before = baseBookmarks[index2];
362097
+ const after = targetBookmarks[index2];
362098
+ const beforeXml = before.xml;
362099
+ const afterXml = after.xml;
362100
+ if (beforeXml === afterXml)
362101
+ continue;
362102
+ const beforeId = readAttribute2(before, "id");
362103
+ const afterId = readAttribute2(after, "id");
362104
+ const beforeName = readAttribute2(before, "name");
362105
+ const afterName = readAttribute2(after, "name");
362106
+ if (beforeId && afterId && beforeName && beforeName === afterName && replaceAttribute(after, "id", beforeId) === beforeXml) {
362107
+ continue;
362108
+ }
362109
+ if (!beforeId || beforeId !== afterId || !beforeName || !afterName || replaceAttribute(before, "name", afterName) !== afterXml) {
362110
+ unsafeReasons.push(`bookmark ${index2} changed beyond a name-only rename or ID allocation`);
362111
+ continue;
362112
+ }
362113
+ renamedBookmarks.set(beforeName, afterName);
362114
+ steps.push({
362115
+ kind: "bookmark-start",
362116
+ beforeXml,
362117
+ afterXml,
362118
+ beforeNamespaceBindings: { ...before.range.namespaceBindings },
362119
+ afterNamespaceBindings: { ...after.range.namespaceBindings }
362120
+ });
361454
362121
  }
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
- });
362122
+ } else if (targetBookmarks.length > baseBookmarks.length) {
362123
+ steps.push(...buildAddedBookmarkSteps(baseXml, targetXml, baseBookmarks, targetBookmarks, unsafeReasons));
362124
+ } else {
362125
+ unsafeReasons.push("bookmark removal is not supported");
361463
362126
  }
361464
362127
  const baseFields = collectWordElements(baseXml, "fldSimple");
361465
362128
  const targetFields = collectWordElements(targetXml, "fldSimple");
@@ -361490,8 +362153,125 @@ function buildReferenceSteps(baseXml, targetXml, unsafeReasons) {
361490
362153
  afterNamespaceBindings: { ...after.range.namespaceBindings }
361491
362154
  });
361492
362155
  }
362156
+ steps.push(...buildAddedComplexFieldSteps(baseXml, targetXml, unsafeReasons));
361493
362157
  return steps;
361494
362158
  }
362159
+ function buildAddedBookmarkSteps(baseXml, targetXml, baseBookmarks, targetBookmarks, unsafeReasons) {
362160
+ const baseNames = new Set(baseBookmarks.map((element) => readAttribute2(element, "name")));
362161
+ const targetNames = targetBookmarks.map((element) => readAttribute2(element, "name"));
362162
+ if (baseNames.has(null) || targetNames.some((name) => name === null) || new Set(targetNames).size !== targetNames.length || [...baseNames].some((name) => !targetNames.includes(name))) {
362163
+ unsafeReasons.push("bookmark addition cannot be matched unambiguously by name");
362164
+ return [];
362165
+ }
362166
+ const baseParagraphs = collectWordElements(baseXml, "p");
362167
+ const targetParagraphs = collectWordElements(targetXml, "p");
362168
+ if (baseParagraphs.length !== targetParagraphs.length) {
362169
+ unsafeReasons.push("bookmark addition changed paragraph topology");
362170
+ return [];
362171
+ }
362172
+ const targetEnds = collectWordElements(targetXml, "bookmarkEnd", true);
362173
+ const steps = [];
362174
+ for (const start of targetBookmarks.filter((element) => !baseNames.has(readAttribute2(element, "name")))) {
362175
+ const id = readAttribute2(start, "id");
362176
+ const end = targetEnds.find((candidate2) => readAttribute2(candidate2, "id") === id);
362177
+ const paragraphIndex = targetParagraphs.findIndex((paragraph4) => paragraph4.range.start < start.range.start && paragraph4.range.end > (end?.range.end ?? Number.MAX_SAFE_INTEGER));
362178
+ if (!id || !end || paragraphIndex < 0) {
362179
+ unsafeReasons.push("added bookmark range is not contained in one paragraph");
362180
+ continue;
362181
+ }
362182
+ const before = baseParagraphs[paragraphIndex];
362183
+ const after = targetParagraphs[paragraphIndex];
362184
+ const beforeFragmentXml = targetXml.slice(start.range.end, end.range.start);
362185
+ const afterFragmentXml = targetXml.slice(start.range.start, end.range.end);
362186
+ if (replaceUnique(after.xml, afterFragmentXml, beforeFragmentXml) !== before.xml) {
362187
+ unsafeReasons.push("added bookmark range changes content beyond a balanced wrapper");
362188
+ continue;
362189
+ }
362190
+ steps.push({
362191
+ kind: "bookmark-range",
362192
+ beforeXml: before.xml,
362193
+ afterXml: after.xml,
362194
+ beforeFragmentXml,
362195
+ afterFragmentXml,
362196
+ beforeNamespaceBindings: { ...before.range.namespaceBindings },
362197
+ afterNamespaceBindings: { ...after.range.namespaceBindings }
362198
+ });
362199
+ }
362200
+ return steps;
362201
+ }
362202
+ function buildAddedComplexFieldSteps(baseXml, targetXml, unsafeReasons) {
362203
+ const baseParagraphs = collectWordElements(baseXml, "p");
362204
+ const targetParagraphs = collectWordElements(targetXml, "p");
362205
+ if (baseParagraphs.length !== targetParagraphs.length)
362206
+ return [];
362207
+ const targetBookmarkNameCounts = new Map;
362208
+ for (const bookmark of collectWordElements(targetXml, "bookmarkStart", true)) {
362209
+ const name = readAttribute2(bookmark, "name");
362210
+ if (name !== null) {
362211
+ targetBookmarkNameCounts.set(name, (targetBookmarkNameCounts.get(name) ?? 0) + 1);
362212
+ }
362213
+ }
362214
+ const steps = [];
362215
+ for (let index2 = 0;index2 < targetParagraphs.length; index2 += 1) {
362216
+ const before = baseParagraphs[index2];
362217
+ const after = targetParagraphs[index2];
362218
+ if (before.xml === after.xml)
362219
+ continue;
362220
+ const candidate2 = addedComplexRefField(after, before.xml);
362221
+ if (!candidate2)
362222
+ continue;
362223
+ const targetCount = targetBookmarkNameCounts.get(candidate2.referenceName) ?? 0;
362224
+ if (targetCount === 0) {
362225
+ unsafeReasons.push(`complex REF field ${index2} targets a missing bookmark`);
362226
+ continue;
362227
+ }
362228
+ if (targetCount !== 1) {
362229
+ unsafeReasons.push(`complex REF field ${index2} targets an ambiguous bookmark`);
362230
+ continue;
362231
+ }
362232
+ steps.push({
362233
+ kind: "complex-field",
362234
+ beforeXml: before.xml,
362235
+ afterXml: after.xml,
362236
+ beforeFragmentXml: candidate2.resultXml,
362237
+ afterFragmentXml: candidate2.fieldXml,
362238
+ beforeNamespaceBindings: { ...before.range.namespaceBindings },
362239
+ afterNamespaceBindings: { ...after.range.namespaceBindings }
362240
+ });
362241
+ }
362242
+ return steps;
362243
+ }
362244
+ function addedComplexRefField(targetParagraph, expectedBaseParagraphXml) {
362245
+ const fields = collectWordElements(targetParagraph.xml, "fldChar", true, targetParagraph.range.namespaceBindings);
362246
+ for (let beginIndex = 0;beginIndex < fields.length; beginIndex += 1) {
362247
+ const begin = fields[beginIndex];
362248
+ if (readAttribute2(begin, "fldCharType") !== "begin")
362249
+ continue;
362250
+ const separate = fields.slice(beginIndex + 1).find((field) => readAttribute2(field, "fldCharType") === "separate");
362251
+ const end = separate && fields.find((field) => field.range.start > separate.range.start && readAttribute2(field, "fldCharType") === "end");
362252
+ if (!separate || !end)
362253
+ continue;
362254
+ const runs = collectWordElements(targetParagraph.xml, "r", false, targetParagraph.range.namespaceBindings);
362255
+ const beginRun = runs.find((run) => run.range.start <= begin.range.start && run.range.end >= begin.range.end);
362256
+ const separateRun = runs.find((run) => run.range.start <= separate.range.start && run.range.end >= separate.range.end);
362257
+ const endRun = runs.find((run) => run.range.start <= end.range.start && run.range.end >= end.range.end);
362258
+ if (!beginRun || !separateRun || !endRun)
362259
+ continue;
362260
+ 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("");
362261
+ const parsed = parseRefInstruction(instruction);
362262
+ if (!parsed)
362263
+ continue;
362264
+ const fieldXml = targetParagraph.xml.slice(beginRun.range.start, endRun.range.end);
362265
+ const resultXml = targetParagraph.xml.slice(separateRun.range.end, endRun.range.start);
362266
+ const fieldChars = fields.filter((field) => field.range.start >= begin.range.start && field.range.end <= end.range.end);
362267
+ if (fieldChars.length !== 3 || fieldChars.map((field) => readAttribute2(field, "fldCharType")).join(",") !== "begin,separate,end")
362268
+ continue;
362269
+ if (!resultXml || replaceUnique(targetParagraph.xml, fieldXml, resultXml) !== expectedBaseParagraphXml)
362270
+ continue;
362271
+ return { fieldXml, resultXml, referenceName: parsed.name };
362272
+ }
362273
+ return null;
362274
+ }
361495
362275
  function bookmarkRangesArePaired(xml) {
361496
362276
  const startsById = new Map;
361497
362277
  for (const element of collectWordElements(xml, "bookmarkStart", true)) {
@@ -361520,8 +362300,10 @@ function collectEmptyElements(xml, localName2) {
361520
362300
  function collectElements(xml, localName2) {
361521
362301
  return collectWordElements(xml, localName2).map((element) => element.xml);
361522
362302
  }
361523
- function collectWordElements(xml, localName2, selfClosingOnly = false) {
361524
- const parsed = collectXmlElementRanges(xml, { additionalNamespaces: { w: WORDPROCESSINGML_NS6 } });
362303
+ function collectWordElements(xml, localName2, selfClosingOnly = false, additionalNamespaces = {}) {
362304
+ const parsed = collectXmlElementRanges(xml, {
362305
+ additionalNamespaces: { w: WORDPROCESSINGML_NS6, ...additionalNamespaces }
362306
+ });
361525
362307
  if (parsed.errors.length > 0)
361526
362308
  return [];
361527
362309
  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 +362458,11 @@ class V2DiffAdapter {
361676
362458
  try {
361677
362459
  const engine = createV2CompareEngine(this.context);
361678
362460
  const base3 = captureCompareSnapshotSync(this.context);
361679
- const analysis = engine.analyzeSnapshots(base3, targetPayload.compareSnapshot);
362461
+ const baseMainDocument = projectDiffMainDocumentSource(this.context);
362462
+ const targetMainDocument = targetPayload.mainDocument ?? EMPTY_MAIN_DOCUMENT_SOURCE;
362463
+ const rawAnalysis = engine.analyzeSnapshots(base3, targetPayload.compareSnapshot);
362464
+ const comparisonTarget = paragraphIdentityOnlyComparisonTarget(base3, targetPayload.compareSnapshot, baseMainDocument, targetMainDocument, rawAnalysis.result);
362465
+ const analysis = comparisonTarget ? engine.analyzeSnapshots(base3, comparisonTarget) : rawAnalysis;
361680
362466
  if (!analysis.plan) {
361681
362467
  throw diffValidationError("INVALID_INPUT", "diff.compare: compare engine did not return a semantic analysis plan.");
361682
362468
  }
@@ -361693,8 +362479,6 @@ class V2DiffAdapter {
361693
362479
  const baseNumbering = projectDiffNumberingSource(this.context);
361694
362480
  const targetNumbering = targetPayload.numbering ?? { xml: null };
361695
362481
  const numberingPlan = buildNumberingApplyPlan(baseNumbering, targetNumbering);
361696
- const baseMainDocument = projectDiffMainDocumentSource(this.context);
361697
- const targetMainDocument = targetPayload.mainDocument ?? EMPTY_MAIN_DOCUMENT_SOURCE;
361698
362482
  const sectionsReferencesPlan = buildSectionsReferencesReplayPlan(baseMainDocument, targetMainDocument);
361699
362483
  const adapterApplySupportedFamilies = new Set;
361700
362484
  if (sectionsReferencesPlan.unsafeReasons.length === 0) {
@@ -361967,8 +362751,9 @@ class V2DiffAdapter {
361967
362751
  unsupportedReason: "section-reference-replay-unsafe"
361968
362752
  });
361969
362753
  }
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) {
362754
+ const usesLocalizedMainDocumentReplay = sectionsReferencesChanged && (sectionsReferencesPlan.sectionSteps.length > 0 || sectionsReferencesPlan.referenceSteps.length > 0);
362755
+ const localizedBodyOperations = usesLocalizedMainDocumentReplay ? buildLocalizedBodyOperations(payload.base.body, payload.target.body, sectionsReferencesPlan.sectionSteps, sectionsReferencesPlan.referenceSteps, { base: payload.base.definitions, target: payload.target.definitions }, canonicalBodyOperations) : [];
362756
+ if (usesLocalizedMainDocumentReplay) {
361972
362757
  setRuntimeBodyOperationCounts(operationCounts, payload.applyPlan, localizedBodyOperations);
361973
362758
  }
361974
362759
  const localizedMainDocumentCovered = !sectionsReferencesPlan.hasUnownedMainDocumentChanges || localizedMainDocumentChangesAreCovered({
@@ -362014,19 +362799,23 @@ class V2DiffAdapter {
362014
362799
  }
362015
362800
  }
362016
362801
  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, {
362802
+ const trackedTableBodyOperations = !tablesChanged || !reviewMarkup ? payload.applyPlan.bodyOperations : usesLocalizedMainDocumentReplay ? localizedBodyOperations : mergeCanonicalRevisionReplay(computeCompareBodyOperations(payload.base.body, payload.target.body, {
362018
362803
  base: payload.base.definitions,
362019
362804
  target: payload.target.definitions
362020
362805
  }), canonicalBodyOperations);
362021
362806
  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({
362807
+ let trackedTablePlan = tablesChanged && reviewMarkup && !trackedTablePropertyPlan ? buildTrackedTableRowReplayPlan({
362024
362808
  base: liveBaseMainDocument,
362025
362809
  target: payloadMainDocument.target,
362810
+ baseBody: payload.base.body,
362026
362811
  targetBody: payload.target.body,
362027
362812
  bodyOperations: trackedTableBodyOperations
362028
- }) : { steps: [], bodyOperations: trackedTableBlockPlan?.bodyOperations ?? (trackedTablePropertyPlan ? trackedTableBodyOperations : payload.applyPlan.bodyOperations), unsafeReasons: [] };
362029
- if (trackedTablePlan.unsafeReasons.length > 0) {
362813
+ }) : { steps: [], bodyOperations: trackedTablePropertyPlan ? trackedTableBodyOperations : payload.applyPlan.bodyOperations, unsafeReasons: [] };
362814
+ let trackedTableBlockPlan = tablesChanged && reviewMarkup && !trackedTablePropertyPlan && !trackedTablePlan.refuseBlockFallback && (trackedTablePlan.unsafeReasons.length > 0 || trackedTablePlan.steps.length === 0) ? buildTrackedTableBlockReplayPlan({ base: liveBaseMainDocument, target: payloadMainDocument.target, bodyOperations: trackedTableBodyOperations }) : null;
362815
+ if (trackedTableBlockPlan) {
362816
+ trackedTablePlan = { steps: [], bodyOperations: trackedTableBlockPlan.bodyOperations, unsafeReasons: [] };
362817
+ }
362818
+ if (trackedTablePlan.unsafeReasons.length > 0 && !trackedTablePlan.refuseBlockFallback) {
362030
362819
  const replacement = buildTrackedTableBlockReplayPlan({
362031
362820
  base: liveBaseMainDocument,
362032
362821
  target: payloadMainDocument.target,
@@ -362081,7 +362870,7 @@ class V2DiffAdapter {
362081
362870
  if (mainDocumentPlan.unsafeReasons.length > 0) {
362082
362871
  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
362872
  }
362084
- const runtimeApplyPlan = localizedBodyOperations.length > 0 ? {
362873
+ const runtimeApplyPlan = usesLocalizedMainDocumentReplay ? {
362085
362874
  ...runtimeApplyPlanBase,
362086
362875
  applyPlan: {
362087
362876
  ...runtimeApplyPlanBase.applyPlan,
@@ -362113,6 +362902,13 @@ class V2DiffAdapter {
362113
362902
  handledFamilies.add("body");
362114
362903
  }
362115
362904
  }
362905
+ const rowControlsCovered = trackedTablePlan.steps.length > 0 && rowReplayCoversContentControls(payload.base.annotations, payload.target.annotations, trackedTablePlan.steps, liveBaseMainDocument.xml, payloadMainDocument.target.xml);
362906
+ if (trackedTablePlan.steps.length > 0 && !rowControlsCovered) {
362907
+ 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" });
362908
+ }
362909
+ if (changedPolicies.some((policy) => policy.family === "content-controls") && rowControlsCovered) {
362910
+ handledFamilies.add("content-controls");
362911
+ }
362116
362912
  if (mainRevisionChangesHandled)
362117
362913
  handledFamilies.add("tracked-changes");
362118
362914
  if (mainDocumentPlan.steps.length > 0)
@@ -362289,6 +363085,23 @@ function buildCompareApplyEligibility(context, payload, plans) {
362289
363085
  direct.add("fields-references");
362290
363086
  tracked.add("fields-references");
362291
363087
  }
363088
+ const usesLocalizedMainDocumentReplay = plans.sectionsReferencesPlan.sectionSteps.length > 0 || plans.sectionsReferencesPlan.referenceSteps.length > 0;
363089
+ if (usesLocalizedMainDocumentReplay) {
363090
+ 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);
363091
+ const localizedMainDocumentCovered = !plans.sectionsReferencesPlan.hasUnownedMainDocumentChanges || localizedMainDocumentChangesAreCovered({
363092
+ base: payload.mainDocument?.base ?? EMPTY_MAIN_DOCUMENT_SOURCE,
363093
+ target: payload.mainDocument?.target ?? EMPTY_MAIN_DOCUMENT_SOURCE,
363094
+ baseBody: payload.base.body,
363095
+ targetBody: payload.target.body,
363096
+ sectionSteps: plans.sectionsReferencesPlan.sectionSteps,
363097
+ referenceSteps: plans.sectionsReferencesPlan.referenceSteps,
363098
+ bodyOperations: localizedBodyOperations
363099
+ });
363100
+ if (localizedMainDocumentCovered) {
363101
+ direct.add("body");
363102
+ tracked.add("body");
363103
+ }
363104
+ }
362292
363105
  }
362293
363106
  const slotsChanged = plans.physicalPlan.hasReferenceIdChanges || payload.familyPolicy.some((policy) => policy.family === "header-footer-slots" && policy.applyRequired);
362294
363107
  const slotPlan = slotsChanged ? buildHeaderFooterSlotPackageRewrite(context, payload.base.slots ?? [], payload.target.slots ?? [], plans.physicalPlan.refByPart) : { rewrites: [], unsafeReasons: [], operationCount: 0 };
@@ -362350,13 +363163,19 @@ function buildCompareApplyEligibility(context, payload, plans) {
362350
363163
  const trackedTablePlan = buildTrackedTableRowReplayPlan({
362351
363164
  base: mainDocument.base,
362352
363165
  target: mainDocument.target,
363166
+ baseBody: payload.base.body,
362353
363167
  targetBody: payload.target.body,
362354
- bodyOperations: payload.applyPlan.bodyOperations
363168
+ bodyOperations: payload.applyPlan.bodyOperations.length > 0 ? payload.applyPlan.bodyOperations : computeCompareBodyOperations(payload.base.body, payload.target.body, {
363169
+ base: payload.base.definitions,
363170
+ target: payload.target.definitions
363171
+ })
362355
363172
  });
362356
363173
  if (trackedTablePlan.unsafeReasons.length === 0 && trackedTablePlan.steps.length > 0) {
362357
363174
  tracked.add("tables");
362358
363175
  if (bodyDeferredToTableReplay)
362359
363176
  tracked.add("body");
363177
+ if (rowReplayCoversContentControls(payload.base.annotations, payload.target.annotations, trackedTablePlan.steps, mainDocument.base.xml, mainDocument.target.xml))
363178
+ tracked.add("content-controls");
362360
363179
  }
362361
363180
  }
362362
363181
  const packageGraphCoveredByComments = direct.has("comments") && packageGraphKeysNotCoveredByComments(context, payload.base, payload.target, plans.commentPlan.steps).length === 0;
@@ -362414,7 +363233,16 @@ function buildCompareApplyEligibility(context, payload, plans) {
362414
363233
  });
362415
363234
  }
362416
363235
  for (const mode of ["direct", "tracked"]) {
362417
- const runtimePlan = buildHeaderFooterRuntimeApplyPlan(payload, slotPlan, plans.physicalPlan, mode === "tracked");
363236
+ let runtimePlan = buildHeaderFooterRuntimeApplyPlan(payload, slotPlan, plans.physicalPlan, mode === "tracked");
363237
+ if (plans.sectionsReferencesPlan.unsafeReasons.length === 0 && (plans.sectionsReferencesPlan.sectionSteps.length > 0 || plans.sectionsReferencesPlan.referenceSteps.length > 0)) {
363238
+ runtimePlan = {
363239
+ ...runtimePlan,
363240
+ applyPlan: {
363241
+ ...runtimePlan.applyPlan,
363242
+ 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)
363243
+ }
363244
+ };
363245
+ }
362418
363246
  const unsupportedFamilies = findStructurallyUnsupportedOperationFamilies(context, runtimePlan, payload.mainDocument?.base ?? EMPTY_MAIN_DOCUMENT_SOURCE, mode === "tracked", plans.sectionsReferencesPlan.referenceSteps);
362419
363247
  if (unsupportedFamilies.length > 0) {
362420
363248
  hazards[mode].push({
@@ -362958,7 +363786,8 @@ function buildLocalizedBodyOperations(base3, target, sectionSteps, referenceStep
362958
363786
  throwLocalizedBodyCorrespondenceError(sectionSteps, referenceSteps);
362959
363787
  const neutralTarget = target.map((entry) => {
362960
363788
  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;
363789
+ const reversedRunXml = baseEntry?.runXml && entry.runXml ? referenceSteps.reduce((xml, step3) => reverseReferenceAttribute(xml, step3), entry.runXml) : entry.runXml;
363790
+ const neutralRunXml = baseEntry?.runXml && reversedRunXml && stripReplayNamespaceDeclarations(reversedRunXml) === stripReplayNamespaceDeclarations(baseEntry.runXml) ? baseEntry.runXml : reversedRunXml;
362962
363791
  const neutralParagraphPropertiesXml = baseEntry?.paragraphPropertiesXml !== undefined && entry.paragraphPropertiesXml !== undefined ? sectionSteps.reduce((xml, step3) => reverseSectionProperties(xml, baseEntry.paragraphPropertiesXml, step3), entry.paragraphPropertiesXml) : entry.paragraphPropertiesXml;
362963
363792
  if (neutralRunXml === entry.runXml && neutralParagraphPropertiesXml === entry.paragraphPropertiesXml)
362964
363793
  return entry;
@@ -363025,6 +363854,17 @@ function reverseSectionProperties(targetParagraphPropertiesXml, baseParagraphPro
363025
363854
  return targetParagraphPropertiesXml.slice(0, targetStart) + step3.beforeXml + targetParagraphPropertiesXml.slice(targetStart + step3.afterXml.length);
363026
363855
  }
363027
363856
  function reverseReferenceAttribute(xml, step3) {
363857
+ if (step3.kind === "bookmark-range" || step3.kind === "complex-field") {
363858
+ if (step3.beforeFragmentXml === undefined || step3.afterFragmentXml === undefined)
363859
+ return xml;
363860
+ const normalizedXml = stripReplayNamespaceDeclarations(xml);
363861
+ const after = stripReplayNamespaceDeclarations(step3.afterFragmentXml);
363862
+ const before = stripReplayNamespaceDeclarations(step3.beforeFragmentXml);
363863
+ const start = normalizedXml.indexOf(after);
363864
+ if (start < 0 || normalizedXml.indexOf(after, start + after.length) >= 0)
363865
+ return xml;
363866
+ return normalizedXml.slice(0, start) + before + normalizedXml.slice(start + after.length);
363867
+ }
363028
363868
  const elementName = step3.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
363029
363869
  const localName2 = step3.kind === "bookmark-start" ? "name" : "instr";
363030
363870
  const afterValue = readReplayAttribute(step3.afterXml, localName2, step3.afterNamespaceBindings);
@@ -363094,7 +363934,9 @@ function validateFullDiffCanonicalArtifacts(engine, payload) {
363094
363934
  const problems = [];
363095
363935
  let canonicalAnalysis;
363096
363936
  try {
363097
- canonicalAnalysis = engine.analyzeSnapshots(payload.base, payload.target);
363937
+ const rawAnalysis = engine.analyzeSnapshots(payload.base, payload.target);
363938
+ const comparisonTarget = paragraphIdentityOnlyComparisonTarget(payload.base, payload.target, payload.mainDocument?.base ?? EMPTY_MAIN_DOCUMENT_SOURCE, payload.mainDocument?.target ?? EMPTY_MAIN_DOCUMENT_SOURCE, rawAnalysis.result);
363939
+ canonicalAnalysis = comparisonTarget ? engine.analyzeSnapshots(payload.base, comparisonTarget) : rawAnalysis;
363098
363940
  } catch (error48) {
363099
363941
  const message = error48 instanceof Error ? error48.message : String(error48);
363100
363942
  return { problems: [`payload.base/target could not be re-analyzed (${message})`] };
@@ -363114,6 +363956,25 @@ function validateFullDiffCanonicalArtifacts(engine, payload) {
363114
363956
  }
363115
363957
  return { problems };
363116
363958
  }
363959
+ function paragraphIdentityOnlyComparisonTarget(base3, target, baseDocument, targetDocument, rawAnalysis) {
363960
+ if (!baseDocument.xml || !targetDocument.xml || baseDocument.xml === targetDocument.xml)
363961
+ return null;
363962
+ 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+\/>$/, "/>"));
363963
+ if (withoutParagraphIds(baseDocument.xml) !== withoutParagraphIds(targetDocument.xml))
363964
+ return null;
363965
+ if (validateMainDocumentSourceAgainstSnapshotPart(baseDocument, base3, "base").length > 0 || validateMainDocumentSourceAgainstSnapshotPart(targetDocument, target, "target").length > 0)
363966
+ return null;
363967
+ const levels = (source) => new Map(Object.entries(source.paragraphStyleOutlineLevels ?? {}));
363968
+ if (validateMainDocumentSourceAgainstBodySnapshot(baseDocument, base3.body, { paragraphStyleOutlineLevels: levels(baseDocument) }).length > 0 || validateMainDocumentSourceAgainstBodySnapshot(targetDocument, target.body, { paragraphStyleOutlineLevels: levels(targetDocument) }).length > 0)
363969
+ return null;
363970
+ const otherParts = (snapshot2) => snapshot2.sourceInventory?.parts.filter((part) => part.partUri !== DOCUMENT_PART_URI6);
363971
+ if (!base3.sourceInventory || !target.sourceInventory || !artifactEquals(otherParts(base3), otherParts(target)))
363972
+ return null;
363973
+ const changedFamilies3 = rawAnalysis.families.filter((family) => family.state === "changed-supported");
363974
+ if (!changedFamilies3.some((family) => family.family === "tables") || changedFamilies3.some((family) => family.family !== "body" && family.family !== "tables"))
363975
+ return null;
363976
+ return { ...base3, identity: target.identity };
363977
+ }
363117
363978
  function artifactEquals(left, right) {
363118
363979
  return JSON.stringify(normalizeArtifactForCompare(left)) === JSON.stringify(normalizeArtifactForCompare(right));
363119
363980
  }
@@ -363293,8 +364154,14 @@ function containsStructuralParagraphTag(xml, blockKind) {
363293
364154
  return errors4.length === 0 ? found : null;
363294
364155
  }
363295
364156
  function findUniqueReplayCarrier(xml, step3) {
364157
+ if (step3.kind === "bookmark-range" || step3.kind === "complex-field") {
364158
+ const fragment3 = step3.afterFragmentXml;
364159
+ if (!fragment3)
364160
+ return null;
364161
+ const start = xml.indexOf(fragment3);
364162
+ return start >= 0 && xml.indexOf(fragment3, start + fragment3.length) < 0 ? { start, end: start + fragment3.length } : null;
364163
+ }
363296
364164
  const expectedName = step3.kind === "bookmark-start" ? "bookmarkStart" : "fldSimple";
363297
- const localName2 = step3.kind === "bookmark-start" ? "name" : "instr";
363298
364165
  const parsed = collectXmlElementRanges(xml, {
363299
364166
  fragment: true,
363300
364167
  additionalNamespaces: { ...step3.beforeNamespaceBindings }
@@ -377122,13 +377989,7 @@ function normalizeParagraphBlock(value2, path7, version2, allowHeading) {
377122
377989
  return normalized;
377123
377990
  inlines.push(normalized);
377124
377991
  }
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
- });
377992
+ const projected = inlines.flatMap(projectInlineText);
377132
377993
  if (projected.length !== runs.length || projected.some((inline, runIndex) => inline.text !== runs[runIndex]?.text || JSON.stringify(inline.marks ?? {}) !== JSON.stringify(runs[runIndex]?.marks ?? {}))) {
377133
377994
  return structureFailure("Paragraph runs must equal the text projection of inlines.", path7);
377134
377995
  }
@@ -377153,6 +378014,12 @@ function normalizeParagraphBlock(value2, path7, version2, allowHeading) {
377153
378014
  const paragraphProps = normalizeParagraphProperties(value2.paragraphProps, [...path7, "paragraphProps"]);
377154
378015
  if (isPlanFailure(paragraphProps))
377155
378016
  return paragraphProps;
378017
+ const blockSdts = normalizeBlockSdts(value2.blockSdts, [...path7, "blockSdts"]);
378018
+ if (isPlanFailure(blockSdts))
378019
+ return blockSdts;
378020
+ if (blockSdts && (version2 !== SUPERDOC_V22_CLIPBOARD_FRAGMENT_VERSION || value2.complete !== true)) {
378021
+ return structureFailure("Complete block content controls require a v2.2 paragraph.", [...path7, "blockSdts"]);
378022
+ }
377156
378023
  const sourcePath = normalizeSourcePath(value2.sourcePath, [...path7, "sourcePath"]);
377157
378024
  if (isPlanFailure(sourcePath))
377158
378025
  return sourcePath;
@@ -377167,16 +378034,83 @@ function normalizeParagraphBlock(value2, path7, version2, allowHeading) {
377167
378034
  ...typeof continuation === "number" ? { listItemContinuationLevel: continuation } : {},
377168
378035
  ...typeof value2.pPrXml === "string" && value2.pPrXml.length > 0 ? { pPrXml: value2.pPrXml } : {},
377169
378036
  ...value2.complete === true ? { complete: true } : {},
378037
+ ...blockSdts ? { blockSdts } : {},
377170
378038
  ...sourcePath ? { sourcePath } : {}
377171
378039
  };
377172
378040
  return isHeading ? { kind: "heading", level: value2.level, ...common } : { kind: "paragraph", ...common };
377173
378041
  }
377174
- function normalizeInline(value2, path7, allowHyperlink) {
378042
+ function normalizeBlockSdts(value2, path7) {
378043
+ if (value2 === undefined)
378044
+ return;
378045
+ if (!Array.isArray(value2) || value2.length === 0 || value2.length > 16) {
378046
+ return structureFailure("Block content-control ancestry must contain 1 to 16 wrappers.", path7);
378047
+ }
378048
+ const ids = new Set;
378049
+ const result = [];
378050
+ for (const [index2, wrapper] of value2.entries()) {
378051
+ 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))) {
378052
+ return structureFailure("Invalid block content-control wrapper.", [...path7, index2]);
378053
+ }
378054
+ ids.add(wrapper.sourceId);
378055
+ result.push({
378056
+ sourceId: wrapper.sourceId,
378057
+ controlType: wrapper.controlType,
378058
+ ...wrapper.tag !== undefined ? { tag: wrapper.tag } : {},
378059
+ ...wrapper.alias !== undefined ? { alias: wrapper.alias } : {},
378060
+ ...wrapper.lockMode !== undefined ? { lockMode: wrapper.lockMode } : {},
378061
+ ...wrapper.multiline !== undefined ? { multiline: wrapper.multiline } : {}
378062
+ });
378063
+ }
378064
+ return result;
378065
+ }
378066
+ function validSdtMetadata(value2) {
378067
+ 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");
378068
+ }
378069
+ function projectInlineText(inline) {
378070
+ if (inline.kind === "text")
378071
+ return [inline];
378072
+ if (inline.kind === "sdt" || inline.kind === "hyperlink")
378073
+ return inline.inlines.flatMap(projectInlineText);
378074
+ return [];
378075
+ }
378076
+ function normalizeInline(value2, path7, allowHyperlink, depth = 0) {
377175
378077
  if (!isRecord15(value2))
377176
378078
  return structureFailure("Paste inlines must be objects.", path7);
378079
+ if (depth > 32)
378080
+ return structureFailure("Paste inline nesting exceeds the allowed depth.", path7);
377177
378081
  const sourcePath = normalizeSourcePath(value2.sourcePath, [...path7, "sourcePath"]);
377178
378082
  if (isPlanFailure(sourcePath))
377179
378083
  return sourcePath;
378084
+ if (value2.kind === "sdt") {
378085
+ if (value2.controlType !== "text" && value2.controlType !== "richText" || !validSdtMetadata(value2) || !Array.isArray(value2.inlines) || value2.inlines.length === 0) {
378086
+ return structureFailure("Content controls require a supported type and nonempty inlines.", path7);
378087
+ }
378088
+ for (const key2 of ["tag", "alias"]) {
378089
+ if (value2[key2] !== undefined && (typeof value2[key2] !== "string" || value2[key2].length > 255 || !isXml10SafeAttributeValue(value2[key2]))) {
378090
+ return structureFailure(`Content control ${key2} must be XML-safe and at most 255 characters.`, [...path7, key2]);
378091
+ }
378092
+ }
378093
+ const inlines = [];
378094
+ for (const [index2, child] of value2.inlines.entries()) {
378095
+ const normalized = normalizeInline(child, [...path7, "inlines", index2], false, depth + 1);
378096
+ if (isPlanFailure(normalized))
378097
+ return normalized;
378098
+ if (normalized.kind !== "text" && normalized.kind !== "lineBreak" && normalized.kind !== "sdt") {
378099
+ return structureFailure("Unsupported content inside a content control.", [...path7, "inlines", index2]);
378100
+ }
378101
+ inlines.push(normalized);
378102
+ }
378103
+ return {
378104
+ kind: "sdt",
378105
+ controlType: value2.controlType,
378106
+ inlines,
378107
+ ...typeof value2.tag === "string" ? { tag: value2.tag } : {},
378108
+ ...typeof value2.alias === "string" ? { alias: value2.alias } : {},
378109
+ ...value2.lockMode !== undefined ? { lockMode: value2.lockMode } : {},
378110
+ ...value2.multiline !== undefined ? { multiline: value2.multiline } : {},
378111
+ ...sourcePath ? { sourcePath } : {}
378112
+ };
378113
+ }
377180
378114
  if (value2.kind === "text" && typeof value2.text === "string" && isXml10SafeString(value2.text)) {
377181
378115
  if (value2.marks !== undefined && (!isRecord15(value2.marks) || Array.isArray(value2.marks))) {
377182
378116
  return structureFailure("Paste run marks must be an object.", [...path7, "marks"]);
@@ -377234,8 +378168,8 @@ function normalizeInline(value2, path7, allowHyperlink) {
377234
378168
  const normalized = normalizeInline(child, [...path7, "inlines", childIndex], false);
377235
378169
  if (isPlanFailure(normalized))
377236
378170
  return normalized;
377237
- if (normalized.kind === "hyperlink")
377238
- return structureFailure("Nested hyperlinks are not supported.", [...path7, "inlines", childIndex]);
378171
+ if (normalized.kind === "hyperlink" || normalized.kind === "sdt")
378172
+ return structureFailure("Nested hyperlinks and content controls are not supported.", [...path7, "inlines", childIndex]);
377239
378173
  if (normalized.kind === "footnoteRef" || normalized.kind === "endnoteRef") {
377240
378174
  return structureFailure("Note references cannot be nested inside hyperlinks.", [...path7, "inlines", childIndex]);
377241
378175
  }
@@ -377680,6 +378614,9 @@ function normalizeTableBlock(block, index2, version2) {
377680
378614
  const paragraph4 = normalizeParagraphBlock(cellBlock, ["blocks", index2, "rows", rowIndex, "cells", cellIndex, "blocks", cellBlockIndex], version2, false);
377681
378615
  if (isPlanFailure(paragraph4))
377682
378616
  return paragraph4;
378617
+ if (paragraph4.blockSdts?.length) {
378618
+ return structureFailure("Block content controls inside table cells are not supported.", ["blocks", index2, "rows", rowIndex, "cells", cellIndex, "blocks", cellBlockIndex, "blockSdts"]);
378619
+ }
377683
378620
  cellBlocks.push(paragraph4);
377684
378621
  }
377685
378622
  cells.push({
@@ -378437,7 +379374,8 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378437
379374
  code: "PRECONDITION_FAILED",
378438
379375
  message: "story part bytes are unavailable."
378439
379376
  };
378440
- const boundaryMap = buildStructuralBoundaryMap(bytes.subarray(0, endEntry.byteEnd));
379377
+ const boundaryMap = buildStructuralBoundaryMap(bytes);
379378
+ const blockSdtWrappers = scanBlockSdtWrappers(SERIALIZE_TD.decode(bytes));
378441
379379
  const hyperlinkDescriptorResolver = new SelectionHyperlinkDescriptorResolver(context, resolved.storyId);
378442
379380
  let tableCatalog;
378443
379381
  const notes = new SerializeNotes;
@@ -378500,6 +379438,7 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378500
379438
  loadCatalog: loadCommentsCatalog
378501
379439
  };
378502
379440
  const tableCellPath = boundaryMap.byBlockId(entry.blockId)?.tableCellPath ?? [];
379441
+ const blockSdtPath = boundaryMap.byBlockId(entry.blockId)?.blockSdtPath ?? [];
378503
379442
  const properties = readParagraphProperties(paragraphXml);
378504
379443
  const fullyCovered = from === 0 && to === map3.visibleLength && (options?.wholeParagraphs === true || startEntry.ordinal !== endEntry.ordinal || properties.styleRef !== undefined || properties.numbering !== undefined || hasParagraphProperties(paragraphXml));
378505
379444
  const styleModel = copyStyleModel ?? (copyStyleModel = compileCopyStyleModel(context));
@@ -378508,7 +379447,23 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378508
379447
  const semanticHeadingLevel = semanticStyleRole?.startsWith("heading-") ? Number(semanticStyleRole.slice("heading-".length)) : null;
378509
379448
  const parts = [];
378510
379449
  let objectOrdinal = 0;
378511
- for (const piece of map3.leaves()) {
379450
+ for (const piece of copySelectionPieces(map3.pieces)) {
379451
+ if (piece.kind === "container" && piece.containerKind === "sdt") {
379452
+ const sdt = from <= piece.visibleStart && piece.visibleEnd <= to ? serializeSdtPart(piece, from, to, notes, styleContext, review) : null;
379453
+ if (sdt) {
379454
+ parts.push(sdt);
379455
+ continue;
379456
+ }
379457
+ for (const leaf of copySelectionPieces(piece.children, false)) {
379458
+ if (leaf.visibleStart >= to || leaf.visibleEnd <= from)
379459
+ continue;
379460
+ notes.add("fields", "Content-control and field structure is not preserved by copy; visible text was kept.");
379461
+ const leafParts = [];
379462
+ walkPieces([leaf], from, to, leafParts, notes, styleContext, review);
379463
+ parts.push(...leafParts);
379464
+ }
379465
+ continue;
379466
+ }
378512
379467
  if (isVisibleZeroWidthObjectPiece(piece)) {
378513
379468
  const selected = fullyCovered || selectedObjectOrdinals?.has(objectOrdinal);
378514
379469
  objectOrdinal += 1;
@@ -378588,8 +379543,18 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378588
379543
  }
378589
379544
  const pPrXml = fullyCovered ? materializeEffectivePPrXml(capturedPPrXml, properties.styleRef, copyParagraphPropertyModel ?? (copyParagraphPropertyModel = compileCopyParagraphPropertyXmlModel(context)), inTable ? "table" : "full", resolveRunMarks(extractParagraphMarkRPrXml(capturedPPrXml), styleContext), numbering != null) : null;
378590
379545
  const listLevel = numbering ? Number.parseInt(numbering.ilvl, 10) : undefined;
379546
+ const blockSdts = blockSdtPath.map((ordinal) => {
379547
+ const wrapper = blockSdtWrappers.get(ordinal);
379548
+ 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)))
379549
+ return null;
379550
+ return wrapper.properties;
379551
+ });
379552
+ if (blockSdtPath.length > 0 && blockSdts.some((wrapper) => wrapper === null)) {
379553
+ notes.add("block-sdt", "A partial or unsupported block content control was copied as visible text without its wrapper.");
379554
+ }
378591
379555
  blocks.push({
378592
379556
  blockId: entry.blockId,
379557
+ ...blockSdts.length > 0 && blockSdts.every((wrapper) => wrapper !== null) ? { blockSdts } : {},
378593
379558
  runs: parts.flatMap(serializedPartRuns),
378594
379559
  ...fullyCovered && properties.styleRef ? { styleRef: properties.styleRef } : {},
378595
379560
  ...fullyCovered && semanticStyleRole ? { semanticStyleRole } : {},
@@ -378639,14 +379604,16 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378639
379604
  })), notes.diagnostics());
378640
379605
  plan = buildPlan(fallbackFragment, "plain-text", notes.diagnostics(), true);
378641
379606
  } else {
378642
- const fragment3 = withPasteComments(buildFragment(grouped.map(fragmentBlockFor), notes.diagnostics()), commentsById);
379607
+ const hasPreservedControls = blocks.some((block) => !!block.blockSdts?.length || block.parts.some((part) => part.kind === "sdt"));
379608
+ 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();
379609
+ const fragment3 = withPasteComments(buildFragment(grouped.map(fragmentBlockFor), legacyDiagnostics), commentsById);
378643
379610
  items.push({
378644
379611
  type: SUPERDOC_V2_CLIPBOARD_MIME,
378645
379612
  kind: "string",
378646
379613
  data: JSON.stringify(fragment3)
378647
379614
  });
378648
379615
  const assets = [...rich.assetsByPartUri.values()];
378649
- const hasRichInlineStructure = blocks.some((block) => block.parts.some((part) => part.kind === "hyperlink" || part.kind === "image"));
379616
+ const hasRichInlineStructure = blocks.some((block) => !!block.blockSdts?.length || block.parts.some((part) => part.kind === "hyperlink" || part.kind === "image" || part.kind === "sdt"));
378650
379617
  if (!rich.invalid && hasRichInlineStructure) {
378651
379618
  const richBlocks = grouped.map(richFragmentBlockFor);
378652
379619
  const wireFragment = withPasteComments({
@@ -378670,6 +379637,84 @@ function serializeSelectionRange(context, target, includeHtml, options) {
378670
379637
  }
378671
379638
  };
378672
379639
  }
379640
+ function topLevelChildrenCoverXml(xml, children2) {
379641
+ let cursor = 0;
379642
+ for (const child of children2) {
379643
+ const start = xml.indexOf(child.xml, cursor);
379644
+ if (start < 0 || xml.slice(cursor, start).trim().length > 0)
379645
+ return false;
379646
+ cursor = start + child.xml.length;
379647
+ }
379648
+ return xml.slice(cursor).trim().length === 0;
379649
+ }
379650
+ function parseSupportedSdtProperties(xml) {
379651
+ const children2 = parseTopLevelChildren(xml);
379652
+ if (!topLevelChildrenCoverXml(xml, children2))
379653
+ return null;
379654
+ const properties = { controlType: "richText" };
379655
+ let seenControlType = false;
379656
+ for (const child of children2) {
379657
+ if (child.prefix !== "w")
379658
+ return null;
379659
+ if (child.localName === "id") {
379660
+ if (!/^<w:id\b[^>]*\/>$/.test(child.xml))
379661
+ return null;
379662
+ } else if (child.localName === "tag" || child.localName === "alias") {
379663
+ if (!/^<w:(?:tag|alias)\b[^>]*\/>$/.test(child.xml))
379664
+ return null;
379665
+ const value2 = readBareAttribute(child.xml, "w:val");
379666
+ if (value2 === null || value2.length > 255)
379667
+ return null;
379668
+ properties[child.localName] = value2;
379669
+ } else if (child.localName === "lock") {
379670
+ const match = /^<w:lock\s+w:val=(['"])(unlocked|sdtLocked|contentLocked|sdtContentLocked)\1\s*\/>$/.exec(child.xml);
379671
+ if (!match || properties.lockMode !== undefined)
379672
+ return null;
379673
+ properties.lockMode = match[2];
379674
+ } else if (child.localName === "text") {
379675
+ const match = /^<w:text(?:\s+w:multiLine=(['"])(1|0|true|false|on|off)\1)?\s*\/>$/.exec(child.xml);
379676
+ if (!match || seenControlType)
379677
+ return null;
379678
+ seenControlType = true;
379679
+ properties.controlType = "text";
379680
+ if (match[2] !== undefined)
379681
+ properties.multiline = match[2] === "1" || match[2] === "true" || match[2] === "on";
379682
+ } else if (child.localName === "richText") {
379683
+ if (!/^<w:richText\s*\/>$/.test(child.xml) || seenControlType)
379684
+ return null;
379685
+ seenControlType = true;
379686
+ } else {
379687
+ return null;
379688
+ }
379689
+ }
379690
+ return properties;
379691
+ }
379692
+ function scanBlockSdtWrappers(xml) {
379693
+ const wrappers = new Map;
379694
+ const stack = [];
379695
+ const tokens = /<w:p\b[^>]*>[\s\S]*?<\/w:p\s*>|<w:sdt\b[^>]*>|<\/w:sdt\s*>/g;
379696
+ let ordinal = 0;
379697
+ for (const token of xml.matchAll(tokens)) {
379698
+ if (token[0].startsWith("<w:p"))
379699
+ continue;
379700
+ if (token[0].startsWith("</")) {
379701
+ const frame = stack.pop();
379702
+ if (!frame)
379703
+ continue;
379704
+ const wrapperXml = xml.slice(frame.start, token.index + token[0].length);
379705
+ const propertiesXml = /<w:sdtPr\b[^>]*>([\s\S]*?)<\/w:sdtPr>/.exec(wrapperXml)?.[1];
379706
+ const contentXml = /<w:sdtContent\b[^>]*>([\s\S]*)<\/w:sdtContent>/.exec(wrapperXml)?.[1];
379707
+ const children2 = contentXml === undefined ? [] : parseTopLevelChildren(contentXml);
379708
+ const parsedProperties = propertiesXml === undefined ? null : parseSupportedSdtProperties(propertiesXml);
379709
+ const properties = { sourceId: String(frame.ordinal), ...parsedProperties ?? { controlType: "richText" } };
379710
+ const supported = parsedProperties !== null && contentXml !== undefined && children2.length > 0 && children2.every((child) => child.prefix === "w" && (child.localName === "p" || child.localName === "sdt")) && topLevelChildrenCoverXml(contentXml, children2);
379711
+ wrappers.set(frame.ordinal, { properties, supported });
379712
+ } else {
379713
+ stack.push({ ordinal: ordinal++, start: token.index });
379714
+ }
379715
+ }
379716
+ return wrappers;
379717
+ }
378673
379718
  function serializeSelectedDrawing(input) {
378674
379719
  const fail6 = (key2, message) => {
378675
379720
  input.notes.add(key2, message);
@@ -378842,7 +379887,7 @@ function appendSerializedParts(out, parts, hyperlink) {
378842
379887
  function serializedPartRuns(part) {
378843
379888
  if (part.kind === "run")
378844
379889
  return [part.run];
378845
- if (part.kind === "hyperlink")
379890
+ if (part.kind === "hyperlink" || part.kind === "sdt")
378846
379891
  return part.parts.flatMap(serializedPartRuns);
378847
379892
  return [];
378848
379893
  }
@@ -378868,6 +379913,48 @@ function noteContainingStructures(piece, notes, preservesHyperlink) {
378868
379913
  }
378869
379914
  return supported;
378870
379915
  }
379916
+ function copySelectionPieces(pieces, stopAtSdt = true) {
379917
+ return pieces.flatMap((piece) => {
379918
+ if (piece.kind === "container" && piece.containerKind === "sdt" && stopAtSdt)
379919
+ return [piece];
379920
+ if (piece.kind === "container" || piece.kind === "tracked") {
379921
+ if (piece.kind === "container" && piece.policy === "atomic")
379922
+ return [piece];
379923
+ return copySelectionPieces(piece.children, stopAtSdt);
379924
+ }
379925
+ return [piece];
379926
+ });
379927
+ }
379928
+ function serializeSdtPart(piece, from, to, notes, styleContext, review) {
379929
+ const propertiesXml = /<w:sdtPr\b[^>]*>([\s\S]*?)<\/w:sdtPr>/.exec(piece.rawXml)?.[1];
379930
+ if (propertiesXml === undefined)
379931
+ return null;
379932
+ const properties = parseSupportedSdtProperties(propertiesXml);
379933
+ if (!properties)
379934
+ return null;
379935
+ const parts = [];
379936
+ for (const child of piece.children) {
379937
+ if (child.kind === "container" && child.containerKind === "sdt") {
379938
+ if (child.visibleStart < from || child.visibleEnd > to)
379939
+ return null;
379940
+ const nested = serializeSdtPart(child, from, to, notes, styleContext, review);
379941
+ if (!nested)
379942
+ return null;
379943
+ parts.push(nested);
379944
+ } else if (child.kind === "text" || child.kind === "control") {
379945
+ if (child.kind === "text" && child.textRole !== "visible")
379946
+ return null;
379947
+ const leaves = [];
379948
+ walkPieces([child], from, to, leaves, notes, styleContext, review);
379949
+ parts.push(...leaves);
379950
+ } else {
379951
+ return null;
379952
+ }
379953
+ }
379954
+ if (!parts.length)
379955
+ return null;
379956
+ return { kind: "sdt", ...properties, parts };
379957
+ }
378871
379958
 
378872
379959
  class SerializeNotes {
378873
379960
  constructor() {
@@ -379479,10 +380566,34 @@ function paragraphFragmentFor(block) {
379479
380566
  function richParagraphFragmentFor(block) {
379480
380567
  return {
379481
380568
  ...paragraphFragmentFor(block),
380569
+ ...block.blockSdts ? { blockSdts: block.blockSdts } : {},
379482
380570
  inlines: block.parts.flatMap(serializedPartInlines)
379483
380571
  };
379484
380572
  }
380573
+ function serializedSdtChildInline(part) {
380574
+ if (part.kind === "sdt")
380575
+ return {
380576
+ kind: "sdt",
380577
+ controlType: part.controlType,
380578
+ ...part.tag !== undefined ? { tag: part.tag } : {},
380579
+ ...part.alias !== undefined ? { alias: part.alias } : {},
380580
+ ...part.lockMode !== undefined ? { lockMode: part.lockMode } : {},
380581
+ ...part.multiline !== undefined ? { multiline: part.multiline } : {},
380582
+ inlines: part.parts.map(serializedSdtChildInline)
380583
+ };
380584
+ return serializedLeafPartInline(part);
380585
+ }
379485
380586
  function serializedPartInlines(part) {
380587
+ if (part.kind === "sdt")
380588
+ return [{
380589
+ kind: "sdt",
380590
+ controlType: part.controlType,
380591
+ ...part.tag !== undefined ? { tag: part.tag } : {},
380592
+ ...part.alias !== undefined ? { alias: part.alias } : {},
380593
+ ...part.lockMode !== undefined ? { lockMode: part.lockMode } : {},
380594
+ ...part.multiline !== undefined ? { multiline: part.multiline } : {},
380595
+ inlines: part.parts.map(serializedSdtChildInline)
380596
+ }];
379486
380597
  if (part.kind !== "hyperlink")
379487
380598
  return [serializedLeafPartInline(part)];
379488
380599
  return [
@@ -379540,7 +380651,7 @@ function serializedPartPlainText(part) {
379540
380651
  `;
379541
380652
  if (part.kind === "run")
379542
380653
  return part.run.text;
379543
- if (part.kind === "hyperlink")
380654
+ if (part.kind === "hyperlink" || part.kind === "sdt")
379544
380655
  return part.parts.map(serializedPartPlainText).join("");
379545
380656
  return "";
379546
380657
  }
@@ -379564,6 +380675,8 @@ function renderHtmlPart(part) {
379564
380675
  return renderHtmlRun(part.run);
379565
380676
  if (part.kind === "image")
379566
380677
  return "";
380678
+ if (part.kind === "sdt")
380679
+ return part.parts.map(renderHtmlPart).join("");
379567
380680
  const href = part.target.kind === "external" ? part.target.url : `#${part.target.anchor}`;
379568
380681
  const title = part.tooltip ? ` title="${escapeHtmlAttribute3(part.tooltip)}"` : "";
379569
380682
  return `<a href="${escapeHtmlAttribute3(href)}"${title}>${part.parts.map(renderHtmlPart).join("")}</a>`;
@@ -380720,8 +381833,8 @@ function materializeMetadataAnchor(documentXml, sdt, id) {
380720
381833
  const para = findParagraphContaining2(documentXml, sdt.start);
380721
381834
  if (!para)
380722
381835
  return null;
380723
- const startOffset = extractText4(para.xml.slice(0, sdt.start - para.start)).length;
380724
- const length = extractText4(unwrapSdt(sdt.xml) ?? sdt.xml).length;
381836
+ const startOffset = extractVisibleTextFromXml2(para.xml.slice(0, sdt.start - para.start)).length;
381837
+ const length = extractVisibleTextFromXml2(unwrapSdt(sdt.xml) ?? sdt.xml).length;
380725
381838
  const endOffset = startOffset + length;
380726
381839
  return {
380727
381840
  id,
@@ -380906,7 +382019,7 @@ function wrapTextRangeWithMetadataSdt(paragraphXml, start, end, id, sdtId) {
380906
382019
  }
380907
382020
  function metadataSdtOpenXml(id, sdtId) {
380908
382021
  return [
380909
- "<w:sdt><w:sdtPr>",
382022
+ `<w:sdt xmlns:w15="${W15_NS}"><w:sdtPr>`,
380910
382023
  `<w:id w:val="${sdtId}"/>`,
380911
382024
  `<w:alias w:val="${xmlEscapeAttr(METADATA_ANCHOR_ALIAS)}"/>`,
380912
382025
  `<w:tag w:val="${xmlEscapeAttr(id)}"/>`,
@@ -381003,13 +382116,6 @@ function unwrapSdt(sdtXml) {
381003
382116
  }
381004
382117
  return null;
381005
382118
  }
381006
- function extractText4(xml) {
381007
- let out = "";
381008
- const re = /<w:t\b[^>]*>([\s\S]*?)<\/w:t>/g;
381009
- for (let match = re.exec(xml);match; match = re.exec(xml))
381010
- out += decodeXmlEntities(match[1]);
381011
- return out;
381012
- }
381013
382119
  function replaceRange3(xml, start, end, replacement) {
381014
382120
  return `${xml.slice(0, start)}${replacement}${xml.slice(end)}`;
381015
382121
  }
@@ -381042,6 +382148,7 @@ var TEXT_ENCODER2, METADATA_ANCHOR_ALIAS = "Anchored metadata", W15_NS = "http:/
381042
382148
  var init_adapter38 = __esm(() => {
381043
382149
  init_src3();
381044
382150
  init_sd_document_session_internal();
382151
+ init_visible_text();
381045
382152
  init_inline();
381046
382153
  init_adapter2();
381047
382154
  init_shared();