wordinweb 0.3.4 → 0.3.6

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.
@@ -2131,6 +2131,22 @@ function serializeXml(el26, declaration = false) {
2131
2131
  write2(el26);
2132
2132
  return out.join("");
2133
2133
  }
2134
+ function isEmptyPlaceholder(e) {
2135
+ return !!e.omitWhenEmpty && Object.keys(e.attrs).length === 0 && e.text.length === 0 && e.children.length === 1 && localName(e.children[0].name) === "t" && e.children[0].children.length === 0 && e.children[0].text.length === 0 && Object.keys(e.children[0].attrs).every((key) => key === "xml:space" && e.children[0].attrs[key] === "preserve");
2136
+ }
2137
+ function sameXml(a, b) {
2138
+ if (a.name !== b.name) return false;
2139
+ const ak = Object.keys(a.attrs);
2140
+ if (ak.length !== Object.keys(b.attrs).length) return false;
2141
+ for (const k of ak) if (a.attrs[k] !== b.attrs[k]) return false;
2142
+ if (a.children.length === 0 && b.children.length === 0) return a.text === b.text;
2143
+ const ac = a.children.filter((c) => !isEmptyPlaceholder(c));
2144
+ const bc = b.children.filter((c) => !isEmptyPlaceholder(c));
2145
+ if (ac.length !== bc.length) return false;
2146
+ if (ac.length === 0) return (a.children.length > 0 ? "" : a.text) === (b.children.length > 0 ? "" : b.text);
2147
+ for (let i = 0; i < ac.length; i++) if (!sameXml(ac[i], bc[i])) return false;
2148
+ return true;
2149
+ }
2134
2150
  function cloneXml(el26) {
2135
2151
  return {
2136
2152
  name: el26.name,
@@ -2176,6 +2192,12 @@ var Package = class _Package {
2176
2192
  static from(data) {
2177
2193
  return new _Package(data instanceof Uint8Array ? data : new Uint8Array(data));
2178
2194
  }
2195
+ /** A package over an already-unzipped entry map (DocxDocument.clone). */
2196
+ static fromFiles(files) {
2197
+ const pkg = Object.create(_Package.prototype);
2198
+ pkg.files = files;
2199
+ return pkg;
2200
+ }
2179
2201
  has(name) {
2180
2202
  return this.normalize(name) in this.files;
2181
2203
  }
@@ -2198,6 +2220,13 @@ var Package = class _Package {
2198
2220
  return name.startsWith("/") ? name.slice(1) : name;
2199
2221
  }
2200
2222
  };
2223
+ function relativePartPath(fromPart, toPart) {
2224
+ const fromDir = fromPart.split("/").slice(0, -1);
2225
+ const to = toPart.split("/");
2226
+ let i = 0;
2227
+ while (i < fromDir.length && i < to.length - 1 && fromDir[i] === to[i]) i++;
2228
+ return [...fromDir.slice(i).map(() => ".."), ...to.slice(i)].join("/");
2229
+ }
2201
2230
  function resolvePartPath(sourcePart, target) {
2202
2231
  if (target.startsWith("/")) return target.slice(1);
2203
2232
  const baseDir = sourcePart.includes("/") ? sourcePart.slice(0, sourcePart.lastIndexOf("/")) : "";
@@ -6355,6 +6384,10 @@ function parseCell(tc, ctx) {
6355
6384
  if (tcW && attr(tcW, "type") === "dxa") {
6356
6385
  const w = intAttr(tcW, "w");
6357
6386
  if (w) props.width = twipsToPx(w);
6387
+ } else if (tcW && attr(tcW, "type") === "pct") {
6388
+ const raw = attr(tcW, "w");
6389
+ const num = raw !== void 0 ? parseFloat(raw) : NaN;
6390
+ if (Number.isFinite(num) && num > 0) props.widthPct = raw.trim().endsWith("%") ? num / 100 : num / 5e3;
6358
6391
  } else if (tcW && attr(tcW, "type") === "auto") {
6359
6392
  props.widthAuto = true;
6360
6393
  }
@@ -8794,6 +8827,10 @@ var _DocxDocument = class _DocxDocument {
8794
8827
  /** The header part roots, in package order. A watermark is authored into
8795
8828
  * every one of them: Word paints it on every page, and a document with a
8796
8829
  * first-page or even-page header has more than one header part. */
8830
+ /** The root of the header or footer part at `target`, if this document has one. */
8831
+ headerFooterRoot(target) {
8832
+ return this.hfParts.find((p) => p.target === target)?.root;
8833
+ }
8797
8834
  headerRoots() {
8798
8835
  return this.hfParts.filter((p) => p.isHeader).map((p) => p.root);
8799
8836
  }
@@ -8957,6 +8994,22 @@ var _DocxDocument = class _DocxDocument {
8957
8994
  this.saveJournal = null;
8958
8995
  }
8959
8996
  }
8997
+ /**
8998
+ * An independent copy: what `DocxDocument.load(doc.save())` gives, without
8999
+ * zipping the package and unzipping it again. compareDocuments starts from a
9000
+ * copy of the revised document, and the round trip through the zip was a
9001
+ * third of its time on a document that had not changed at all.
9002
+ */
9003
+ clone() {
9004
+ const journal = [];
9005
+ this.saveJournal = journal;
9006
+ try {
9007
+ return new _DocxDocument(Package.fromFiles(this.buildPackageFiles()));
9008
+ } finally {
9009
+ for (let i = journal.length - 1; i >= 0; i--) journal[i]();
9010
+ this.saveJournal = null;
9011
+ }
9012
+ }
8960
9013
  saveAsync() {
8961
9014
  const journal = [];
8962
9015
  this.saveJournal = journal;
@@ -9158,8 +9211,91 @@ var _DocxDocument = class _DocxDocument {
9158
9211
  return this.relsRoot;
9159
9212
  }
9160
9213
  /** Register an external hyperlink relationship and return its rId. */
9161
- addHyperlinkRel(url) {
9162
- const rels = this.ensureRelsRoot();
9214
+ /**
9215
+ * Copy the part that `source`'s main-document relationship `relId` points
9216
+ * at into this package, with every part it references in turn (a chart's
9217
+ * embedded workbook, SmartArt's layout, style and colour parts) and their
9218
+ * content types, and add a relationship to it. Returns the new
9219
+ * relationship id, or null when the source has no such internal part.
9220
+ *
9221
+ * compareDocuments needs this for struck content: a removed picture keeps
9222
+ * its w:drawing, and without its image part the result's reject-all
9223
+ * brought the picture back as an empty frame.
9224
+ */
9225
+ adoptRelFrom(source, relId, scope = {}) {
9226
+ const rel = source.relationshipsOf(scope.from).get(relId);
9227
+ if (!rel || rel.external) return null;
9228
+ const copied = /* @__PURE__ */ new Map();
9229
+ const sameBytes = (path2, bytes) => {
9230
+ const have = this.pkg.binary(path2);
9231
+ return !!have && have.length === bytes.length && have.every((b, i) => b === bytes[i]);
9232
+ };
9233
+ const copyPart = (from) => {
9234
+ const known = copied.get(from);
9235
+ if (known) return known;
9236
+ const bytes = source.pkg.binary(from);
9237
+ if (!bytes) return null;
9238
+ let to = from;
9239
+ for (let n = 2; this.pkg.has(to) && !sameBytes(to, bytes); n++) to = from.replace(/(\.[^./]+)?$/, `-${n}$1`);
9240
+ copied.set(from, to);
9241
+ if (this.pkg.has(to)) return to;
9242
+ this.pkg.raw()[to] = bytes;
9243
+ this.adoptContentType(source, from, to);
9244
+ const relsXml = source.pkg.text(relsPathFor(from));
9245
+ if (relsXml) {
9246
+ const root = parseXml(relsXml);
9247
+ for (const r of root.children) {
9248
+ if (r.attrs.TargetMode === "External" || !r.attrs.Target) continue;
9249
+ const sub = copyPart(resolvePartPath(from, r.attrs.Target));
9250
+ if (sub) r.attrs.Target = relativePartPath(to, sub);
9251
+ }
9252
+ this.pkg.raw()[relsPathFor(to)] = strToU82(serializeXml(root, true));
9253
+ }
9254
+ return to;
9255
+ };
9256
+ const part = copyPart(rel.target);
9257
+ if (!part) return null;
9258
+ const owner = this.relsOwnerOf(scope.into);
9259
+ const rels = owner.relsRoot;
9260
+ let maxId = 0;
9261
+ for (const r of rels.children) {
9262
+ const m = /^rId(\d+)$/.exec(r.attrs["Id"] ?? "");
9263
+ if (m) maxId = Math.max(maxId, parseInt(m[1], 10));
9264
+ }
9265
+ const id = `rId${maxId + 1}`;
9266
+ rels.children.push({
9267
+ name: "Relationship",
9268
+ attrs: { Id: id, Type: rel.type, Target: relativePartPath(owner.partPath, part) },
9269
+ children: [],
9270
+ text: ""
9271
+ });
9272
+ owner.relationships.set(id, { id, type: rel.type, target: part, external: false });
9273
+ return id;
9274
+ }
9275
+ /** Give a part copied from `source` the content type it had there. */
9276
+ adoptContentType(source, from, to) {
9277
+ const types = this.contentTypesRoot;
9278
+ const theirs = source.contentTypesRoot;
9279
+ if (!types || !theirs) return;
9280
+ const override = theirs.children.find((c) => localName(c.name) === "Override" && c.attrs.PartName === `/${from}`);
9281
+ const prefixEnd = types.name.indexOf(":") + 1;
9282
+ const prefix = prefixEnd > 0 ? types.name.slice(0, prefixEnd) : "";
9283
+ if (override) {
9284
+ if (!types.children.some((c) => localName(c.name) === "Override" && c.attrs.PartName === `/${to}`)) {
9285
+ types.children.push({ name: `${prefix}Override`, attrs: { PartName: `/${to}`, ContentType: override.attrs.ContentType }, children: [], text: "" });
9286
+ }
9287
+ return;
9288
+ }
9289
+ const ext = from.slice(from.lastIndexOf(".") + 1).toLowerCase();
9290
+ const byExt = (root) => root.children.find((c) => localName(c.name) === "Default" && c.attrs.Extension?.toLowerCase() === ext);
9291
+ const fallback = byExt(theirs);
9292
+ if (fallback && !byExt(types)) {
9293
+ types.children.unshift({ name: `${prefix}Default`, attrs: { Extension: ext, ContentType: fallback.attrs.ContentType }, children: [], text: "" });
9294
+ }
9295
+ }
9296
+ addHyperlinkRel(url, ownerPart) {
9297
+ const owner = this.relsOwnerOf(ownerPart);
9298
+ const rels = owner.relsRoot;
9163
9299
  let maxId = 0;
9164
9300
  for (const r of rels.children) {
9165
9301
  const m = /^rId(\d+)$/.exec(r.attrs["Id"] ?? "");
@@ -9177,7 +9313,7 @@ var _DocxDocument = class _DocxDocument {
9177
9313
  children: [],
9178
9314
  text: ""
9179
9315
  });
9180
- this.documentRels.set(id, { id, type: "hyperlink", target: url, external: true });
9316
+ owner.relationships.set(id, { id, type: "hyperlink", target: url, external: true });
9181
9317
  return id;
9182
9318
  }
9183
9319
  /** Retarget an existing external relationship (hyperlink href edit). */
@@ -9189,6 +9325,23 @@ var _DocxDocument = class _DocxDocument {
9189
9325
  if (el26) el26.attrs["Target"] = url;
9190
9326
  return true;
9191
9327
  }
9328
+ /** The relationships owned by `part` (a header or footer part), or by the
9329
+ * document part when null; a header or footer gets a rels root on demand. */
9330
+ relsOwnerOf(part) {
9331
+ const owner = part ? this.hfParts.find((p) => p.target === part) : void 0;
9332
+ if (!owner) return { relsRoot: this.ensureRelsRoot(), relationships: this.documentRels, partPath: this.docPart };
9333
+ owner.relsRoot ?? (owner.relsRoot = {
9334
+ name: "Relationships",
9335
+ attrs: { xmlns: "http://schemas.openxmlformats.org/package/2006/relationships" },
9336
+ children: [],
9337
+ text: ""
9338
+ });
9339
+ return { relsRoot: owner.relsRoot, relationships: owner.rels, partPath: owner.target };
9340
+ }
9341
+ /** The relationships of `part` (a header or footer), or of the document part. */
9342
+ relationshipsOf(part) {
9343
+ return (part ? this.hfParts.find((p) => p.target === part)?.rels : void 0) ?? this.documentRels;
9344
+ }
9192
9345
  /** The relationships of the part that holds `source`: a header or footer
9193
9346
  * part when the element is inside one, else the document part. */
9194
9347
  imageRelsOwner(source) {
@@ -10556,7 +10709,11 @@ var WORD_FONT_METRICS = {
10556
10709
  // lineGap 99 / upem 2048 = 1.2734em (14.0pt at 11pt - phase23's sub-bullet
10557
10710
  // lists run 14.0pt/line among 13.5pt Calibri text).
10558
10711
  symbol: { asc: 1.005371, desc: 0.219727, gap: 0.04834 },
10559
- symbolmt: { asc: 1.005371, desc: 0.219727, gap: 0.04834 },
10712
+ // A Symbol bullet LABEL (the engine's metrics face) sizes its line without
10713
+ // the lineGap: Word runs an 11pt bullet at 13.5pt among 11pt Times New
10714
+ // Roman (wild-termsheet-behinddoc) and a 10pt one at 12.25pt, both
10715
+ // asc + desc; with the gap they would be 14.0pt and 12.75pt.
10716
+ symbolmt: { asc: 1.005371, desc: 0.219727, gap: 0 },
10560
10717
  "pingfang sc": { asc: 2.27, desc: 0.7627, gap: 0 },
10561
10718
  "songti sc": { asc: 2.27, desc: 0.7627, gap: 0 },
10562
10719
  "heiti sc": { asc: 2.27, desc: 0.7627, gap: 0 },
@@ -10583,13 +10740,19 @@ function quantizeQuarterPt(px) {
10583
10740
  return Math.round(px / QUARTER_PT_PX) * QUARTER_PT_PX;
10584
10741
  }
10585
10742
  var FACE_SUFFIX = /\s+(bold italic|italic bold|bold|italic)$/i;
10743
+ var WORD_REPLACES = {
10744
+ "liberation serif": "Times New Roman",
10745
+ "liberation sans": "Arial",
10746
+ "liberation mono": "Courier New"
10747
+ };
10586
10748
  function normalizeFamily(family, bold, italic) {
10587
10749
  if (family === "\u5B8B\u4F53") family = "SimSun";
10750
+ const replaced = (f) => WORD_REPLACES[f.toLowerCase()] ?? f;
10588
10751
  const m = FACE_SUFFIX.exec(family);
10589
- if (!m) return { family, bold, italic };
10752
+ if (!m) return { family: replaced(family), bold, italic };
10590
10753
  const suffix = m[1].toLowerCase();
10591
10754
  return {
10592
- family: family.slice(0, m.index).trim() || family,
10755
+ family: replaced(family.slice(0, m.index).trim() || family),
10593
10756
  bold: bold || suffix.includes("bold"),
10594
10757
  italic: italic || suffix.includes("italic")
10595
10758
  };
@@ -18641,7 +18804,9 @@ var Engine = class {
18641
18804
  if (tbl.props.layout === "fixed") return base;
18642
18805
  const gridTotal = tbl.grid.reduce((a, b) => a + b, 0);
18643
18806
  const target = base.reduce((a, b) => a + b, 0);
18644
- const cellsDeclareWidths = tbl.rows.some((r) => r.cells.some((c) => c.props.width !== void 0));
18807
+ const cellsDeclareWidths = tbl.rows.some(
18808
+ (r) => r.cells.some((c) => c.props.width !== void 0 || c.props.widthPct !== void 0)
18809
+ );
18645
18810
  const containsHyperlink = tbl.rows.some(
18646
18811
  (row) => row.cells.some(
18647
18812
  (cell) => cell.blocks.some(
@@ -19216,15 +19381,20 @@ var Engine = class {
19216
19381
  while (out.length < nCols) out.push(bw(fallback));
19217
19382
  return out;
19218
19383
  };
19384
+ const colTopPads = (r) => {
19385
+ const fallback = this.cellMarginsOf(tbl, false).top ?? 0;
19386
+ return rows[r].cells.flatMap((c) => Array(Math.max(1, c.props.gridSpan)).fill(c.props.margins?.top ?? fallback));
19387
+ };
19219
19388
  const boundary = (k) => {
19220
19389
  if (k === 0) return Math.max(0, ...colWidths(0, "top", tb?.top));
19221
19390
  if (k === rows.length)
19222
19391
  return Math.max(0, ...colWidths(rows.length - 1, "bottom", tb?.bottom));
19223
19392
  const above = colWidths(k - 1, "bottom", tb?.insideH);
19224
19393
  const below = colWidths(k, "top", tb?.insideH);
19394
+ const topPads = colTopPads(k);
19225
19395
  const n = Math.max(above.length, below.length);
19226
19396
  let w = 0;
19227
- for (let i = 0; i < n; i++) w = Math.max(w, Math.max(above[i] ?? 0, below[i] ?? 0));
19397
+ for (let i = 0; i < n; i++) w = Math.max(w, below[i] ?? 0, (above[i] ?? 0) - (topPads[i] ?? 0));
19228
19398
  return w;
19229
19399
  };
19230
19400
  return { top: boundary(ri), bottom: boundary(ri + 1) };
@@ -19800,7 +19970,14 @@ var Engine = class {
19800
19970
  if (boundaryPara === void 0) continue;
19801
19971
  const above = keptTops.filter((t) => paraOfTop(part.keep, t) === boundaryPara);
19802
19972
  const below = restTops.filter((t) => paraOfTop(part.rest, t) === boundaryPara);
19803
- if (below.length !== 1 || above.length === 0) continue;
19973
+ if (above.length === 0) continue;
19974
+ if (above.length === 1 && below.length > 1) {
19975
+ const moved2 = part.keep.filter((it) => topOf(it) >= above[0] - 0.01);
19976
+ part.keep = part.keep.filter((it) => topOf(it) < above[0] - 0.01);
19977
+ part.rest = [...moved2, ...part.rest];
19978
+ continue;
19979
+ }
19980
+ if (below.length !== 1) continue;
19804
19981
  const moveTop = above.length >= 3 ? above[above.length - 1] : above[0];
19805
19982
  const moved = part.keep.filter((it) => topOf(it) >= moveTop - 0.01);
19806
19983
  part.keep = part.keep.filter((it) => topOf(it) < moveTop - 0.01);
@@ -21423,6 +21600,8 @@ function renderPageShell(page, zoom, options) {
21423
21600
  if (options.pageShadow !== false) {
21424
21601
  el26.style.boxShadow = "var(--dxw-page-shadow, 0 1px 3px rgba(0,0,0,.28), 0 4px 14px rgba(0,0,0,.12))";
21425
21602
  }
21603
+ el26.dataset.pageWidth = String(page.width);
21604
+ el26.dataset.pageHeight = String(page.height);
21426
21605
  el26.dataset.bodyTop = String(page.bodyTop);
21427
21606
  el26.dataset.bodyBottom = String(page.bodyBottom);
21428
21607
  el26.dataset.columnBands = JSON.stringify(page.columnBands);
@@ -21739,9 +21918,21 @@ function trackPaint(work) {
21739
21918
  async function paintsSettled() {
21740
21919
  while (pendingPaints.size > 0) await Promise.allSettled([...pendingPaints]);
21741
21920
  }
21742
- function printStylesheet(pageWidthPx, pageHeightPx) {
21921
+ function printStylesheet(pageWidthPx, pageHeightPx, root) {
21922
+ const inches = (px) => `${Math.round(px / 96 * 1e3) / 1e3}in`;
21923
+ const sizes = /* @__PURE__ */ new Set();
21924
+ for (const el26 of root ? Array.from(root.querySelectorAll(".dxw-page[data-page-width]")) : []) {
21925
+ sizes.add(`${el26.dataset.pageWidth}x${el26.dataset.pageHeight}`);
21926
+ }
21927
+ const named = [...sizes].map((key) => {
21928
+ const [w, h] = key.split("x");
21929
+ const name = `dxw-${key.replace(/\./g, "_")}`;
21930
+ return `@page ${name} { size: ${inches(Number(w))} ${inches(Number(h))}; margin: 0; }
21931
+ .dxw-page[data-page-width="${w}"][data-page-height="${h}"] { page: ${name}; }`;
21932
+ });
21743
21933
  return `
21744
- @page { size: ${pageWidthPx / 96}in ${pageHeightPx / 96}in; margin: 0; }
21934
+ @page { size: ${inches(pageWidthPx)} ${inches(pageHeightPx)}; margin: 0; }
21935
+ ${named.join("\n ")}
21745
21936
  html, body { margin: 0; padding: 0; }
21746
21937
  .dxw-pages { display: block !important; padding: 0 !important; gap: 0 !important; }
21747
21938
  .dxw-pages, .dxw-pages * {
@@ -21752,13 +21943,25 @@ function printStylesheet(pageWidthPx, pageHeightPx) {
21752
21943
  .dxw-comment-card, .dxw-hf-marker { display: none !important; }
21753
21944
  `;
21754
21945
  }
21946
+ function pruneBlankSpans(root) {
21947
+ let removed = 0;
21948
+ for (const el26 of Array.from(root.querySelectorAll(".dxw-page span"))) {
21949
+ if (el26.childElementCount > 0 || el26.textContent?.trim()) continue;
21950
+ if (el26.style.textDecoration.includes("underline")) continue;
21951
+ el26.remove();
21952
+ removed++;
21953
+ }
21954
+ return removed;
21955
+ }
21755
21956
  function buildPrintHtml(root, pageWidthPx, pageHeightPx) {
21756
21957
  const head = [`<base href="${document.location.href}">`];
21757
21958
  for (const node of Array.from(document.head.querySelectorAll("style, link[rel=stylesheet]"))) {
21758
21959
  head.push(node.outerHTML);
21759
21960
  }
21760
- head.push(`<style>${printStylesheet(pageWidthPx, pageHeightPx)}</style>`);
21761
- return `<!doctype html><html><head>${head.join("")}</head><body>${root.outerHTML}</body></html>`;
21961
+ head.push(`<style>${printStylesheet(pageWidthPx, pageHeightPx, root)}</style>`);
21962
+ const pages = root.cloneNode(true);
21963
+ pruneBlankSpans(pages);
21964
+ return `<!doctype html><html><head>${head.join("")}</head><body>${pages.outerHTML}</body></html>`;
21762
21965
  }
21763
21966
  function printPages(root, pageWidthPx, pageHeightPx) {
21764
21967
  const iframe = document.createElement("iframe");
@@ -23805,7 +24008,9 @@ function revisionWrapper(sample, kind, content, meta) {
23805
24008
  }
23806
24009
  function markDeletedText(el26) {
23807
24010
  const walk2 = (e) => {
24011
+ if (localName(e.name) === "txbxContent") return;
23808
24012
  if (localName(e.name) === "t") e.name = e.name.replace(/t$/, "delText");
24013
+ else if (localName(e.name) === "instrText") e.name = e.name.replace(/instrText$/, "delInstrText");
23809
24014
  for (const c of e.children) walk2(c);
23810
24015
  };
23811
24016
  walk2(el26);
@@ -24064,7 +24269,8 @@ function ensurePPrRPr(pEl) {
24064
24269
  let rPr = pPr.children.find((c) => localName(c.name) === "rPr");
24065
24270
  if (!rPr) {
24066
24271
  rPr = el2(`${w}rPr`);
24067
- pPr.children.push(rPr);
24272
+ const at = pPr.children.findIndex((c) => localName(c.name) === "sectPr" || localName(c.name) === "pPrChange");
24273
+ pPr.children.splice(at === -1 ? pPr.children.length : at, 0, rPr);
24068
24274
  }
24069
24275
  return rPr;
24070
24276
  }
@@ -24224,6 +24430,8 @@ function convertDelTextToText(el26) {
24224
24430
  const walk2 = (e) => {
24225
24431
  if (localName(e.name) === "delText") {
24226
24432
  e.name = e.name.replace(/delText$/, "t");
24433
+ } else if (localName(e.name) === "delInstrText") {
24434
+ e.name = e.name.replace(/delInstrText$/, "instrText");
24227
24435
  }
24228
24436
  for (const c of e.children) walk2(c);
24229
24437
  };
@@ -24245,7 +24453,7 @@ function removeEl(doc, el26) {
24245
24453
  parent.children.splice(idx, 1);
24246
24454
  return true;
24247
24455
  }
24248
- var RUN_CONTAINERS = ["hyperlink", "smartTag", "dir", "bdo"];
24456
+ var RUN_CONTAINERS = ["hyperlink", "smartTag", "dir", "bdo", "fldSimple"];
24249
24457
  function removeRevisionAndEmptyHost(doc, el26) {
24250
24458
  const parent = doc.findParentOf(el26);
24251
24459
  if (!removeEl(doc, el26)) return false;
@@ -24254,26 +24462,57 @@ function removeRevisionAndEmptyHost(doc, el26) {
24254
24462
  }
24255
24463
  return true;
24256
24464
  }
24257
- function joinWithNext(doc, pEl) {
24465
+ function joinWithNext(doc, pEl, gone) {
24258
24466
  const parent = doc.findParentOf(pEl);
24259
24467
  if (!parent) return false;
24260
24468
  const idx = parent.children.indexOf(pEl);
24261
- const next = parent.children[idx + 1];
24262
- if (!next || localName(next.name) !== "p") return false;
24469
+ let at = idx + 1;
24470
+ while (at < parent.children.length && MARKERS.includes(localName(parent.children[at].name))) at++;
24471
+ const next = parent.children[at];
24472
+ if (!next || localName(next.name) !== "p") {
24473
+ const goes = gone === "del" ? ["pPr", "del", "moveFrom", "proofErr", ...MOVE_RANGES] : ["pPr", "ins", "moveTo", "proofErr", ...MOVE_RANGES];
24474
+ const stays = MARKERS.filter((m) => m !== "proofErr");
24475
+ const removable = (c) => goes.includes(localName(c.name)) || stays.includes(localName(c.name)) || isEmptyPlaceholder(c) || RUN_CONTAINERS.includes(localName(c.name)) && c.children.every(removable);
24476
+ if (!pEl.children.every(removable)) return false;
24477
+ parent.children.splice(idx, 1, ...pEl.children.filter((c) => stays.includes(localName(c.name))));
24478
+ dropEmptiedControl(doc, parent);
24479
+ return true;
24480
+ }
24481
+ const between = parent.children.slice(idx + 1, at);
24263
24482
  const moved = next.children.filter((c) => localName(c.name) !== "pPr");
24264
- pEl.children.push(...moved);
24265
- parent.children.splice(idx + 1, 1);
24483
+ pEl.children.push(...between, ...moved);
24484
+ parent.children.splice(idx + 1, at - idx);
24485
+ carrySectionBreak(pEl, next);
24266
24486
  return true;
24267
24487
  }
24488
+ var MARKERS = ["bookmarkStart", "bookmarkEnd", "commentRangeStart", "commentRangeEnd", "permStart", "permEnd", "proofErr"];
24489
+ function carrySectionBreak(pEl, next) {
24490
+ const pPrOf = (p) => p.children.find((c) => localName(c.name) === "pPr");
24491
+ const sectOf = (p) => pPrOf(p)?.children.find((c) => localName(c.name) === "sectPr");
24492
+ const mine = sectOf(pEl);
24493
+ const theirs = sectOf(next);
24494
+ if (!mine && !theirs) return;
24495
+ let pPr = pPrOf(pEl);
24496
+ if (!pPr) {
24497
+ pPr = { name: "w:pPr", attrs: {}, children: [], text: "" };
24498
+ pEl.children.unshift(pPr);
24499
+ }
24500
+ pPr.children = pPr.children.filter((c) => c !== mine);
24501
+ if (theirs) {
24502
+ const at = pPr.children.findIndex((c) => localName(c.name) === "pPrChange");
24503
+ pPr.children.splice(at === -1 ? pPr.children.length : at, 0, theirs);
24504
+ }
24505
+ if (pPr.children.length === 0) pEl.children.splice(pEl.children.indexOf(pPr), 1);
24506
+ }
24268
24507
  function collectRevisions(doc) {
24269
24508
  const out = [];
24270
24509
  const walk2 = (el26) => {
24271
24510
  const ln = localName(el26.name);
24272
24511
  if (ln === "pPr") return;
24273
- if (ln === "ins" || ln === "del") {
24512
+ if (ln === "ins" || ln === "del" || ln === "moveTo" || ln === "moveFrom") {
24274
24513
  out.push({
24275
24514
  el: el26,
24276
- kind: ln === "ins" ? "insertion" : "deletion",
24515
+ kind: ln === "ins" || ln === "moveTo" ? "insertion" : "deletion",
24277
24516
  author: attrByLocal(el26, "author")
24278
24517
  });
24279
24518
  }
@@ -24307,10 +24546,10 @@ function collectRevisions(doc) {
24307
24546
  const rPr = pPr?.children.find((c) => localName(c.name) === "rPr");
24308
24547
  for (const c of rPr?.children ?? []) {
24309
24548
  const cn = localName(c.name);
24310
- if (cn === "ins" || cn === "del") {
24549
+ if (cn === "ins" || cn === "del" || cn === "moveTo" || cn === "moveFrom") {
24311
24550
  out.push({
24312
24551
  el: c,
24313
- kind: cn === "ins" ? "markInsertion" : "markDeletion",
24552
+ kind: cn === "ins" || cn === "moveTo" ? "markInsertion" : "markDeletion",
24314
24553
  paragraph: el26,
24315
24554
  author: attrByLocal(c, "author")
24316
24555
  });
@@ -24332,7 +24571,7 @@ function applyAccept(doc, ref) {
24332
24571
  case "markDeletion":
24333
24572
  if (!ref.paragraph) return false;
24334
24573
  removeMarkRevision(ref);
24335
- return joinWithNext(doc, ref.paragraph);
24574
+ return joinWithNext(doc, ref.paragraph, "del");
24336
24575
  case "rowInsertion":
24337
24576
  return removeRowRevision(doc, ref);
24338
24577
  case "rowDeletion":
@@ -24403,7 +24642,7 @@ function applyReject(doc, ref) {
24403
24642
  case "markInsertion":
24404
24643
  if (!ref.paragraph) return false;
24405
24644
  removeMarkRevision(ref);
24406
- return joinWithNext(doc, ref.paragraph);
24645
+ return joinWithNext(doc, ref.paragraph, "ins");
24407
24646
  case "markDeletion":
24408
24647
  return removeMarkRevision(ref);
24409
24648
  case "rowInsertion":
@@ -24454,11 +24693,23 @@ function applyToAll(doc, apply, author) {
24454
24693
  for (const part of held) notes.add(part);
24455
24694
  }
24456
24695
  if (n > 0) {
24696
+ dropSettledMoveRanges(doc);
24457
24697
  doc.markNotePartsChanged([...notes]);
24458
24698
  doc.refresh();
24459
24699
  }
24460
24700
  return n;
24461
24701
  }
24702
+ var MOVE_RANGES = ["moveFromRangeStart", "moveFromRangeEnd", "moveToRangeStart", "moveToRangeEnd"];
24703
+ function dropSettledMoveRanges(doc) {
24704
+ const roots = doc.revisionRoots();
24705
+ const has = (el26) => ["moveFrom", "moveTo"].includes(localName(el26.name)) || el26.children.some(has);
24706
+ if (roots.some(has)) return;
24707
+ const strip = (el26) => {
24708
+ el26.children = el26.children.filter((c) => !MOVE_RANGES.includes(localName(c.name)));
24709
+ for (const c of el26.children) strip(c);
24710
+ };
24711
+ for (const root of roots) strip(root);
24712
+ }
24462
24713
  function removeMarkRevision(ref) {
24463
24714
  const pPr = ref.paragraph?.children.find((c) => localName(c.name) === "pPr");
24464
24715
  const rPr = pPr?.children.find((c) => localName(c.name) === "rPr");
@@ -24490,9 +24741,24 @@ function removeRow(doc, ref) {
24490
24741
  const idx = tbl.children.indexOf(ref.row);
24491
24742
  if (idx === -1) return false;
24492
24743
  tbl.children.splice(idx, 1);
24493
- if (!tbl.children.some((c) => localName(c.name) === "tr")) removeEl(doc, tbl);
24744
+ if (!tbl.children.some((c) => localName(c.name) === "tr")) {
24745
+ const holder = doc.findParentOf(tbl);
24746
+ removeEl(doc, tbl);
24747
+ if (holder) dropEmptiedControl(doc, holder);
24748
+ }
24494
24749
  return true;
24495
24750
  }
24751
+ function dropEmptiedControl(doc, container) {
24752
+ const ln = localName(container.name);
24753
+ const control = ln === "sdtContent" ? doc.findParentOf(container) : ln === "customXml" ? container : void 0;
24754
+ if (!control) return;
24755
+ const left = container.children.filter((c) => localName(c.name) !== "customXmlPr");
24756
+ if (left.some((c) => !MARKERS.includes(localName(c.name)))) return;
24757
+ const holder = doc.findParentOf(control);
24758
+ if (!holder) return;
24759
+ holder.children.splice(holder.children.indexOf(control), 1, ...left.filter((c) => localName(c.name) !== "proofErr"));
24760
+ dropEmptiedControl(doc, holder);
24761
+ }
24496
24762
 
24497
24763
  // ../core/src/edit/blocks.ts
24498
24764
  function el3(name, attrs = {}, children3 = [], text = "") {
@@ -27792,7 +28058,7 @@ function sortTableRows(doc, tblEl, colIdx, order, compare, hasHeader = false) {
27792
28058
  return tc ? cellText(tc).trim() : "";
27793
28059
  };
27794
28060
  const numeric2 = (s) => parseFloat(s.replace(/[^0-9.eE+-]/g, ""));
27795
- const compareRows = (a, b) => {
28061
+ const compareRows2 = (a, b) => {
27796
28062
  const ka = keyOf2(a);
27797
28063
  const kb = keyOf2(b);
27798
28064
  let d;
@@ -27808,7 +28074,7 @@ function sortTableRows(doc, tblEl, colIdx, order, compare, hasHeader = false) {
27808
28074
  }
27809
28075
  return order === "asc" ? d : -d;
27810
28076
  };
27811
- const sorted = [...body].sort(compareRows);
28077
+ const sorted = [...body].sort(compareRows2);
27812
28078
  if (sorted.every((tr, i) => tr === body[i])) return false;
27813
28079
  const slots = body.map((tr) => tblEl.children.indexOf(tr));
27814
28080
  sorted.forEach((tr, i) => {
@@ -42987,6 +43253,15 @@ var COMPARE_TUNING = {
42987
43253
  shortParagraphTokens: 5,
42988
43254
  /** Levenshtein ratio a short paragraph pair must reach instead of minDice. */
42989
43255
  minShortRatio: 0.8,
43256
+ /**
43257
+ * Or the share of words a short pair has in common (Dice over words), which
43258
+ * a one-word edit in a short heading or list item keeps at half or more
43259
+ * ("Bullet one" / "Bullet updated", "Alignment and flow" / "Alignment and
43260
+ * flow as amended") while its character ratio falls under minShortRatio.
43261
+ * Below it, those came out struck and reinserted whole: 99% of the compare
43262
+ * benchmark's whole-paragraph misses were paragraphs under five words.
43263
+ */
43264
+ minShortWordDice: 0.5,
42990
43265
  /**
42991
43266
  * ABSOLUTE floor for the coalescing pass: a matched run shorter than this,
42992
43267
  * sitting between two changes in the inner word diff, is absorbed into the
@@ -43005,15 +43280,19 @@ var COMPARE_TUNING = {
43005
43280
  * next to 1) while a rewritten clause becomes one (3 is small next to 7).
43006
43281
  *
43007
43282
  * Both numbers were settled against test/compare-corpus.test.ts; see the
43008
- * calibration note there for what each fixture pins down.
43283
+ * calibration note there for what each fixture pins down. The ratio was 2
43284
+ * while spaces rode on words: "hardware," and "hardware " differed, so the
43285
+ * rewritten sentence of fixture 3 shared only three words. Matched exactly,
43286
+ * it shares four ("shortage of test hardware") beside seven changed, which
43287
+ * 1.5 absorbs (6 < 7) and 2 does not.
43009
43288
  */
43010
- coalesceRatio: 2
43289
+ coalesceRatio: 1.5
43011
43290
  };
43012
43291
  function normalizeText(s) {
43013
43292
  return s.replace(/\s+/g, " ").trim();
43014
43293
  }
43015
43294
  function wordTokens(s) {
43016
- return s.match(/\S+\s*|\s+/g) ?? [];
43295
+ return s.match(/[\p{L}\p{M}\p{N}]+(?:['\u2019][\p{L}\p{M}\p{N}]+)*|[^\s\p{L}\p{M}\p{N}]|\s+/gu) ?? [];
43017
43296
  }
43018
43297
  function similarityTokens(s) {
43019
43298
  const n = normalizeText(s).toLowerCase();
@@ -43059,14 +43338,16 @@ function similarity(a, b) {
43059
43338
  const nb = normalizeText(b).toLowerCase();
43060
43339
  const longest = Math.max(na.length, nb.length);
43061
43340
  if (longest === 0) return 1;
43062
- return 1 - levenshtein(na, nb) / longest;
43341
+ const ratio = 1 - levenshtein(na, nb) / longest;
43342
+ const words2 = dice(new Set(ta), new Set(tb));
43343
+ return Math.max(words2, ratio >= COMPARE_TUNING.minShortRatio ? ratio : 0);
43063
43344
  }
43064
43345
  return dice(bigrams(ta), bigrams(tb));
43065
43346
  }
43066
43347
  function similarityFloor(a, b) {
43067
43348
  const la = similarityTokens(a).length;
43068
43349
  const lb = similarityTokens(b).length;
43069
- return isShortPair(la, lb) ? COMPARE_TUNING.minShortRatio : COMPARE_TUNING.minDice;
43350
+ return isShortPair(la, lb) ? COMPARE_TUNING.minShortWordDice : COMPARE_TUNING.minDice;
43070
43351
  }
43071
43352
  function isShortPair(la, lb) {
43072
43353
  return Math.min(la, lb) < COMPARE_TUNING.shortParagraphTokens;
@@ -43077,6 +43358,25 @@ var PASSENGERS = /* @__PURE__ */ new Set(["proofErr", "bookmarkStart", "bookmark
43077
43358
  function isPassenger(ln) {
43078
43359
  return PASSENGERS.has(ln);
43079
43360
  }
43361
+ var WORD_CHAR = /[\p{L}\p{M}\p{N}]/u;
43362
+ var CONTAINERS = /* @__PURE__ */ new Set(["hyperlink", "smartTag", "fldSimple", "customXml", "dir", "bdo", "sdt"]);
43363
+ var CONTAINER_PROPS = /* @__PURE__ */ new Set(["pPr", "sdtPr", "sdtEndPr", "smartTagPr", "customXmlPr"]);
43364
+ function atomKey(atom, noteKey) {
43365
+ const ln = localName(atom.name);
43366
+ if (ln === "footnoteReference" || ln === "endnoteReference") {
43367
+ const { "w:id": _id, ...rest } = atom.attrs;
43368
+ return serializeXml({ ...atom, attrs: rest }) + (noteKey ? "\0" + noteKey(atom) : "");
43369
+ }
43370
+ const strip = (e) => localName(e.name) === "txbxContent" ? { ...e, children: [], text: "" } : { ...e, children: e.children.map(strip) };
43371
+ return serializeXml(hasTextBox(atom) ? strip(atom) : atom);
43372
+ }
43373
+ function hasTextBox(el26) {
43374
+ return localName(el26.name) === "txbxContent" || el26.children.some(hasTextBox);
43375
+ }
43376
+ function textBoxesIn(el26) {
43377
+ if (localName(el26.name) === "txbxContent") return [el26];
43378
+ return el26.children.flatMap(textBoxesIn);
43379
+ }
43080
43380
  function readParagraph(pEl, options = {}) {
43081
43381
  const keyOf2 = options.whitespace === false ? (word) => word.trimEnd() : (word) => word;
43082
43382
  const tokens = [];
@@ -43089,31 +43389,80 @@ function readParagraph(pEl, options = {}) {
43089
43389
  }
43090
43390
  tokens.push(token);
43091
43391
  };
43092
- for (const cEl of pEl.children) {
43093
- const ln = localName(cEl.name);
43094
- if (ln === "pPr") continue;
43095
- if (PASSENGERS.has(ln)) {
43096
- pending.push(cEl);
43097
- continue;
43098
- }
43099
- if (ln !== "r") {
43100
- inlineDiffable = false;
43101
- continue;
43102
- }
43103
- for (const rc of cEl.children) {
43104
- const rln = localName(rc.name);
43105
- if (rln === "rPr") continue;
43106
- if (rln === "t") {
43107
- let at = 0;
43108
- for (const word of wordTokens(rc.text)) {
43109
- push({ key: "w\0" + keyOf2(word), text: word, run: cEl, t: rc, start: at, end: at + word.length });
43110
- at += word.length;
43111
- }
43392
+ let segment = [];
43393
+ let segmentPath = [];
43394
+ const flush = () => {
43395
+ if (segment.length === 0) return;
43396
+ const text = segment.map((p) => p.text).join("");
43397
+ const cuts = [];
43398
+ let base = 0;
43399
+ for (let i = 0; i + 1 < segment.length; i++) {
43400
+ base += segment[i].text.length;
43401
+ if (WORD_CHAR.test(text[base - 1] ?? "") && WORD_CHAR.test(text[base] ?? "") && runPropsKey(segment[i].run) !== runPropsKey(segment[i + 1].run)) {
43402
+ cuts.push(base);
43403
+ }
43404
+ }
43405
+ const words2 = [];
43406
+ let from = 0;
43407
+ for (const cut of [...cuts, text.length]) {
43408
+ words2.push(...wordTokens(text.slice(from, cut)));
43409
+ from = cut;
43410
+ }
43411
+ let at = 0;
43412
+ for (const word of words2) {
43413
+ const end = at + word.length;
43414
+ const pieces = [];
43415
+ let base2 = 0;
43416
+ for (const p of segment) {
43417
+ const from2 = Math.max(at, base2);
43418
+ const to = Math.min(end, base2 + p.text.length);
43419
+ if (from2 < to) pieces.push({ run: p.run, t: p.t, start: from2 - base2, end: to - base2, text: text.slice(from2, to) });
43420
+ base2 += p.text.length;
43421
+ }
43422
+ const first = pieces[0];
43423
+ push({ key: "w\0" + keyOf2(word), text: word, run: first.run, t: first.t, start: first.start, end: first.end, pieces, path: segmentPath });
43424
+ at = end;
43425
+ }
43426
+ segment = [];
43427
+ };
43428
+ const walk2 = (kids, path2) => {
43429
+ for (const cEl of kids) {
43430
+ const ln = localName(cEl.name);
43431
+ if (CONTAINER_PROPS.has(ln)) continue;
43432
+ if (PASSENGERS.has(ln)) {
43433
+ flush();
43434
+ pending.push(cEl);
43435
+ continue;
43436
+ }
43437
+ if (CONTAINERS.has(ln)) {
43438
+ const inner = ln === "sdt" ? cEl.children.find((c) => localName(c.name) === "sdtContent") : cEl;
43439
+ if (!inner) continue;
43440
+ flush();
43441
+ walk2(inner.children, [...path2, cEl]);
43442
+ flush();
43112
43443
  continue;
43113
43444
  }
43114
- push({ key: "a\0" + serializeXml(rc), text: "", run: cEl, atom: rc });
43445
+ if (ln !== "r") {
43446
+ flush();
43447
+ inlineDiffable = false;
43448
+ continue;
43449
+ }
43450
+ if (segment.length > 0 && segmentPath !== path2) flush();
43451
+ segmentPath = path2;
43452
+ for (const rc of cEl.children) {
43453
+ const rln = localName(rc.name);
43454
+ if (rln === "rPr") continue;
43455
+ if (rln === "t") {
43456
+ segment.push({ run: cEl, t: rc, text: rc.text });
43457
+ continue;
43458
+ }
43459
+ flush();
43460
+ push({ key: "a\0" + atomKey(rc, options.noteKey), text: "", run: cEl, atom: rc, path: path2 });
43461
+ }
43115
43462
  }
43116
- }
43463
+ };
43464
+ walk2(pEl.children, []);
43465
+ flush();
43117
43466
  return { tokens, tail: pending, text: paragraphText3(pEl), inlineDiffable };
43118
43467
  }
43119
43468
  function paragraphText3(el26) {
@@ -43130,31 +43479,41 @@ function paragraphText3(el26) {
43130
43479
  }
43131
43480
  return out;
43132
43481
  }
43133
- function sliceContent(tokens, from, to, deleted) {
43482
+ function sliceItems(tokens, from, to, deleted) {
43134
43483
  const out = [];
43135
- let i = from;
43136
- while (i < to) {
43137
- const token = tokens[i];
43138
- if (!deleted && token.lead) out.push(...token.lead.map(cloneXml));
43139
- const run = token.run;
43484
+ let open = null;
43485
+ let text = null;
43486
+ const runFor = (run, path2) => {
43487
+ if (open?.run === run) return open.el;
43140
43488
  const rPr = run.children.find((c) => localName(c.name) === "rPr");
43141
- const children3 = rPr ? [cloneXml(rPr)] : [];
43142
- while (i < to && tokens[i].run === run) {
43143
- const cur = tokens[i];
43144
- if (cur.atom) {
43145
- children3.push(cloneXml(cur.atom));
43146
- i++;
43489
+ const el26 = { name: run.name, attrs: { ...run.attrs }, children: rPr ? [cloneXml(rPr)] : [], text: "" };
43490
+ out.push({ el: el26, path: path2 });
43491
+ open = { run, el: el26 };
43492
+ text = null;
43493
+ return el26;
43494
+ };
43495
+ for (let i = from; i < to; i++) {
43496
+ const token = tokens[i];
43497
+ if (!deleted && token.lead) {
43498
+ out.push(...token.lead.map((el26) => ({ el: cloneXml(el26), path: token.path })));
43499
+ open = null;
43500
+ text = null;
43501
+ }
43502
+ if (token.atom) {
43503
+ runFor(token.run, token.path).children.push(cloneXml(token.atom));
43504
+ text = null;
43505
+ continue;
43506
+ }
43507
+ for (const piece of token.pieces ?? []) {
43508
+ const el26 = runFor(piece.run, token.path);
43509
+ if (text && text.t === piece.t) {
43510
+ text.el.text += piece.text;
43147
43511
  continue;
43148
43512
  }
43149
- const t = cur.t;
43150
- let text = "";
43151
- while (i < to && tokens[i].run === run && tokens[i].t === t) {
43152
- text += tokens[i].text;
43153
- i++;
43154
- }
43155
- children3.push(textElement2(t, text, deleted));
43513
+ const te = textElement2(piece.t, piece.text, deleted);
43514
+ el26.children.push(te);
43515
+ text = { t: piece.t, el: te };
43156
43516
  }
43157
- out.push({ name: run.name, attrs: { ...run.attrs }, children: children3, text: "" });
43158
43517
  }
43159
43518
  return out;
43160
43519
  }
@@ -43214,6 +43573,7 @@ function lowestOccurrenceRegion(a, b, aLo, aHi, bLo, bHi) {
43214
43573
  sawCommon = true;
43215
43574
  if (candidates.length > MAX_CHAIN2) continue;
43216
43575
  sawUsable = true;
43576
+ if (best !== void 0 && candidates.length > bestCost) continue;
43217
43577
  for (const i of candidates) {
43218
43578
  let aStart = i;
43219
43579
  let bStart = j;
@@ -43226,12 +43586,12 @@ function lowestOccurrenceRegion(a, b, aLo, aHi, bLo, bHi) {
43226
43586
  aEnd++;
43227
43587
  }
43228
43588
  const len = aEnd - aStart;
43229
- let cost = 0;
43589
+ let cost = candidates.length;
43230
43590
  for (let k = aStart; k < aEnd; k++) {
43231
- cost = Math.max(cost, positions.get(a[k])?.length ?? 0);
43591
+ cost = Math.min(cost, positions.get(a[k])?.length ?? cost);
43232
43592
  }
43233
43593
  const offset = Math.abs((aStart - aLo) / (aSpan || 1) - (bStart - bLo) / (bSpan || 1));
43234
- const better = cost < bestCost || cost === bestCost && best !== void 0 && len > best.len || cost === bestCost && best !== void 0 && len === best.len && offset < bestOffset;
43594
+ const better = best !== void 0 && (len > best.len || cost < bestCost || len === best.len && cost === bestCost && offset < bestOffset);
43235
43595
  if (best === void 0 || better) {
43236
43596
  best = { aStart, bStart, len };
43237
43597
  bestCost = cost;
@@ -43260,46 +43620,232 @@ function lcsFallback(a, b, aLo, aHi, bLo, bHi, out) {
43260
43620
 
43261
43621
  // ../core/src/edit/compare/index.ts
43262
43622
  function compareDocuments(original, revised, options) {
43263
- const merged = DocxDocument.load(revised.save());
43623
+ const note = options.onNote ?? (() => {
43624
+ });
43625
+ const accepted = (doc, which) => {
43626
+ if (collectRevisions(doc).length === 0) return doc;
43627
+ const copy = doc.clone();
43628
+ acceptAllRevisions(copy);
43629
+ note({ code: "existing-revisions-accepted", detail: `the ${which} document held tracked changes; it was compared as if they were accepted` });
43630
+ return copy;
43631
+ };
43632
+ const merged = accepted(revised, "revised");
43633
+ return compareInto(accepted(original, "original"), merged === revised ? revised.clone() : merged, options);
43634
+ }
43635
+ function compareInto(original, merged, options) {
43636
+ let issued = 0;
43264
43637
  const ctx = {
43265
43638
  doc: merged,
43266
43639
  original,
43267
43640
  meta: {
43268
43641
  author: options.author,
43269
43642
  date: options.date,
43270
- nextId: () => merged.nextRevisionId()
43643
+ nextId: () => {
43644
+ issued++;
43645
+ return merged.nextRevisionId();
43646
+ }
43271
43647
  },
43272
43648
  formatting: options.formatting !== false,
43273
43649
  whitespace: options.whitespace !== false,
43274
43650
  note: options.onNote ?? (() => {
43275
- })
43651
+ }),
43652
+ scope: { from: null, into: null },
43653
+ noteIds: { footnote: /* @__PURE__ */ new Map(), endnote: /* @__PURE__ */ new Map() }
43276
43654
  };
43277
43655
  const originalBody = child(original.docRoot, "body");
43278
43656
  const mergedBody = child(merged.docRoot, "body");
43279
43657
  if (!originalBody || !mergedBody) throw new Error("document.xml has no w:body");
43280
- reportUncomparedParts(original, revised, ctx);
43658
+ reportUncomparedParts(original, merged, ctx);
43659
+ const headerFooterPairs = pairHeadersFooters(original, merged);
43660
+ compareNotes("footnote", original, merged, ctx);
43661
+ compareNotes("endnote", original, merged, ctx);
43281
43662
  const tail = mergedBody.children.filter((c) => localName(c.name) === "sectPr");
43282
43663
  const entries = alignBlocks(blockList(originalBody, "sectPr"), blockList(mergedBody, "sectPr"), ctx);
43283
43664
  applyParagraphMarks(entries, ctx);
43284
43665
  mergedBody.children = [...entries.map((e) => e.el), ...tail];
43285
- merged.refresh();
43666
+ giveStruckReferencesTheirOwnNotes("footnote", merged, ctx);
43667
+ giveStruckReferencesTheirOwnNotes("endnote", merged, ctx);
43668
+ for (const [from, into] of headerFooterPairs) {
43669
+ const oRoot = original.headerFooterRoot(from);
43670
+ const mRoot = merged.headerFooterRoot(into);
43671
+ if (oRoot && mRoot) compareContainer(oRoot, mRoot, { ...ctx, scope: { from, into } });
43672
+ }
43673
+ if (issued > 0) merged.refresh();
43286
43674
  return merged;
43287
43675
  }
43676
+ function compareContainer(oRoot, mRoot, ctx) {
43677
+ const o = blockList(oRoot);
43678
+ const m = blockList(mRoot);
43679
+ if (o.length === m.length && o.every((el26, i) => sameXml(el26, m[i]))) return;
43680
+ const entries = alignBlocks(o, m, ctx);
43681
+ applyParagraphMarks(entries, ctx);
43682
+ const blocks = new Set(m);
43683
+ mRoot.children = [...mRoot.children.filter((c) => !blocks.has(c) && !["p", "tbl", "sdt"].includes(localName(c.name))), ...entries.map((e) => e.el)];
43684
+ }
43685
+ function pairHeadersFooters(original, merged) {
43686
+ const refs = (doc) => {
43687
+ const out = /* @__PURE__ */ new Map();
43688
+ const body = child(doc.docRoot, "body");
43689
+ const sects = (body?.children ?? []).flatMap(
43690
+ (c) => localName(c.name) === "sectPr" ? [c] : localName(c.name) === "p" ? [child(child(c, "pPr"), "sectPr")].filter((x) => !!x) : []
43691
+ );
43692
+ sects.forEach((sp, i) => {
43693
+ for (const ref of sp.children) {
43694
+ const ln = localName(ref.name);
43695
+ if (ln !== "headerReference" && ln !== "footerReference") continue;
43696
+ const target = doc.documentRels.get(ref.attrs["r:id"])?.target;
43697
+ if (target) out.set(`${i}:${ln}:${ref.attrs["w:type"] ?? "default"}`, target);
43698
+ }
43699
+ });
43700
+ return out;
43701
+ };
43702
+ const mine = refs(merged);
43703
+ const pairs = [];
43704
+ const done = /* @__PURE__ */ new Set();
43705
+ for (const [key, from] of refs(original)) {
43706
+ const into = mine.get(key);
43707
+ if (!into || done.has(into)) continue;
43708
+ done.add(into);
43709
+ pairs.push([from, into]);
43710
+ }
43711
+ return pairs;
43712
+ }
43713
+ function compareNotes(kind, original, merged, ctx) {
43714
+ const tree = (doc, create = false) => kind === "footnote" ? doc.footnotesTree(create) : doc.endnotesTree(create);
43715
+ const oRoot = tree(original);
43716
+ const mRoot = tree(merged);
43717
+ if (!oRoot && !mRoot) return;
43718
+ const refName = `${kind}Reference`;
43719
+ const inOrder = (doc, root) => {
43720
+ if (!root) return [];
43721
+ const byId = new Map(
43722
+ root.children.filter((c) => localName(c.name) === kind && !c.attrs["w:type"]).map((c) => [c.attrs["w:id"], c])
43723
+ );
43724
+ const out = [];
43725
+ const seen = /* @__PURE__ */ new Set();
43726
+ const walk2 = (e) => {
43727
+ if (localName(e.name) === refName) {
43728
+ const id = e.attrs["w:id"];
43729
+ const note = byId.get(id);
43730
+ if (note && !seen.has(id)) {
43731
+ seen.add(id);
43732
+ out.push(note);
43733
+ }
43734
+ }
43735
+ for (const c of e.children) walk2(c);
43736
+ };
43737
+ walk2(doc.docRoot);
43738
+ return out;
43739
+ };
43740
+ const oNotes = inOrder(original, oRoot);
43741
+ const rNotes = inOrder(merged, mRoot);
43742
+ const text = (n) => normalizeText(paragraphText3(n));
43743
+ const pairs = [];
43744
+ const waiting = /* @__PURE__ */ new Map();
43745
+ rNotes.forEach((n, j) => {
43746
+ const list = waiting.get(text(n));
43747
+ if (list) list.push(j);
43748
+ else waiting.set(text(n), [j]);
43749
+ });
43750
+ oNotes.forEach((n, i) => {
43751
+ const j = waiting.get(text(n))?.shift();
43752
+ if (j !== void 0) pairs.push([i, j]);
43753
+ });
43754
+ const exactO = new Set(pairs.map((p) => p[0]));
43755
+ const exactR = new Set(pairs.map((p) => p[1]));
43756
+ const oRest = oNotes.map((_, i) => i).filter((i) => !exactO.has(i));
43757
+ const rRest = rNotes.map((_, j) => j).filter((j) => !exactR.has(j));
43758
+ let oAt = 0;
43759
+ let rAt = 0;
43760
+ const rest = histogramMatch(oRest.map((i) => text(oNotes[i])), rRest.map((j) => text(rNotes[j])));
43761
+ for (const a of [...rest, { a: oRest.length, b: rRest.length }]) {
43762
+ if (a.a - oAt === 1 && a.b - rAt === 1) pairs.push([oRest[oAt], rRest[rAt]]);
43763
+ if (a.a < oRest.length && a.b < rRest.length) pairs.push([oRest[a.a], rRest[a.b]]);
43764
+ oAt = a.a + 1;
43765
+ rAt = a.b + 1;
43766
+ }
43767
+ const ids = ctx.noteIds[kind];
43768
+ const pairedO = new Set(pairs.map((p) => p[0]));
43769
+ const pairedR = new Set(pairs.map((p) => p[1]));
43770
+ let changed = false;
43771
+ for (const [i, j] of pairs) {
43772
+ ids.set(oNotes[i].attrs["w:id"], rNotes[j].attrs["w:id"]);
43773
+ const before = ctx.meta.nextId;
43774
+ let touched = false;
43775
+ compareContainer(oNotes[i], rNotes[j], { ...ctx, meta: { ...ctx.meta, nextId: () => (touched = true, before()) } });
43776
+ changed || (changed = touched);
43777
+ }
43778
+ rNotes.forEach((note, j) => {
43779
+ if (pairedR.has(j)) return;
43780
+ for (const block of blockList(note)) if (localName(block.name) === "p") wrapContainerContent(block, "ins", ctx);
43781
+ changed = true;
43782
+ });
43783
+ const removed = oNotes.filter((_, i) => !pairedO.has(i));
43784
+ if (removed.length > 0) {
43785
+ const root = tree(merged, true);
43786
+ if (root) {
43787
+ let next = 1 + Math.max(0, ...root.children.map((c) => Number(c.attrs["w:id"]) || 0));
43788
+ for (const o of removed) {
43789
+ const copy = cloneXml(o);
43790
+ copy.attrs["w:id"] = String(next);
43791
+ ids.set(o.attrs["w:id"], String(next++));
43792
+ for (const block of blockList(copy)) {
43793
+ if (localName(block.name) !== "p") continue;
43794
+ adoptFromOriginal(block, { ...ctx, scope: { from: null, into: null } });
43795
+ wrapContainerContent(block, "del", ctx);
43796
+ }
43797
+ root.children.push(copy);
43798
+ }
43799
+ changed = true;
43800
+ }
43801
+ }
43802
+ if (changed) merged.markNotePartsChanged([kind === "footnote" ? "footnotes" : "endnotes"]);
43803
+ }
43804
+ function giveStruckReferencesTheirOwnNotes(kind, merged, ctx) {
43805
+ const root = kind === "footnote" ? merged.footnotesTree() : merged.endnotesTree();
43806
+ if (!root) return;
43807
+ const refName = `${kind}Reference`;
43808
+ const refs = /* @__PURE__ */ new Map();
43809
+ const walk2 = (e, struck) => {
43810
+ if (localName(e.name) === refName) {
43811
+ const list = refs.get(e.attrs["w:id"]) ?? [];
43812
+ list.push({ ref: e, struck });
43813
+ refs.set(e.attrs["w:id"], list);
43814
+ }
43815
+ for (const c of e.children) walk2(c, struck || localName(c.name) === "del" || rowDeleted(c));
43816
+ };
43817
+ const rowDeleted = (tr) => localName(tr.name) === "tr" && !!child(child(tr, "trPr"), "del");
43818
+ walk2(merged.docRoot, false);
43819
+ let next = 1 + Math.max(0, ...root.children.map((c) => Number(c.attrs["w:id"]) || 0));
43820
+ let changed = false;
43821
+ for (const [id, list] of refs) {
43822
+ if (list.length < 2) continue;
43823
+ const note = root.children.find((c) => localName(c.name) === kind && c.attrs["w:id"] === id);
43824
+ if (!note) continue;
43825
+ const keep = list.find((r) => !r.struck) ?? list[0];
43826
+ for (const r of list) {
43827
+ if (r === keep || !r.struck) continue;
43828
+ const copy = cloneXml(note);
43829
+ copy.attrs["w:id"] = String(next);
43830
+ r.ref.attrs["w:id"] = String(next++);
43831
+ for (const block of blockList(copy)) if (localName(block.name) === "p") wrapContainerContent(block, "del", ctx);
43832
+ root.children.push(copy);
43833
+ changed = true;
43834
+ }
43835
+ }
43836
+ if (changed) merged.markNotePartsChanged([kind === "footnote" ? "footnotes" : "endnotes"]);
43837
+ }
43288
43838
  function blockList(container, ...skip) {
43289
43839
  return container.children.filter((c) => !skip.includes(localName(c.name)));
43290
43840
  }
43291
43841
  function reportUncomparedParts(original, revised, ctx) {
43292
43842
  const parts = [
43293
43843
  ["styles.xml", original.stylesTree()],
43294
- ["numbering.xml", original.numberingTree()],
43295
- ["footnotes.xml", original.footnotesTree()],
43296
- ["endnotes.xml", original.endnotesTree()]
43844
+ ["numbering.xml", original.numberingTree()]
43297
43845
  ];
43298
43846
  const revisedTrees = {
43299
43847
  "styles.xml": revised.stylesTree(),
43300
- "numbering.xml": revised.numberingTree(),
43301
- "footnotes.xml": revised.footnotesTree(),
43302
- "endnotes.xml": revised.endnotesTree()
43848
+ "numbering.xml": revised.numberingTree()
43303
43849
  };
43304
43850
  for (const [name, tree] of parts) {
43305
43851
  const other = revisedTrees[name];
@@ -43309,13 +43855,12 @@ function reportUncomparedParts(original, revised, ctx) {
43309
43855
  ctx.note({ code: "part-not-compared", detail: `${name} differs; the result carries the revised document's` });
43310
43856
  }
43311
43857
  }
43312
- const hf = (doc) => [...doc.headerRoots(), ...doc.footerRoots()].map((root) => serializeXml(root)).join("");
43313
- if (hf(original) !== hf(revised)) {
43314
- ctx.note({ code: "part-not-compared", detail: "headers/footers differ; the result carries the revised document's" });
43315
- }
43316
43858
  }
43317
43859
  function alignBlocks(oBlocks, rBlocks, ctx) {
43318
- const anchors = histogramMatch(oBlocks.map(blockFingerprint), rBlocks.map(blockFingerprint));
43860
+ const anchors = histogramMatch(
43861
+ oBlocks.map((el26) => blockFingerprint(el26, ctx.original)),
43862
+ rBlocks.map((el26) => blockFingerprint(el26, ctx.doc))
43863
+ );
43319
43864
  const entries = [];
43320
43865
  let oAt = 0;
43321
43866
  let rAt = 0;
@@ -43329,19 +43874,23 @@ function alignBlocks(oBlocks, rBlocks, ctx) {
43329
43874
  }
43330
43875
  return entries;
43331
43876
  }
43332
- function blockFingerprint(el26) {
43877
+ function blockFingerprint(el26, doc) {
43333
43878
  const ln = localName(el26.name);
43334
43879
  if (ln === "p") {
43335
43880
  const pPr = child(el26, "pPr");
43336
43881
  const style = child(pPr, "pStyle")?.attrs["w:val"] ?? "";
43337
43882
  const numPr = child(pPr, "numPr");
43338
- return `P\0${normalizeText(paragraphText3(el26))}\0${style}\0${numPr ? serializeXml(numPr) : ""}`;
43883
+ const jc = child(pPr, "jc")?.attrs["w:val"] ?? "";
43884
+ const sect = sectionKey(el26, doc);
43885
+ return `P\0${normalizeText(paragraphText3(el26))}\0${style}\0${numPr ? serializeXml(numPr) : ""}\0${jc}\0${sect}`;
43339
43886
  }
43340
43887
  if (ln === "tbl") return `T\0${normalizeText(paragraphText3(el26))}`;
43341
43888
  return `X\0${serializeXml(el26)}`;
43342
43889
  }
43343
43890
  function emitGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, entries, ctx) {
43344
- const pairings = resolveGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, ctx, oLo === 0 && rLo === 0);
43891
+ const before = entries[entries.length - 1];
43892
+ const unanchored = (!before || !isParagraph(before.el)) && (rHi >= rBlocks.length || !isParagraph(rBlocks[rHi]));
43893
+ const pairings = resolveGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, ctx, oLo === 0 && rLo === 0 || unanchored);
43345
43894
  let oAt = oLo;
43346
43895
  let rAt = rLo;
43347
43896
  for (const pairing of [...pairings, { kind: "pair", o: oHi, r: rHi, other: -1 }]) {
@@ -43373,16 +43922,43 @@ function emitGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, entries, ctx) {
43373
43922
  }
43374
43923
  }
43375
43924
  }
43925
+ var GAP_PAIR_CAP = 200 * 200;
43926
+ var GAP_WINDOW = 50;
43376
43927
  function resolveGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, ctx, needsAnchor = false) {
43377
43928
  const out = [];
43378
43929
  const oUsed = /* @__PURE__ */ new Set();
43379
43930
  const rUsed = /* @__PURE__ */ new Set();
43380
- const crosses = (o, r) => out.some((p) => o < p.o !== r < p.r);
43931
+ const placed = [];
43932
+ const crosses = (o, r) => {
43933
+ let lo = 0;
43934
+ let hi = placed.length;
43935
+ while (lo < hi) {
43936
+ const mid = lo + hi >> 1;
43937
+ if (placed[mid].o < o) lo = mid + 1;
43938
+ else hi = mid;
43939
+ }
43940
+ return lo > 0 && placed[lo - 1].r >= r || lo < placed.length && placed[lo].r <= r;
43941
+ };
43942
+ const accept = (p) => {
43943
+ out.push(p);
43944
+ let at = placed.length;
43945
+ while (at > 0 && placed[at - 1].o > p.o) at--;
43946
+ placed.splice(at, 0, p);
43947
+ };
43948
+ const oLen = oHi - oLo;
43949
+ const rLen = rHi - rLo;
43950
+ const windowed = oLen * rLen > GAP_PAIR_CAP;
43951
+ const span = (i) => {
43952
+ if (!windowed) return [rLo, rHi];
43953
+ const centre = rLo + Math.round((i - oLo) * rLen / Math.max(1, oLen));
43954
+ return [Math.max(rLo, centre - GAP_WINDOW), Math.min(rHi, centre + GAP_WINDOW + 1)];
43955
+ };
43381
43956
  for (let i = oLo; i < oHi; i++) {
43382
- for (let j = rLo; j + 1 < rHi; j++) {
43957
+ const [jLo, jHi] = span(i);
43958
+ for (let j = jLo; j + 1 < jHi; j++) {
43383
43959
  if (oUsed.has(i) || rUsed.has(j) || rUsed.has(j + 1) || crosses(i, j)) continue;
43384
43960
  if (concatMatches([oBlocks[i]], [rBlocks[j], rBlocks[j + 1]], ctx)) {
43385
- out.push({ kind: "split", o: i, r: j, other: j + 1 });
43961
+ accept({ kind: "split", o: i, r: j, other: j + 1 });
43386
43962
  oUsed.add(i);
43387
43963
  rUsed.add(j);
43388
43964
  rUsed.add(j + 1);
@@ -43390,10 +43966,11 @@ function resolveGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, ctx, needsAnchor = fal
43390
43966
  }
43391
43967
  }
43392
43968
  for (let i = oLo; i + 1 < oHi; i++) {
43393
- for (let j = rLo; j < rHi; j++) {
43969
+ const [jLo, jHi] = span(i);
43970
+ for (let j = jLo; j < jHi; j++) {
43394
43971
  if (oUsed.has(i) || oUsed.has(i + 1) || rUsed.has(j) || crosses(i, j)) continue;
43395
43972
  if (concatMatches([oBlocks[i], oBlocks[i + 1]], [rBlocks[j]], ctx)) {
43396
- out.push({ kind: "merge", o: i, r: j, other: i + 1 });
43973
+ accept({ kind: "merge", o: i, r: j, other: i + 1 });
43397
43974
  oUsed.add(i);
43398
43975
  oUsed.add(i + 1);
43399
43976
  rUsed.add(j);
@@ -43403,9 +43980,10 @@ function resolveGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, ctx, needsAnchor = fal
43403
43980
  const scored = [];
43404
43981
  for (let i = oLo; i < oHi; i++) {
43405
43982
  if (oUsed.has(i)) continue;
43406
- for (let j = rLo; j < rHi; j++) {
43983
+ const [jLo, jHi] = span(i);
43984
+ for (let j = jLo; j < jHi; j++) {
43407
43985
  if (rUsed.has(j)) continue;
43408
- if (localName(oBlocks[i].name) !== localName(rBlocks[j].name)) continue;
43986
+ if (!pairable(oBlocks[i], rBlocks[j], ctx)) continue;
43409
43987
  const a = paragraphText3(oBlocks[i]);
43410
43988
  const b = paragraphText3(rBlocks[j]);
43411
43989
  const score = similarity(a, b);
@@ -43416,30 +43994,74 @@ function resolveGap(oBlocks, rBlocks, oLo, oHi, rLo, rHi, ctx, needsAnchor = fal
43416
43994
  for (const cand of scored) {
43417
43995
  if (oUsed.has(cand.o) || rUsed.has(cand.r)) continue;
43418
43996
  if (crosses(cand.o, cand.r)) continue;
43419
- out.push({ kind: "pair", o: cand.o, r: cand.r, other: -1 });
43997
+ accept({ kind: "pair", o: cand.o, r: cand.r, other: -1 });
43420
43998
  oUsed.add(cand.o);
43421
43999
  rUsed.add(cand.r);
43422
44000
  }
44001
+ const settled = [...out].sort((a, b) => a.o - b.o);
44002
+ let oFrom = oLo;
44003
+ let rFrom = rLo;
44004
+ for (const p of [...settled, { kind: "pair", o: oHi, r: rHi, other: -1 }]) {
44005
+ const os = [];
44006
+ const rs = [];
44007
+ for (let i = oFrom; i < p.o; i++) if (!oUsed.has(i)) os.push(i);
44008
+ for (let j = rFrom; j < p.r; j++) if (!rUsed.has(j)) rs.push(j);
44009
+ if (os.length === 1 && rs.length === 1 && sameShape2(oBlocks[os[0]], rBlocks[rs[0]], ctx)) {
44010
+ accept({ kind: "pair", o: os[0], r: rs[0], other: -1 });
44011
+ oUsed.add(os[0]);
44012
+ rUsed.add(rs[0]);
44013
+ }
44014
+ oFrom = p.kind === "merge" ? p.other + 1 : p.o + 1;
44015
+ rFrom = p.kind === "split" ? p.other + 1 : p.r + 1;
44016
+ }
43423
44017
  if (needsAnchor && out.length === 0) {
43424
44018
  for (let i = oLo; i < oHi && out.length === 0; i++) {
43425
44019
  if (oUsed.has(i)) continue;
43426
44020
  for (let j = rLo; j < rHi; j++) {
43427
- if (rUsed.has(j) || localName(oBlocks[i].name) !== localName(rBlocks[j].name)) continue;
43428
- out.push({ kind: "pair", o: i, r: j, other: -1 });
44021
+ if (rUsed.has(j) || !pairable(oBlocks[i], rBlocks[j], ctx)) continue;
44022
+ accept({ kind: "pair", o: i, r: j, other: -1 });
43429
44023
  break;
43430
44024
  }
43431
44025
  }
43432
44026
  }
43433
44027
  return out.sort((a, b) => a.o - b.o);
43434
44028
  }
44029
+ function pairable(a, b, ctx) {
44030
+ if (localName(a.name) !== localName(b.name)) return false;
44031
+ if (!isParagraph(a)) return true;
44032
+ return sectionKey(a, ctx.original) === sectionKey(b, ctx.doc);
44033
+ }
44034
+ function sectionKey(el26, doc) {
44035
+ const sectPr = child(child(el26, "pPr"), "sectPr");
44036
+ if (!sectPr) return "";
44037
+ return serializeXml(sectPr).replace(/r:id="([^"]*)"/g, (_, id) => `r:id="${doc.documentRels.get(id)?.target ?? id}"`);
44038
+ }
44039
+ function sameShape2(a, b, ctx) {
44040
+ if (isParagraph(a) && isParagraph(b)) return pairable(a, b, ctx);
44041
+ if (localName(a.name) === localName(b.name) && ["tbl", "sdt", "customXml"].includes(localName(a.name))) return true;
44042
+ const cells = (tr) => tr.children.filter((c) => localName(c.name) === "tc").length;
44043
+ return localName(a.name) === "tr" && localName(b.name) === "tr" && cells(a) === cells(b);
44044
+ }
44045
+ function readOptions(ctx, original) {
44046
+ const noteKey = (ref) => {
44047
+ const kind = localName(ref.name) === "footnoteReference" ? "footnote" : "endnote";
44048
+ const id = ref.attrs["w:id"];
44049
+ return original ? `${ctx.noteIds[kind].get(id) ?? `original ${id}`}` : id;
44050
+ };
44051
+ return { whitespace: ctx.whitespace, noteKey };
44052
+ }
43435
44053
  function concatMatches(oEls, rEls, ctx) {
43436
44054
  const key = (els) => {
43437
44055
  const parts = [];
43438
44056
  for (const el26 of els) {
43439
44057
  if (localName(el26.name) !== "p") return null;
43440
- const content = readParagraph(el26, { whitespace: ctx.whitespace });
44058
+ if (child(child(el26, "pPr"), "sectPr")) return null;
44059
+ const content = readParagraph(el26, readOptions(ctx, oEls.includes(el26)));
43441
44060
  if (!content.inlineDiffable) return null;
43442
- for (const token of content.tokens) parts.push(token.key + "" + runPropsKey(token.run));
44061
+ for (const token of content.tokens) {
44062
+ const props = token.pieces ? token.pieces.map((p) => `${p.text.length}:${runPropsKey(p.run)}`).join("|") : runPropsKey(token.run);
44063
+ parts.push(token.key + "" + props + "" + token.path.map((c) => serializeXml({ ...c, children: c.children.filter((k) => /Pr$/.test(localName(k.name))) })).join(">"));
44064
+ }
43443
44065
  }
43444
44066
  return parts.join("");
43445
44067
  };
@@ -43451,14 +44073,24 @@ function emitMatched(oEl, rEl, ctx) {
43451
44073
  const ln = localName(oEl.name);
43452
44074
  if (ln === "p") comparePair(oEl, rEl, ctx);
43453
44075
  else if (ln === "tbl") compareTable(oEl, rEl, ctx);
44076
+ else if (ln === "sdt") {
44077
+ const o = child(oEl, "sdtContent");
44078
+ const r = child(rEl, "sdtContent");
44079
+ if (o && r) compareContainer(o, r, ctx);
44080
+ } else if (ln === "customXml") compareContainer(oEl, rEl, ctx);
43454
44081
  return { el: rEl, cls: "kept" };
43455
44082
  }
43456
44083
  var REL_ATTRS = ["id", "embed", "link", "pict", "dm", "lo", "qs", "cs"];
43457
44084
  function adoptFromOriginal(root, ctx) {
43458
- const mine = ctx.doc.documentRels;
43459
- const theirs = ctx.original.documentRels;
44085
+ const mine = ctx.doc.relationshipsOf(ctx.scope.into);
44086
+ const theirs = ctx.original.relationshipsOf(ctx.scope.from);
43460
44087
  const remapped = /* @__PURE__ */ new Map();
43461
44088
  const walk2 = (el26) => {
44089
+ const ln = localName(el26.name);
44090
+ if (ln === "footnoteReference" || ln === "endnoteReference") {
44091
+ const to = ctx.noteIds[ln === "footnoteReference" ? "footnote" : "endnote"].get(el26.attrs["w:id"]);
44092
+ if (to !== void 0) el26.attrs["w:id"] = to;
44093
+ }
43462
44094
  for (const key of Object.keys(el26.attrs)) {
43463
44095
  if (!key.startsWith("r:") || !REL_ATTRS.includes(localName(key))) continue;
43464
44096
  const id = el26.attrs[key];
@@ -43468,7 +44100,7 @@ function adoptFromOriginal(root, ctx) {
43468
44100
  if (here && here.target === source.target && here.type === source.type) continue;
43469
44101
  let replacement = remapped.get(id);
43470
44102
  if (replacement === void 0) {
43471
- replacement = source.external ? ctx.doc.addHyperlinkRel(source.target) : null;
44103
+ replacement = source.external ? ctx.doc.addHyperlinkRel(source.target, ctx.scope.into) : ctx.doc.adoptRelFrom(ctx.original, id, ctx.scope);
43472
44104
  if (replacement === null) {
43473
44105
  ctx.note({
43474
44106
  code: "unresolved-relationship",
@@ -43487,32 +44119,31 @@ function adoptFromOriginal(root, ctx) {
43487
44119
  function emitDeleted(el26, ctx) {
43488
44120
  const clone = cloneXml(el26);
43489
44121
  adoptFromOriginal(clone, ctx);
43490
- const ln = localName(clone.name);
43491
- if (ln === "p") {
43492
- wrapContainerContent(clone, "del", ctx);
43493
- } else if (ln === "tbl") {
43494
- for (const row of clone.children.filter((c) => localName(c.name) === "tr")) {
43495
- markRowRevision(row, "del", ctx.meta);
43496
- wrapRowContent(row, "del", ctx);
43497
- }
43498
- } else if (!isPassenger(ln)) {
43499
- ctx.note({ code: "untracked-block", detail: `a <${clone.name}> block was removed and is not expressible as a revision` });
43500
- }
44122
+ markWholeBlock(clone, "del", ctx);
43501
44123
  return { el: clone, cls: "deleted" };
43502
44124
  }
43503
44125
  function emitInserted(el26, ctx) {
44126
+ markWholeBlock(el26, "ins", ctx);
44127
+ return { el: el26, cls: "inserted" };
44128
+ }
44129
+ function markWholeBlock(el26, kind, ctx) {
43504
44130
  const ln = localName(el26.name);
43505
44131
  if (ln === "p") {
43506
- wrapContainerContent(el26, "ins", ctx);
44132
+ wrapContainerContent(el26, kind, ctx);
43507
44133
  } else if (ln === "tbl") {
43508
44134
  for (const row of el26.children.filter((c) => localName(c.name) === "tr")) {
43509
- markRowRevision(row, "ins", ctx.meta);
43510
- wrapRowContent(row, "ins", ctx);
44135
+ markRowRevision(row, kind, ctx.meta);
44136
+ wrapRowContent(row, kind, ctx);
44137
+ }
44138
+ } else if (ln === "sdt" || ln === "customXml") {
44139
+ const inner = ln === "sdt" ? child(el26, "sdtContent") : el26;
44140
+ for (const block of inner?.children ?? []) {
44141
+ markWholeBlock(block, kind, ctx);
44142
+ if (isParagraph(block)) markParagraphGlyph(block, kind, ctx.meta);
43511
44143
  }
43512
- } else if (!isPassenger(ln)) {
43513
- ctx.note({ code: "untracked-block", detail: `a <${el26.name}> block was added and is not expressible as a revision` });
44144
+ } else if (!isPassenger(ln) && ln !== "sdtPr" && ln !== "sdtEndPr" && ln !== "customXmlPr") {
44145
+ ctx.note({ code: "untracked-block", detail: `a <${el26.name}> block was ${kind === "ins" ? "added" : "removed"} and is not expressible as a revision` });
43514
44146
  }
43515
- return { el: el26, cls: "inserted" };
43516
44147
  }
43517
44148
  function wrapRowContent(row, kind, ctx) {
43518
44149
  for (const cell of row.children.filter((c) => localName(c.name) === "tc")) {
@@ -43522,7 +44153,8 @@ function wrapRowContent(row, kind, ctx) {
43522
44153
  }
43523
44154
  }
43524
44155
  function wrapContainerContent(container, kind, ctx) {
43525
- const NESTED = ["hyperlink", "smartTag", "dir", "bdo"];
44156
+ const NESTED = ["hyperlink", "smartTag", "dir", "bdo", "fldSimple"];
44157
+ const WRAPPABLE = ["r", "sdt", "customXml"];
43526
44158
  const out = [];
43527
44159
  let group = [];
43528
44160
  const flush = () => {
@@ -43533,7 +44165,7 @@ function wrapContainerContent(container, kind, ctx) {
43533
44165
  };
43534
44166
  for (const c of container.children) {
43535
44167
  const ln = localName(c.name);
43536
- if (ln === "r") {
44168
+ if (WRAPPABLE.includes(ln)) {
43537
44169
  group.push(c);
43538
44170
  continue;
43539
44171
  }
@@ -43551,10 +44183,10 @@ function wrapContainerContent(container, kind, ctx) {
43551
44183
  container.children = out;
43552
44184
  }
43553
44185
  function comparePair(oEl, rEl, ctx) {
43554
- if (serializeXml(oEl) === serializeXml(rEl)) return;
44186
+ if (sameXml(oEl, rEl)) return;
43555
44187
  if (ctx.formatting) alignParagraphProps(rEl, child(rEl, "pPr"), child(oEl, "pPr"), ctx);
43556
- const oc = readParagraph(oEl, { whitespace: ctx.whitespace });
43557
- const rc = readParagraph(rEl, { whitespace: ctx.whitespace });
44188
+ const oc = readParagraph(oEl, readOptions(ctx, true));
44189
+ const rc = readParagraph(rEl, readOptions(ctx, false));
43558
44190
  if (!oc.inlineDiffable || !rc.inlineDiffable) {
43559
44191
  if (oc.text !== rc.text || serializeXml(stripProps(oEl)) !== serializeXml(stripProps(rEl))) {
43560
44192
  const deleted = cloneXml(oEl);
@@ -43569,70 +44201,180 @@ function comparePair(oEl, rEl, ctx) {
43569
44201
  ];
43570
44202
  ctx.note({
43571
44203
  code: "coarse-paragraph",
43572
- detail: "a paragraph holding a hyperlink, content control, smart tag or inline math changed; struck and reinserted whole"
44204
+ detail: "a paragraph holding inline math or other content a word diff cannot rebuild changed; struck and reinserted whole"
43573
44205
  });
43574
44206
  }
43575
44207
  return;
43576
44208
  }
43577
44209
  const segments = wordSegments(oc.tokens, rc.tokens);
43578
44210
  const changed = segments.some((s) => !s.equal);
43579
- if (!changed) {
43580
- if (ctx.formatting) alignRunPropsInPlace(oEl, rEl, ctx);
43581
- return;
43582
- }
44211
+ const flat = oc.tokens.every((t) => t.path.length === 0) && rc.tokens.every((t) => t.path.length === 0);
44212
+ if (!changed && (!ctx.formatting || flat && alignRunPropsInPlace(oEl, rEl, ctx))) return;
43583
44213
  const pPr = child(rEl, "pPr");
43584
- const out = [];
44214
+ const items = [];
44215
+ const counterpart = /* @__PURE__ */ new Map();
43585
44216
  for (const seg of segments) {
43586
44217
  if (seg.equal) {
43587
- out.push(...emitEqualSpan(oc.tokens, rc.tokens, seg, ctx));
44218
+ for (const it of emitEqualSpan(oc.tokens, rc.tokens, seg, ctx, counterpart)) items.push({ ...it, kind: null });
43588
44219
  continue;
43589
44220
  }
43590
44221
  if (seg.aEnd > seg.aStart) {
43591
- const struck = sliceContent(oc.tokens, seg.aStart, seg.aEnd, true);
43592
- for (const run of struck) adoptFromOriginal(run, ctx);
43593
- out.push(revisionWrapper(rEl, "del", struck, ctx.meta));
44222
+ for (const it of sliceItems(oc.tokens, seg.aStart, seg.aEnd, true)) {
44223
+ adoptFromOriginal(it.el, ctx);
44224
+ markDeletedText(it.el);
44225
+ items.push({ ...it, kind: "del" });
44226
+ }
43594
44227
  }
43595
44228
  if (seg.bEnd > seg.bStart) {
43596
- out.push(revisionWrapper(rEl, "ins", sliceContent(rc.tokens, seg.bStart, seg.bEnd, false), ctx.meta));
44229
+ for (const it of sliceItems(rc.tokens, seg.bStart, seg.bEnd, false)) items.push({ ...it, kind: "ins" });
44230
+ }
44231
+ }
44232
+ for (const it of items) if (it.kind === "del") it.path = it.path.map((c) => counterpart.get(c) ?? c);
44233
+ for (const el26 of rc.tail) items.push({ el: cloneXml(el26), path: [], kind: null });
44234
+ const fromOriginal = new Set(oc.tokens.flatMap((t) => t.path));
44235
+ rEl.children = [...pPr ? [pPr] : [], ...nestItems(items, 0, rEl, ctx, fromOriginal)];
44236
+ }
44237
+ var WRAPPABLE_CONTAINERS = /* @__PURE__ */ new Set(["sdt", "customXml"]);
44238
+ function compareTextBoxes(oAtom, rAtom, ctx) {
44239
+ const theirs = textBoxesIn(oAtom);
44240
+ const mine = textBoxesIn(rAtom);
44241
+ for (let i = 0; i < Math.min(theirs.length, mine.length); i++) {
44242
+ if (sameXml(theirs[i], mine[i])) continue;
44243
+ const entries = alignBlocks(blockList(theirs[i]), blockList(mine[i]), ctx);
44244
+ applyParagraphMarks(entries, ctx);
44245
+ mine[i].children = entries.map((e) => e.el);
44246
+ }
44247
+ }
44248
+ function nestItems(items, depth, rEl, ctx, fromOriginal) {
44249
+ const out = [];
44250
+ let i = 0;
44251
+ while (i < items.length) {
44252
+ const c = items[i].path[depth];
44253
+ let j = i + 1;
44254
+ if (c) {
44255
+ while (j < items.length && items[j].path[depth] === c) j++;
44256
+ const group = items.slice(i, j);
44257
+ const kind = group[0].kind;
44258
+ if (kind && WRAPPABLE_CONTAINERS.has(localName(c.name)) && group.every((it) => it.kind === kind)) {
44259
+ const plain = group.map((it) => ({ ...it, kind: null }));
44260
+ out.push(revisionWrapper(rEl, kind, [containerShell(c, nestItems(plain, depth + 1, rEl, ctx, fromOriginal), fromOriginal.has(c), ctx)], ctx.meta));
44261
+ } else {
44262
+ out.push(containerShell(c, nestItems(group, depth + 1, rEl, ctx, fromOriginal), fromOriginal.has(c), ctx));
44263
+ }
44264
+ } else {
44265
+ const kind = items[i].kind;
44266
+ while (j < items.length && !items[j].path[depth] && items[j].kind === kind) j++;
44267
+ const els = items.slice(i, j).map((it) => it.el);
44268
+ if (kind) out.push(revisionWrapper(rEl, kind, els, ctx.meta));
44269
+ else out.push(...els);
43597
44270
  }
44271
+ i = j;
43598
44272
  }
43599
- out.push(...rc.tail.map(cloneXml));
43600
- rEl.children = [...pPr ? [pPr] : [], ...out];
44273
+ return out;
44274
+ }
44275
+ function containerShell(c, content, fromOriginal, ctx) {
44276
+ const isSdt = localName(c.name) === "sdt";
44277
+ const inner = isSdt ? c.children.find((k) => localName(k.name) === "sdtContent") : void 0;
44278
+ const shell = {
44279
+ name: c.name,
44280
+ attrs: { ...c.attrs },
44281
+ // Property children only (sdtPr, smartTagPr, ...): the content is new.
44282
+ children: c.children.filter((k) => /Pr$/.test(localName(k.name))).map(cloneXml),
44283
+ text: ""
44284
+ };
44285
+ if (fromOriginal) adoptFromOriginal(shell, ctx);
44286
+ if (isSdt) shell.children.push({ name: inner?.name ?? c.name.replace(/sdt$/, "sdtContent"), attrs: { ...inner?.attrs ?? {} }, children: content, text: "" });
44287
+ else shell.children.push(...content);
44288
+ return shell;
43601
44289
  }
43602
44290
  function stripProps(el26) {
43603
44291
  return { ...el26, children: el26.children.filter((c) => localName(c.name) !== "pPr") };
43604
44292
  }
43605
- function emitEqualSpan(oTokens, rTokens, seg, ctx) {
44293
+ function emitEqualSpan(oTokens, rTokens, seg, ctx, counterpart) {
44294
+ const units = [];
44295
+ const pieceAt = (pieces, offset) => {
44296
+ let base = 0;
44297
+ for (const p of pieces) {
44298
+ if (offset < base + p.text.length) return p;
44299
+ base += p.text.length;
44300
+ }
44301
+ return pieces[pieces.length - 1];
44302
+ };
44303
+ for (let k = 0; k < seg.bEnd - seg.bStart; k++) {
44304
+ const o = oTokens[seg.aStart + k];
44305
+ const r = rTokens[seg.bStart + k];
44306
+ for (let d = 0; d < Math.min(o.path.length, r.path.length); d++) {
44307
+ if (localName(o.path[d].name) === localName(r.path[d].name)) counterpart.set(o.path[d], r.path[d]);
44308
+ }
44309
+ if (r.atom || !r.pieces || !o.pieces || o.text !== r.text) {
44310
+ if (r.atom) units.push({ token: r, run: r.run, text: "", atom: r.atom, oAtom: o.atom, was: o.run });
44311
+ else for (const p of r.pieces ?? []) units.push({ token: r, run: p.run, t: p.t, text: p.text, was: o.run });
44312
+ continue;
44313
+ }
44314
+ const cuts = /* @__PURE__ */ new Set([0, r.text.length]);
44315
+ for (const pieces of [o.pieces, r.pieces]) {
44316
+ let base = 0;
44317
+ for (const p of pieces) cuts.add(base += p.text.length);
44318
+ }
44319
+ const sorted = [...cuts].sort((x, y) => x - y);
44320
+ for (let c = 0; c + 1 < sorted.length; c++) {
44321
+ const from = sorted[c];
44322
+ const to = sorted[c + 1];
44323
+ if (from === to) continue;
44324
+ const rp = pieceAt(r.pieces, from);
44325
+ units.push({ token: r, run: rp.run, t: rp.t, text: r.text.slice(from, to), was: pieceAt(o.pieces, from).run });
44326
+ }
44327
+ }
43606
44328
  const out = [];
43607
- let at = seg.bStart;
43608
- while (at < seg.bEnd) {
43609
- const run = rTokens[at].run;
43610
- const oKey = runPropsKey(oTokens[seg.aStart + (at - seg.bStart)].run);
43611
- let end = at + 1;
43612
- while (end < seg.bEnd && rTokens[end].run === run && runPropsKey(oTokens[seg.aStart + (end - seg.bStart)].run) === oKey) {
43613
- end++;
44329
+ let open = null;
44330
+ let text = null;
44331
+ const close = () => {
44332
+ if (open && ctx.formatting && open.was !== runPropsKey(open.run)) swapRunProps(open.el, open.prev, ctx);
44333
+ open = null;
44334
+ text = null;
44335
+ };
44336
+ let lastToken = null;
44337
+ for (const u of units) {
44338
+ if (u.token !== lastToken && u.token.lead) {
44339
+ close();
44340
+ out.push(...u.token.lead.map((el26) => ({ el: cloneXml(el26), path: u.token.path })));
44341
+ }
44342
+ lastToken = u.token;
44343
+ const was = runPropsKey(u.was);
44344
+ if (!open || open.run !== u.run || open.was !== was) {
44345
+ close();
44346
+ const rPr = u.run.children.find((c) => localName(c.name) === "rPr");
44347
+ const el26 = { name: u.run.name, attrs: { ...u.run.attrs }, children: rPr ? [cloneXml(rPr)] : [], text: "" };
44348
+ out.push({ el: el26, path: u.token.path });
44349
+ open = { run: u.run, was, el: el26, prev: child(u.was, "rPr") };
44350
+ }
44351
+ if (u.atom) {
44352
+ const atom = cloneXml(u.atom);
44353
+ if (u.oAtom && !sameXml(u.oAtom, u.atom)) compareTextBoxes(u.oAtom, atom, ctx);
44354
+ open.el.children.push(atom);
44355
+ text = null;
44356
+ continue;
43614
44357
  }
43615
- const piece = sliceContent(rTokens, at, end, false);
43616
- if (ctx.formatting && oKey !== runPropsKey(run)) {
43617
- for (const el26 of piece) {
43618
- if (localName(el26.name) === "r") {
43619
- swapRunProps(el26, child(oTokens[seg.aStart + (at - seg.bStart)].run, "rPr"), ctx);
43620
- }
43621
- }
44358
+ if (text && text.t === u.t) {
44359
+ text.el.text += u.text;
44360
+ continue;
43622
44361
  }
43623
- out.push(...piece);
43624
- at = end;
44362
+ const te = { name: u.t.name, attrs: { ...u.t.attrs, "xml:space": "preserve" }, children: [], text: u.text };
44363
+ open.el.children.push(te);
44364
+ text = { t: u.t, el: te };
43625
44365
  }
44366
+ close();
43626
44367
  return out;
43627
44368
  }
43628
44369
  function alignRunPropsInPlace(oEl, rEl, ctx) {
43629
44370
  const oRuns = oEl.children.filter((c) => localName(c.name) === "r");
43630
44371
  const rRuns = rEl.children.filter((c) => localName(c.name) === "r");
43631
- if (oRuns.length !== rRuns.length) return;
44372
+ if (oRuns.length !== rRuns.length) return false;
43632
44373
  for (let i = 0; i < rRuns.length; i++) {
43633
- if (paragraphText3(oRuns[i]) !== paragraphText3(rRuns[i])) return;
44374
+ if (paragraphText3(oRuns[i]) !== paragraphText3(rRuns[i])) return false;
43634
44375
  }
43635
44376
  for (let i = 0; i < rRuns.length; i++) swapRunProps(rRuns[i], child(oRuns[i], "rPr"), ctx);
44377
+ return true;
43636
44378
  }
43637
44379
  function wordSegments(a, b) {
43638
44380
  const raw = [];
@@ -43661,8 +44403,8 @@ function wordSegments(a, b) {
43661
44403
  for (let i = 1; i + 1 < segments.length; i++) {
43662
44404
  const seg = segments[i];
43663
44405
  if (!seg.equal) continue;
43664
- const len = seg.aEnd - seg.aStart;
43665
- const beside = Math.min(changeSize(segments[i - 1]), changeSize(segments[i + 1]));
44406
+ const len = words(a, seg.aStart, seg.aEnd);
44407
+ const beside = Math.min(changeSize(segments[i - 1], a, b), changeSize(segments[i + 1], a, b));
43666
44408
  if (len < COMPARE_TUNING.coalesceWindow || len * COMPARE_TUNING.coalesceRatio < beside) {
43667
44409
  seg.equal = false;
43668
44410
  absorbed = true;
@@ -43686,17 +44428,26 @@ function mergeChanges(segments) {
43686
44428
  }
43687
44429
  return out;
43688
44430
  }
43689
- function changeSize(seg) {
43690
- return Math.max(seg.aEnd - seg.aStart, seg.bEnd - seg.bStart);
44431
+ function changeSize(seg, a, b) {
44432
+ return Math.max(words(a, seg.aStart, seg.aEnd), words(b, seg.bStart, seg.bEnd));
44433
+ }
44434
+ function words(tokens, from, to) {
44435
+ let n = 0;
44436
+ for (let i = from; i < to; i++) if (tokens[i].atom || !/^\s+$/.test(tokens[i].text)) n++;
44437
+ return n;
43691
44438
  }
43692
44439
  function compareTable(oTbl, rTbl, ctx) {
43693
- if (serializeXml(oTbl) === serializeXml(rTbl)) return;
44440
+ if (sameXml(oTbl, rTbl)) return;
43694
44441
  if (ctx.formatting) {
43695
44442
  swapProps(rTbl, "tblPr", recordTableFormatChange, child(oTbl, "tblPr"), ctx);
43696
44443
  alignTableGrid(oTbl, rTbl, ctx);
43697
44444
  }
43698
- const oRows = oTbl.children.filter((c) => localName(c.name) === "tr");
43699
- const rRows = rTbl.children.filter((c) => localName(c.name) === "tr");
44445
+ compareRows(oTbl, rTbl, ctx);
44446
+ }
44447
+ var ROW_LEVEL = ["tr", "sdt", "customXml"];
44448
+ function compareRows(oParent, rParent, ctx) {
44449
+ const oRows = oParent.children.filter((c) => ROW_LEVEL.includes(localName(c.name)));
44450
+ const rRows = rParent.children.filter((c) => ROW_LEVEL.includes(localName(c.name)));
43700
44451
  const anchors = histogramMatch(oRows.map(rowFingerprint), rRows.map(rowFingerprint));
43701
44452
  const rows = [];
43702
44453
  let oAt = 0;
@@ -43721,24 +44472,41 @@ function compareTable(oTbl, rTbl, ctx) {
43721
44472
  oAt = anchor.a + 1;
43722
44473
  rAt = anchor.b + 1;
43723
44474
  }
43724
- rTbl.children = [...rTbl.children.filter((c) => localName(c.name) !== "tr"), ...rows];
44475
+ rParent.children = [...rParent.children.filter((c) => !ROW_LEVEL.includes(localName(c.name))), ...rows];
43725
44476
  }
43726
44477
  function rowFingerprint(tr) {
44478
+ if (localName(tr.name) !== "tr") return `X\0${serializeXml(tr)}`;
43727
44479
  const cells = tr.children.filter((c) => localName(c.name) === "tc");
43728
44480
  return `${cells.length}\0${normalizeText(paragraphText3(tr))}`;
43729
44481
  }
43730
- function deletedRow(tr, ctx) {
43731
- const clone = cloneXml(tr);
43732
- markRowRevision(clone, "del", ctx.meta);
43733
- wrapRowContent(clone, "del", ctx);
44482
+ function rowsIn(el26) {
44483
+ if (localName(el26.name) === "tr") return [el26];
44484
+ const inner = localName(el26.name) === "sdt" ? child(el26, "sdtContent") : el26;
44485
+ return (inner?.children ?? []).filter((c) => ROW_LEVEL.includes(localName(c.name))).flatMap(rowsIn);
44486
+ }
44487
+ function deletedRow(el26, ctx) {
44488
+ const clone = cloneXml(el26);
44489
+ for (const tr of rowsIn(clone)) {
44490
+ markRowRevision(tr, "del", ctx.meta);
44491
+ wrapRowContent(tr, "del", ctx);
44492
+ }
43734
44493
  return clone;
43735
44494
  }
43736
- function insertedRow(tr, ctx) {
43737
- markRowRevision(tr, "ins", ctx.meta);
43738
- wrapRowContent(tr, "ins", ctx);
43739
- return tr;
44495
+ function insertedRow(el26, ctx) {
44496
+ for (const tr of rowsIn(el26)) {
44497
+ markRowRevision(tr, "ins", ctx.meta);
44498
+ wrapRowContent(tr, "ins", ctx);
44499
+ }
44500
+ return el26;
43740
44501
  }
43741
44502
  function compareRow(oTr, rTr, ctx) {
44503
+ if (localName(oTr.name) !== "tr") {
44504
+ const inner = (el26) => localName(el26.name) === "sdt" ? child(el26, "sdtContent") : el26;
44505
+ const o = inner(oTr);
44506
+ const r = inner(rTr);
44507
+ if (o && r) compareRows(o, r, ctx);
44508
+ return [rTr];
44509
+ }
43742
44510
  const oCells = oTr.children.filter((c) => localName(c.name) === "tc");
43743
44511
  const rCells = rTr.children.filter((c) => localName(c.name) === "tc");
43744
44512
  if (oCells.length !== rCells.length) {
@@ -43761,7 +44529,7 @@ function compareRow(oTr, rTr, ctx) {
43761
44529
  function alignTableGrid(oTbl, rTbl, ctx) {
43762
44530
  const oGrid = child(oTbl, "tblGrid");
43763
44531
  const rGrid = child(rTbl, "tblGrid");
43764
- if (!oGrid || !rGrid || serializeXml(oGrid) === serializeXml(rGrid)) return;
44532
+ if (!oGrid || !rGrid || sameXml(oGrid, rGrid)) return;
43765
44533
  const live = rGrid.children.map(cloneXml);
43766
44534
  rGrid.children = oGrid.children.map(cloneXml);
43767
44535
  recordTableGridChange(rTbl, ctx.meta);
@@ -43848,7 +44616,22 @@ function applyParagraphMarks(entries, ctx) {
43848
44616
  }
43849
44617
  }
43850
44618
  function markRun(entries, start, end, ctx) {
43851
- const shifted = start > 0 && isParagraph(entries[start - 1].el);
44619
+ let at = start;
44620
+ while (at <= end) {
44621
+ if (!isParagraph(entries[at].el)) {
44622
+ at++;
44623
+ continue;
44624
+ }
44625
+ let last = at;
44626
+ while (last + 1 <= end && isParagraph(entries[last + 1].el)) last++;
44627
+ markParagraphRun(entries, at, last, ctx);
44628
+ at = last + 1;
44629
+ }
44630
+ }
44631
+ function markParagraphRun(entries, start, end, ctx) {
44632
+ const endsSection = (el26) => isParagraph(el26) && !!child(child(el26, "pPr"), "sectPr");
44633
+ const before = start > 0 ? entries[start - 1].el : void 0;
44634
+ const shifted = !!before && isParagraph(before) && !endsSection(before) && !entries.slice(start, end + 1).some((e) => endsSection(e.el));
43852
44635
  if (shifted) {
43853
44636
  for (let i = start; i <= end; i++) {
43854
44637
  if (!isParagraph(entries[i].el) || !isParagraph(entries[i - 1].el)) continue;
@@ -45335,9 +46118,10 @@ async function renderForPrint(bytes, container) {
45335
46118
  const doc = DocxDocument.load(bytes);
45336
46119
  const layout = layoutDocument(doc);
45337
46120
  renderToDom(doc, layout, container, { zoom: 1, pageGap: 0, pageShadow: false, comments: false });
46121
+ pruneBlankSpans(container);
45338
46122
  const first = layout.pages[0];
45339
46123
  const style = document.createElement("style");
45340
- style.textContent = printStylesheet(first?.width ?? 816, first?.height ?? 1056);
46124
+ style.textContent = printStylesheet(first?.width ?? 816, first?.height ?? 1056, container);
45341
46125
  document.head.append(style);
45342
46126
  await paintsSettled();
45343
46127
  await Promise.all(Array.from(container.querySelectorAll("img"), (img) => img.decode().catch(() => void 0)));
@@ -55791,4 +56575,4 @@ export {
55791
56575
  cfb/cfb.js:
55792
56576
  (*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com *)
55793
56577
  */
55794
- //# sourceMappingURL=chunk-PZIJZDQ7.js.map
56578
+ //# sourceMappingURL=chunk-WPQ6GPG4.js.map