@createiq/htmldiff 1.2.0-beta.1 → 1.2.0-beta.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"HtmlDiff.cjs","names":["Utils","Utils","Utils","Utils"],"sources":["../src/Match.ts","../src/Utils.ts","../src/MatchFinder.ts","../src/Operation.ts","../src/Alignment.ts","../src/HtmlScanner.ts","../src/TableDiff.ts","../src/ThreeWayDiff.ts","../src/ThreeWayTable.ts","../src/WordSplitter.ts","../src/HtmlDiff.ts"],"sourcesContent":["export default class Match {\n private _startInOld: number\n private _startInNew: number\n private _size: number\n\n constructor(startInOld: number, startInNew: number, size: number) {\n this._startInOld = startInOld\n this._startInNew = startInNew\n this._size = size\n }\n\n get startInOld() {\n return this._startInOld\n }\n\n get startInNew() {\n return this._startInNew\n }\n\n get size() {\n return this._size\n }\n\n get endInOld() {\n return this._startInOld + this._size\n }\n\n get endInNew() {\n return this._startInNew + this._size\n }\n}\n","const openingTagRegex = /^\\s*<[^>]+>\\s*$/\nconst closingTagTexRegex = /^\\s*<\\/[^>]+>\\s*$/\nconst tagWordRegex = /<[^\\s>]+/\nconst whitespaceRegex = /^(\\s|&nbsp;)+$/\nconst wordRegex = /[\\w#@]+/\nconst tagRegex = /<\\/?(?<name>[^\\s/>]+)[^>]*>/\n\nconst SpecialCaseWordTags: readonly string[] = ['<img']\n\nexport function isTag(item: string): boolean {\n if (SpecialCaseWordTags.some(re => item?.startsWith(re))) {\n return false\n }\n\n return isOpeningTag(item) || isClosingTag(item)\n}\n\nfunction isOpeningTag(item: string): boolean {\n return openingTagRegex.test(item)\n}\n\nfunction isClosingTag(item: string): boolean {\n return closingTagTexRegex.test(item)\n}\n\nexport function stripTagAttributes(word: string): string {\n const match = tagWordRegex.exec(word)\n if (match) {\n return `${match[0]}${word.endsWith('/>') ? '/>' : '>'}`\n }\n\n return word\n}\n\n/**\n * Optional metadata attached to a wrapped tag. Used by `executeThreeWay`\n * to colour diff segments with their author (CP vs Me) via extra classes\n * and `data-*` attributes; the two-way path passes nothing and gets the\n * unchanged historical output.\n */\nexport interface WrapMetadata {\n /** Space-separated classes appended after `cssClass`. */\n extraClasses?: string\n /** `data-*` attribute map, keyed by the attribute name *without* the `data-` prefix. */\n dataAttrs?: Readonly<Record<string, string>>\n}\n\nexport function wrapText(text: string, tagName: string, cssClass: string, metadata?: WrapMetadata): string {\n if (!metadata) return `<${tagName} class='${cssClass}'>${text}</${tagName}>`\n return `<${tagName}${composeTagAttributes(cssClass, metadata)}>${text}</${tagName}>`\n}\n\n/**\n * Build the attribute portion of an opening tag from a base class plus\n * optional metadata. Exposed so emission paths that build opening-tag\n * fragments by hand (e.g. the formatting-tag special-case in\n * `HtmlDiff.insertTag`) can stay consistent with `wrapText`.\n */\nexport function composeTagAttributes(cssClass: string, metadata: WrapMetadata): string {\n const classes = metadata.extraClasses ? `${cssClass} ${metadata.extraClasses}` : cssClass\n let out = ` class='${classes}'`\n if (metadata.dataAttrs) {\n for (const key of Object.keys(metadata.dataAttrs)) {\n out += ` data-${key}='${metadata.dataAttrs[key]}'`\n }\n }\n return out\n}\n\nexport function isStartOfTag(val: string): boolean {\n return val === '<'\n}\n\nexport function isEndOfTag(val: string): boolean {\n return val === '>'\n}\n\nexport function isStartOfEntity(val: string): boolean {\n return val === '&'\n}\n\nexport function isEndOfEntity(val: string): boolean {\n return val === ';'\n}\n\nexport function isWhiteSpace(value: string): boolean {\n return whitespaceRegex.test(value)\n}\n\nexport function stripAnyAttributes(word: string): string {\n if (isTag(word)) {\n return stripTagAttributes(word)\n }\n\n return word\n}\n\nexport function isWord(text: string): boolean {\n return wordRegex.test(text)\n}\n\nexport function getTagName(word: string | null): string {\n if (word === null) {\n return ''\n }\n\n const match = tagRegex.exec(word)\n if (match) {\n return match.groups?.name.toLowerCase() ?? match[1].toLowerCase()\n }\n\n return ''\n}\n\nexport default {\n isTag,\n stripTagAttributes,\n wrapText,\n composeTagAttributes,\n isStartOfTag,\n isEndOfTag,\n isStartOfEntity,\n isEndOfEntity,\n isWhiteSpace,\n stripAnyAttributes,\n isWord,\n getTagName,\n}\n","import Match from './Match'\nimport type MatchOptions from './MatchOptions'\nimport Utils from './Utils'\n\n/**\n * Finds the longest match in given texts. It uses indexing with fixed granularity that is used to compare blocks of text.\n */\nexport default class MatchFinder {\n private oldWords: string[]\n private newWords: string[]\n private startInOld: number\n private endInOld: number\n private startInNew: number\n private endInNew: number\n private wordIndices: { [word: string]: number[] } = {}\n private options: MatchOptions\n\n constructor(\n oldWords: string[],\n newWords: string[],\n startInOld: number,\n endInOld: number,\n startInNew: number,\n endInNew: number,\n options: MatchOptions\n ) {\n this.oldWords = oldWords\n this.newWords = newWords\n this.startInOld = startInOld\n this.endInOld = endInOld\n this.startInNew = startInNew\n this.endInNew = endInNew\n this.options = options\n }\n\n private indexNewWords() {\n this.wordIndices = {}\n const block: string[] = []\n for (let i = this.startInNew; i < this.endInNew; i++) {\n // if word is a tag, we should ignore attributes as attribute changes are not supported (yet)\n const word = this.normalizeForIndex(this.newWords[i])\n const key = MatchFinder.putNewWord(block, word, this.options.blockSize)\n\n if (key === null) {\n continue\n }\n\n if (!this.wordIndices[key]) {\n this.wordIndices[key] = []\n }\n this.wordIndices[key].push(i)\n }\n }\n\n private static putNewWord(block: string[], word: string, blockSize: number): string | null {\n block.push(word)\n\n if (block.length > blockSize) {\n block.shift()\n }\n\n if (block.length !== blockSize) {\n return null\n }\n\n return block.join('')\n }\n\n private normalizeForIndex(word: string): string {\n const output = Utils.stripAnyAttributes(word)\n if (this.options.ignoreWhitespaceDifferences && Utils.isWhiteSpace(output)) {\n return ' '\n }\n\n return output\n }\n\n findMatch(): Match | null {\n this.indexNewWords()\n this.removeRepeatingWords()\n\n let hasIndices = false\n for (const _key in this.wordIndices) {\n hasIndices = true\n break\n }\n if (!hasIndices) {\n return null\n }\n\n let bestMatchInOld = this.startInOld\n let bestMatchInNew = this.startInNew\n let bestMatchSize = 0\n\n let matchLengthAt: Map<number, number> = new Map()\n const block: string[] = []\n\n for (let indexInOld = this.startInOld; indexInOld < this.endInOld; indexInOld++) {\n const word = this.normalizeForIndex(this.oldWords[indexInOld])\n const index = MatchFinder.putNewWord(block, word, this.options.blockSize)\n\n if (index === null) {\n continue\n }\n\n const newMatchLengthAt: Map<number, number> = new Map()\n\n if (!this.wordIndices[index]) {\n matchLengthAt = newMatchLengthAt\n continue\n }\n\n for (const indexInNew of this.wordIndices[index]) {\n // biome-ignore lint/style/noNonNullAssertion: This is safe as guarded by has()\n const newMatchLength = (matchLengthAt.has(indexInNew - 1) ? matchLengthAt.get(indexInNew - 1)! : 0) + 1\n newMatchLengthAt.set(indexInNew, newMatchLength)\n\n if (newMatchLength > bestMatchSize) {\n bestMatchInOld = indexInOld - newMatchLength - this.options.blockSize + 2\n bestMatchInNew = indexInNew - newMatchLength - this.options.blockSize + 2\n bestMatchSize = newMatchLength\n }\n }\n\n matchLengthAt = newMatchLengthAt\n }\n\n return bestMatchSize !== 0\n ? new Match(bestMatchInOld, bestMatchInNew, bestMatchSize + this.options.blockSize - 1)\n : null\n }\n\n /**\n * This method removes words that occur too many times. This way it reduces total count of comparison operations\n * and as result the diff algorithm takes less time. But the side effect is that it may detect false differences of\n * the repeating words.\n * @private\n */\n private removeRepeatingWords() {\n const threshold = this.newWords.length * this.options.repeatingWordsAccuracy\n const repeatingWords = Object.entries(this.wordIndices)\n .filter(([, indices]) => indices.length > threshold)\n .map(([word]) => word)\n\n for (const w of repeatingWords) {\n delete this.wordIndices[w]\n }\n }\n}\n","import type Action from './Action'\n\nexport default class Operation {\n action: Action\n startInOld: number\n endInOld: number\n startInNew: number\n endInNew: number\n\n constructor(action: Action, startInOld: number, endInOld: number, startInNew: number, endInNew: number) {\n this.action = action\n this.startInOld = startInOld\n this.endInOld = endInOld\n this.startInNew = startInNew\n this.endInNew = endInNew\n }\n}\n","/**\n * Generic sequence-alignment primitives used by the table-aware diff and\n * potentially other granularities (rows, cells, list items, …). Nothing\n * here knows about tables or HTML — the caller passes string keys for\n * exact matching and a similarity callback for fuzzy pairing.\n */\n\nexport interface Alignment {\n oldIdx: number | null\n newIdx: number | null\n}\n\n/**\n * Standard LCS alignment: walks both sequences and emits a list of pairs\n * where `(oldIdx, newIdx)` are both set for matching positions, and one\n * side is null for an unmatched entry on the other side. Equality uses\n * strict ===.\n */\nexport function lcsAlign(oldKeys: string[], newKeys: string[]): Alignment[] {\n const m = oldKeys.length\n const n = newKeys.length\n const dp: number[][] = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0))\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (oldKeys[i - 1] === newKeys[j - 1]) {\n dp[i][j] = dp[i - 1][j - 1] + 1\n } else {\n dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])\n }\n }\n }\n\n // Backtrack and push; reverse at the end. `unshift` is O(n) per call\n // so the naive version was O(n²); push+reverse is O(n) total.\n const result: Alignment[] = []\n let i = m\n let j = n\n while (i > 0 || j > 0) {\n if (i > 0 && j > 0 && oldKeys[i - 1] === newKeys[j - 1]) {\n result.push({ oldIdx: i - 1, newIdx: j - 1 })\n i--\n j--\n } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {\n result.push({ oldIdx: null, newIdx: j - 1 })\n j--\n } else {\n result.push({ oldIdx: i - 1, newIdx: null })\n i--\n }\n }\n result.reverse()\n return result\n}\n\n/**\n * Given a shorter sequence (M items) and a longer sequence (N items, with\n * N > M), find the K = N - M positions in the longer sequence that should\n * be \"skipped\" so the unskipped longer items, aligned positionally with\n * the shorter items, maximise the sum of pairwise similarity.\n *\n * Solves the same problem as enumerating C(N, K) skip combinations and\n * picking the highest-scoring one, but in O(M × N) time via DP:\n *\n * f(i, j) = max similarity from consuming i shorter and j longer items\n * (defined for j >= i; entries below the diagonal are never\n * written or read).\n * f(0, j) = 0\n * f(i, j) = max(\n * f(i-1, j-1) + similarity(i-1, j-1), // pair\n * f(i, j-1) // skip longer[j-1]\n * )\n *\n * Tie-breaking prefers pairing over skipping, so ties resolve to skipping\n * EARLIER positions — matching the lex-first-combo behaviour of a full\n * combinatorial enumeration over which K positions to skip. Backtrack\n * re-asks the fill's pair-vs-skip question to preserve this direction\n * (the alternative — a `dp[i][j] > dp[i][j-1]` shortcut — would invert\n * the tie-breaking).\n *\n * Caller responsibility: ensure `longerTexts.length >= shorterTexts.length`.\n */\nexport function findOptimalAlignmentSkips(\n shorterTexts: string[],\n longerTexts: string[],\n similarity: (shorterIdx: number, longerIdx: number) => number\n): number[] {\n const m = shorterTexts.length\n const n = longerTexts.length\n // dp[i][j] is valid only for j >= i; entries below the diagonal are\n // allocated (uniform-shaped matrix keeps the indexing straight) but\n // never written or read. The wasted (M choose 2)-ish cells are not\n // worth \"optimising\" — a triangular layout would complicate the\n // backtrack's `dp[i][j-1]` reads and the (j > i ? skip : NEG_INF)\n // boundary handling, with no measurable win at the sizes this runs\n // on (capped by MAX_COLUMN_SEARCH_WIDTH).\n const dp: number[][] = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0))\n for (let i = 1; i <= m; i++) {\n for (let j = i; j <= n; j++) {\n const pair = dp[i - 1][j - 1] + similarity(i - 1, j - 1)\n const skip = j > i ? dp[i][j - 1] : Number.NEGATIVE_INFINITY\n dp[i][j] = pair >= skip ? pair : skip\n }\n }\n\n // Backtrack from (m, n). To preserve the fill's \"prefer pair on ties\"\n // direction we have to ask the same question the fill asked:\n // pair = dp[i-1][j-1] + similarity(i-1, j-1)\n // skip = dp[i][j-1]\n // and choose pair iff pair >= skip. A `dp[i][j] > dp[i][j-1]` shortcut\n // would invert the tie-breaking (it'd skip earlier positions on ties)\n // and shift outputs for score-tied scenarios — see the\n // `column-position search — score-tied inputs` regression tests in\n // `HtmlDiff.tables.spec.ts`. The extra similarity calls during\n // backtrack run O(M+N) times total, dwarfed by the O(M × N) fill.\n const skipped: number[] = []\n let i = m\n let j = n\n while (j > 0) {\n if (i === 0) {\n skipped.push(j - 1)\n j--\n continue\n }\n if (j === i) {\n // No slack left — every remaining move is a pair.\n i--\n j--\n continue\n }\n const pair = dp[i - 1][j - 1] + similarity(i - 1, j - 1)\n const skip = dp[i][j - 1]\n if (pair >= skip) {\n i--\n j--\n } else {\n skipped.push(j - 1)\n j--\n }\n }\n skipped.reverse()\n return skipped\n}\n\n/**\n * Identifies pairings inside each unmatched-only run, then builds the\n * output alignment by walking the original and substituting paired\n * entries at the *ins position* (not the del position). This keeps the\n * result monotonically non-decreasing in newIdx — required by any\n * downstream emission that walks the new sequence in order. Emitting at\n * the del position would be safe when del<ins in the alignment array\n * (the typical case), but can violate monotonicity when unpaired\n * entries interleave with paired ones in the same run.\n *\n * Greedy assignment: the first del in document order wins its best ins.\n * Suboptimal vs Hungarian on edge cases (two dels above threshold for\n * the same ins), but bounded — a losing del just emits as a full delete\n * rather than a content edit.\n */\nexport function pairSimilarUnmatched(\n alignment: Alignment[],\n threshold: number,\n similarity: (oldIdx: number, newIdx: number) => number\n): Alignment[] {\n const pairs = new Map<number, number>() // del-alignment-idx → ins-alignment-idx\n let i = 0\n while (i < alignment.length) {\n if (alignment[i].oldIdx !== null && alignment[i].newIdx !== null) {\n i++\n continue\n }\n const runStart = i\n while (i < alignment.length && (alignment[i].oldIdx === null) !== (alignment[i].newIdx === null)) i++\n const runEnd = i\n\n const delIndices: number[] = []\n const insIndices: number[] = []\n for (let k = runStart; k < runEnd; k++) {\n if (alignment[k].oldIdx !== null) delIndices.push(k)\n else insIndices.push(k)\n }\n\n const usedIns = new Set<number>()\n for (const di of delIndices) {\n let bestIi = -1\n let bestSim = threshold\n for (const ii of insIndices) {\n if (usedIns.has(ii)) continue\n const sim = similarity(alignment[di].oldIdx as number, alignment[ii].newIdx as number)\n if (sim > bestSim) {\n bestSim = sim\n bestIi = ii\n }\n }\n if (bestIi >= 0) {\n pairs.set(di, bestIi)\n usedIns.add(bestIi)\n }\n }\n }\n\n const insToDel = new Map<number, number>() // ins-alignment-idx → del-alignment-idx\n for (const [delAi, insAi] of pairs) insToDel.set(insAi, delAi)\n const pairedDels = new Set<number>(pairs.keys())\n\n const result: Alignment[] = []\n for (let k = 0; k < alignment.length; k++) {\n if (pairedDels.has(k)) continue // paired del — emitted when we reach its ins\n if (insToDel.has(k)) {\n const delAi = insToDel.get(k) as number\n result.push({ oldIdx: alignment[delAi].oldIdx, newIdx: alignment[k].newIdx })\n } else {\n result.push(alignment[k])\n }\n }\n return result\n}\n\n/**\n * Reorders the alignment so a cursor-based emission walking the new\n * sequence in order produces entries in their visually-correct\n * position. Each entry is assigned a fractional \"position\" in new's\n * flow:\n *\n * • Preserved/paired (oldIdx, newIdx): position = newIdx.\n * • Pure insert (null, newIdx): position = newIdx.\n * • Pure delete (oldIdx, null): position = newIdx-of-preserved-just-\n * before-this-oldIdx + 0.5. Dels at the same gap sort by oldIdx so\n * they appear in old's source order. The +0.5 places dels BEFORE\n * any insert at the same gap (insert at newIdx N1+1 has position\n * N1+1 which is > N1+0.5), giving the natural \"delete first, insert\n * second\" reading order at a replaced position.\n *\n * Handles the full range:\n * • Run of unpaired dels at the start (no preserved predecessor):\n * position -0.5, sorted by oldIdx.\n * • Dels in the middle: positioned right after their preceding\n * preserved entry.\n * • Dels at the end (no preserved successor): positioned after the\n * last preserved entry.\n *\n * Without this reordering, a run of unpaired deletes ahead of any\n * preserved entry would be emitted before the first preserved entry,\n * regardless of where they originated in old.\n *\n * NB: `0.5` is the ONLY fractional offset used. If another decoration\n * kind ever needs a fractional position too, redesign this scheme\n * (e.g. a discrete `(integerSlot, kind, secondary)` triple) rather than\n * picking another magic offset and hoping it doesn't collide.\n */\nexport function orderAlignmentForEmission(alignment: Alignment[]): Alignment[] {\n const preserved: Array<{ oldIdx: number; newIdx: number }> = []\n for (const a of alignment) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n preserved.push({ oldIdx: a.oldIdx, newIdx: a.newIdx })\n }\n }\n preserved.sort((a, b) => a.oldIdx - b.oldIdx)\n\n // For a deleted entry with oldIdx K, return the newIdx of the preserved\n // entry with the largest oldIdx less than K, or -1 if none.\n function newIdxOfPreservedBefore(oldIdx: number): number {\n let result = -1\n for (const p of preserved) {\n if (p.oldIdx >= oldIdx) break\n result = p.newIdx\n }\n return result\n }\n\n // Decorate each alignment with a fractional position. We use\n // (primary, secondary) tuples so dels at the same gap sort by oldIdx\n // (in old's source order) and inserts at the same newIdx stay stable.\n const decorated = alignment.map((a, i) => {\n let primary: number\n let secondary: number\n if (a.newIdx !== null) {\n primary = a.newIdx\n secondary = a.oldIdx === null ? 1 : 0 // preserved before pure-insert at same newIdx (rare)\n } else {\n // Pure delete\n primary = newIdxOfPreservedBefore(a.oldIdx as number) + 0.5\n secondary = a.oldIdx as number\n }\n return { entry: a, primary, secondary, originalIdx: i }\n })\n\n decorated.sort((a, b) => {\n if (a.primary !== b.primary) return a.primary - b.primary\n if (a.secondary !== b.secondary) return a.secondary - b.secondary\n return a.originalIdx - b.originalIdx // stable\n })\n\n return decorated.map(d => d.entry)\n}\n\n/**\n * Combined similarity metric used for fuzzy pairing. Returns the MAX of\n * two complementary metrics:\n *\n * 1. **Character prefix+suffix similarity** — fraction of the longer\n * string covered by shared prefix + shared suffix. Catches small\n * edits in the middle of a string (one word changed). Misses cases\n * where the bulk of common content is in the middle and the ends\n * differ.\n *\n * 2. **Token Jaccard similarity** — intersection-over-union of the\n * whitespace-split tokens. Catches \"most of the content is the\n * same but bookended by different bits\" — e.g. an edit where the\n * ~50 chars in the middle that DO match would be invisible to\n * prefix+suffix.\n *\n * Either metric exceeding the threshold means pair. Neither alone is\n * sufficient for the full range of legal-doc edits we see in\n * production tables.\n */\nexport function textSimilarity(a: string, b: string): number {\n if (a === b) return 1\n if (a.length === 0 || b.length === 0) return 0\n return Math.max(charPrefixSuffixSimilarity(a, b), tokenJaccardSimilarity(a, b))\n}\n\nfunction charPrefixSuffixSimilarity(a: string, b: string): number {\n let prefix = 0\n const minLen = Math.min(a.length, b.length)\n while (prefix < minLen && a[prefix] === b[prefix]) prefix++\n\n let suffix = 0\n while (\n suffix < a.length - prefix &&\n suffix < b.length - prefix &&\n a[a.length - 1 - suffix] === b[b.length - 1 - suffix]\n ) {\n suffix++\n }\n\n return (prefix + suffix) / Math.max(a.length, b.length)\n}\n\nfunction tokenJaccardSimilarity(a: string, b: string): number {\n const tokensA = new Set(a.split(/\\s+/).filter(Boolean))\n const tokensB = new Set(b.split(/\\s+/).filter(Boolean))\n if (tokensA.size === 0 && tokensB.size === 0) return 1\n let intersection = 0\n for (const t of tokensA) {\n if (tokensB.has(t)) intersection++\n }\n const union = tokensA.size + tokensB.size - intersection\n return union === 0 ? 0 : intersection / union\n}\n","/**\n * Low-level HTML tag-parsing primitives shared by the table-aware\n * preprocessing and (potentially) other consumers. These are deliberately\n * generic over the document type — no table-specific assumptions live\n * here.\n *\n * The goal is to walk HTML at the tag boundary level *without* parsing\n * into a full DOM, so we stay fast and never round-trip through\n * htmlparser2/DOMPurify in the diff hot path.\n */\n\nexport interface OpeningTag {\n /** Index just past the closing `>` of the opening tag. */\n end: number\n}\n\nexport interface ClassAttributeLocation {\n /** Index of the value's first character (inside the surrounding quotes). */\n valueStart: number\n /** Index just past the last character of the value (still inside quotes). */\n valueEnd: number\n /** The class attribute's value, with surrounding quotes stripped. */\n value: string\n}\n\n/**\n * Parses the opening tag (or comment/CDATA/PI) starting at `i`. Returns\n * the index just past the closing delimiter, or null if the tag is\n * malformed (unterminated). HTML comments, CDATA, processing\n * instructions, and DOCTYPE need their own terminators — a plain\n * `>`-walker would cut a comment like `<!-- a > b -->` at the first\n * inner `>`, treating the rest as text and corrupting downstream\n * offsets. Word-exported HTML routinely emits comments inside tables\n * (conditional comments, OLE markers) so these have to be handled.\n */\nexport function parseOpeningTagAt(html: string, i: number): OpeningTag | null {\n if (html.startsWith('<!--', i)) {\n const close = html.indexOf('-->', i + 4)\n return close === -1 ? null : { end: close + 3 }\n }\n if (html.startsWith('<![CDATA[', i)) {\n const close = html.indexOf(']]>', i + 9)\n return close === -1 ? null : { end: close + 3 }\n }\n if (html.startsWith('<?', i)) {\n const close = html.indexOf('?>', i + 2)\n return close === -1 ? null : { end: close + 2 }\n }\n // Walk to the next unquoted '>'. Handles attributes whose values contain\n // a literal '>' inside quotes, which a plain indexOf would mishandle.\n let j = i + 1\n let quote: string | null = null\n while (j < html.length) {\n const ch = html[j]\n if (quote) {\n if (ch === quote) quote = null\n } else if (ch === '\"' || ch === \"'\") {\n quote = ch\n } else if (ch === '>') {\n return { end: j + 1 }\n }\n j++\n }\n return null\n}\n\nexport function matchesTagAt(html: string, i: number, tagName: string): boolean {\n if (html[i] !== '<') return false\n const candidate = html.slice(i + 1, i + 1 + tagName.length).toLowerCase()\n if (candidate !== tagName) return false\n const after = html[i + 1 + tagName.length]\n return after === '>' || after === ' ' || after === '\\t' || after === '\\n' || after === '\\r' || after === '/'\n}\n\nexport function matchesClosingTagAt(html: string, i: number, tagName: string): boolean {\n if (html[i] !== '<' || html[i + 1] !== '/') return false\n const candidate = html.slice(i + 2, i + 2 + tagName.length).toLowerCase()\n if (candidate !== tagName) return false\n const after = html[i + 2 + tagName.length]\n return after === '>' || after === ' ' || after === '\\t' || after === '\\n' || after === '\\r'\n}\n\n/**\n * Returns the index just past the matching `</tagName>`, accounting for\n * nested tags of the same name. Returns -1 if no match before `limit`.\n */\nexport function findMatchingClosingTag(\n html: string,\n from: number,\n tagName: string,\n limit: number = html.length\n): number {\n let depth = 1\n let i = from\n while (i < limit) {\n if (matchesTagAt(html, i, tagName)) {\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const tagText = html.slice(i, opening.end)\n if (!tagText.endsWith('/>')) depth++\n i = opening.end\n } else if (matchesClosingTagAt(html, i, tagName)) {\n depth--\n const closing = parseOpeningTagAt(html, i)\n const closingEnd = closing?.end ?? i + `</${tagName}>`.length\n if (depth === 0) return closingEnd\n i = closingEnd\n } else {\n i++\n }\n }\n return -1\n}\n\n/**\n * Returns the opening tag with the given class injected. Locates the real\n * `class` attribute via attribute-aware walking (NOT a flat regex — that\n * would mis-match inside a foreign attribute value like\n * `title=\"see class='x'\"`). When the class already partially overlaps with\n * `cls` — e.g. existing `class=\"mod\"` and we're injecting `mod colspan` —\n * only the missing tokens get appended, so we never end up with\n * `class=\"mod mod colspan\"`.\n */\nexport function injectClass(openingTag: string, cls: string): string {\n const clsTokens = cls.split(/\\s+/).filter(Boolean)\n if (clsTokens.length === 0) return openingTag\n\n const classAttr = findClassAttribute(openingTag)\n if (classAttr) {\n const existingTokens = classAttr.value.split(/\\s+/).filter(Boolean)\n const missing = clsTokens.filter(t => !existingTokens.includes(t))\n if (missing.length === 0) return openingTag\n const updatedValue =\n existingTokens.length === 0 ? missing.join(' ') : `${existingTokens.join(' ')} ${missing.join(' ')}`\n return openingTag.slice(0, classAttr.valueStart) + updatedValue + openingTag.slice(classAttr.valueEnd)\n }\n\n const isSelfClosing = openingTag.endsWith('/>')\n const insertAt = isSelfClosing ? openingTag.length - 2 : openingTag.length - 1\n return `${openingTag.slice(0, insertAt).replace(/\\s*$/, '')} class='${cls}'${openingTag.slice(insertAt)}`\n}\n\n/**\n * Walks the opening tag's attributes (respecting quoted values) to find\n * the actual `class` attribute. Returns the value range (start/end of the\n * value content, *excluding* the surrounding quotes) and the value, or\n * null if no `class` attribute is present.\n */\nexport function findClassAttribute(openingTag: string): ClassAttributeLocation | null {\n // Skip past the tag name. Tag starts with `<`; first run of [A-Za-z0-9-]\n // is the tag name. Anything after is attribute territory.\n let i = 1\n while (i < openingTag.length && /[A-Za-z0-9_:-]/.test(openingTag[i])) i++\n\n while (i < openingTag.length) {\n // Skip whitespace\n while (i < openingTag.length && /\\s/.test(openingTag[i])) i++\n if (i >= openingTag.length) break\n if (openingTag[i] === '>' || openingTag[i] === '/') break\n\n // Read attribute name\n const nameStart = i\n while (i < openingTag.length && !/[\\s=>/]/.test(openingTag[i])) i++\n const name = openingTag.slice(nameStart, i)\n\n // Optional whitespace + '=' + optional whitespace + value\n while (i < openingTag.length && /\\s/.test(openingTag[i])) i++\n if (openingTag[i] !== '=') {\n // Bare attribute (no value) — not class\n continue\n }\n i++ // past '='\n while (i < openingTag.length && /\\s/.test(openingTag[i])) i++\n\n // Value: quoted or unquoted\n let valueStart: number\n let valueEnd: number\n if (openingTag[i] === '\"' || openingTag[i] === \"'\") {\n const quote = openingTag[i]\n i++\n valueStart = i\n while (i < openingTag.length && openingTag[i] !== quote) i++\n valueEnd = i\n if (i < openingTag.length) i++ // past closing quote\n } else {\n valueStart = i\n while (i < openingTag.length && !/[\\s>/]/.test(openingTag[i])) i++\n valueEnd = i\n }\n\n if (name.toLowerCase() === 'class') {\n return { valueStart, valueEnd, value: openingTag.slice(valueStart, valueEnd) }\n }\n }\n\n return null\n}\n","import {\n type Alignment,\n findOptimalAlignmentSkips,\n lcsAlign,\n orderAlignmentForEmission,\n pairSimilarUnmatched,\n textSimilarity,\n} from './Alignment'\nimport {\n findMatchingClosingTag,\n injectClass,\n matchesClosingTagAt,\n matchesTagAt,\n parseOpeningTagAt,\n} from './HtmlScanner'\nimport { wrapText } from './Utils'\n\n/**\n * Table-aware preprocessing for HtmlDiff.\n *\n * The word-level diff alone matches longest-common-subsequences across cell\n * boundaries and produces structurally wrong output for table edits — it\n * shuffles content between cells, introduces phantom `<td>`s, and provides\n * no signal that an entire row or column was added/deleted. We pre-process\n * the inputs to give Word-style results:\n *\n * • When dimensions match (same row count, same cell count per row), we\n * diff cell content positionally so cross-cell shifts produce one\n * independent del/ins per cell.\n * • When dimensions don't match (added/deleted row, added/deleted column),\n * we run a row-level LCS to identify structurally added/deleted rows,\n * then within preserved rows a cell-level LCS to identify added/deleted\n * columns. Structurally added rows/cells get `class='diffins'` on the\n * `<tr>`/`<td>`; deleted ones get `class='diffdel'`. Preserved cells\n * fall back to a content diff via the recursive HtmlDiff callback.\n *\n * Tables are spliced out into placeholders before the main diff runs and\n * spliced back in after, so the surrounding (non-table) content is diffed\n * by the normal word-level pipeline.\n */\n\nexport interface CellRange {\n /** Start index of the cell's opening tag in the original html. */\n cellStart: number\n /** Index just past the cell's closing tag. */\n cellEnd: number\n /** Cell content range — the slice we feed into the cell-level diff. */\n contentStart: number\n contentEnd: number\n}\n\nexport interface RowRange {\n rowStart: number\n rowEnd: number\n cells: CellRange[]\n}\n\nexport interface TableRange {\n tableStart: number\n tableEnd: number\n rows: RowRange[]\n}\n\nexport interface PreprocessResult {\n modifiedOld: string\n modifiedNew: string\n /** Maps placeholder marker → already-diffed table HTML to splice back in. */\n placeholderToDiff: Map<string, string>\n}\n\n// HTML comments survive WordSplitter as a single atomic token and are\n// treated as equal on both sides, so they pass through the diff\n// untouched and are easy to substitute back later. The nonce is generated\n// per call so a previously-diffed document being re-diffed (or any input\n// that legitimately contains an `<!--HTMLDIFF_TABLE_*-->` comment) can't\n// collide with the placeholder we substitute. We additionally regenerate\n// the nonce if it appears in either input.\nconst PLACEHOLDER_PREFIX_BASE = '<!--HTMLDIFF_TABLE_'\nconst PLACEHOLDER_SUFFIX = '-->'\n\n/**\n * Hard cap on table dimensions handled by the structural-aware path.\n * The row-LCS is O(rows²), the per-row cell-LCS is O(cells²), and each\n * comparison string-equals row content (potentially many KB). Without a\n * cap, a several-thousand-row table can pin a CPU for seconds. Tables\n * larger than this fall through to the word-level diff, which scales\n * linearly. Tuned to comfortably cover real-world ISDA schedules\n * (which routinely have 1000+ rows).\n */\nconst MAX_TABLE_ROWS = 1500\nconst MAX_TABLE_CELLS_PER_ROW = 200\n\n// Caps for the per-row column-position DP in\n// findBestColumnInsertPositions / findBestColumnDeletePositions.\n// MAX_COLUMN_DELTA is the *semantic* guard: a row with more than 6\n// columns added or deleted is almost always a row rewrite rather than\n// a structural column change, and is better handled by cell-LCS with\n// fuzzy pairing. MAX_COLUMN_SEARCH_WIDTH bounds the per-row DP at\n// O(MAX_COLUMN_SEARCH_WIDTH²) ≈ 40K ops; aligned with\n// MAX_TABLE_CELLS_PER_ROW so any row that survives the table-size cap\n// can still use the DP path.\nconst MAX_COLUMN_DELTA = 6\nconst MAX_COLUMN_SEARCH_WIDTH = 200\n\n/**\n * Generate a placeholder-prefix nonce that doesn't collide with any\n * existing content in the inputs. Variadic so callers with N inputs\n * (e.g. three-way diff with V1/V2/V3) check across all of them.\n */\nexport function makePlaceholderPrefix(...inputs: string[]): string {\n // 4 random bytes → 8 hex chars → 16^8 ≈ 4.3 billion combinations. We\n // also retry if the generated nonce happens to occur in any input.\n // Using `Math.random` here is fine: we're not defending against a\n // malicious adversary, just avoiding accidental collisions.\n for (let attempt = 0; attempt < 8; attempt++) {\n const nonce = Math.floor(Math.random() * 0xffffffff)\n .toString(16)\n .padStart(8, '0')\n const prefix = `${PLACEHOLDER_PREFIX_BASE}${nonce}_`\n if (inputs.every(input => !input.includes(prefix))) {\n return prefix\n }\n }\n // Astronomically unlikely. Falling back to a counter ensures progress\n // rather than an infinite loop, and any remaining collision will simply\n // surface as a malformed diff that the caller can detect.\n return `${PLACEHOLDER_PREFIX_BASE}fallback_${Date.now()}_`\n}\n\nexport { PLACEHOLDER_SUFFIX }\n\ntype DiffCellFn = (oldCellContent: string, newCellContent: string) => string\n\n/**\n * Diffs every paired-by-position table in the inputs and replaces each\n * source table with a placeholder, returning the modified inputs plus the\n * placeholder→diff mapping. Returns null when there are no tables to\n * preprocess or the table counts don't line up.\n */\nexport function preprocessTables(oldHtml: string, newHtml: string, diffCell: DiffCellFn): PreprocessResult | null {\n const oldTables = findTopLevelTables(oldHtml)\n const newTables = findTopLevelTables(newHtml)\n\n if (oldTables.length === 0 && newTables.length === 0) return null\n if (oldTables.length !== newTables.length) return null\n\n // Bail out on pathologically large tables — see MAX_TABLE_ROWS comment.\n for (let i = 0; i < oldTables.length; i++) {\n if (exceedsSizeLimit(oldTables[i]) || exceedsSizeLimit(newTables[i])) return null\n }\n\n const pairs: Array<{ oldTable: TableRange; newTable: TableRange; diffed: string }> = []\n for (let i = 0; i < oldTables.length; i++) {\n pairs.push({\n oldTable: oldTables[i],\n newTable: newTables[i],\n diffed: diffTable(oldHtml, newHtml, oldTables[i], newTables[i], diffCell),\n })\n }\n\n // Splice from end → start so earlier offsets stay valid.\n let modifiedOld = oldHtml\n let modifiedNew = newHtml\n const placeholderPrefix = makePlaceholderPrefix(oldHtml, newHtml)\n const placeholderToDiff = new Map<string, string>()\n for (let i = pairs.length - 1; i >= 0; i--) {\n const placeholder = `${placeholderPrefix}${i}${PLACEHOLDER_SUFFIX}`\n placeholderToDiff.set(placeholder, pairs[i].diffed)\n modifiedOld = spliceString(modifiedOld, pairs[i].oldTable.tableStart, pairs[i].oldTable.tableEnd, placeholder)\n modifiedNew = spliceString(modifiedNew, pairs[i].newTable.tableStart, pairs[i].newTable.tableEnd, placeholder)\n }\n\n return { modifiedOld, modifiedNew, placeholderToDiff }\n}\n\nexport function restoreTablePlaceholders(diffOutput: string, placeholderToDiff: Map<string, string>): string {\n let result = diffOutput\n for (const [placeholder, html] of placeholderToDiff) {\n result = result.split(placeholder).join(html)\n }\n return result\n}\n\nexport function spliceString(s: string, start: number, end: number, replacement: string): string {\n return s.slice(0, start) + replacement + s.slice(end)\n}\n\nexport function exceedsSizeLimit(table: TableRange): boolean {\n if (table.rows.length > MAX_TABLE_ROWS) return true\n for (const row of table.rows) {\n if (row.cells.length > MAX_TABLE_CELLS_PER_ROW) return true\n }\n return false\n}\n\nfunction diffTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n if (sameDimensions(oldTable, newTable)) {\n return diffPositionalTable(oldHtml, newHtml, oldTable, newTable, diffCell)\n }\n if (oldTable.rows.length === newTable.rows.length) {\n // Same row count, different cell counts: column add/delete only.\n // Aligning rows positionally avoids the LCS row-key mismatch that\n // happens when rows have different cell counts.\n return diffSameRowCountTable(oldHtml, newHtml, oldTable, newTable, diffCell)\n }\n return diffStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, diffCell)\n}\n\nfunction diffSameRowCountTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n // Walk the new table verbatim (preserving `<thead>`/`<tbody>` wrappers,\n // whitespace, etc.) and substitute each row's content with the diffed\n // form. The cursor-based emission keeps everything between rows intact.\n const out: string[] = []\n let cursor = newTable.tableStart\n let r = 0\n while (r < newTable.rows.length) {\n const merge = detectVerticalMerge(oldHtml, newHtml, oldTable, newTable, r)\n if (merge) {\n out.push(newHtml.slice(cursor, newTable.rows[r].rowStart))\n out.push(merge.diff)\n cursor = newTable.rows[r + merge.span - 1].rowEnd\n r += merge.span\n continue\n }\n const split = detectVerticalSplit(oldHtml, newHtml, oldTable, newTable, r)\n if (split) {\n out.push(newHtml.slice(cursor, newTable.rows[r].rowStart))\n out.push(split.diff)\n cursor = newTable.rows[r + split.span - 1].rowEnd\n r += split.span\n continue\n }\n const newRow = newTable.rows[r]\n out.push(newHtml.slice(cursor, newRow.rowStart))\n out.push(diffPreservedRow(oldHtml, newHtml, oldTable.rows[r], newRow, diffCell))\n cursor = newRow.rowEnd\n r++\n }\n out.push(newHtml.slice(cursor, newTable.tableEnd))\n return out.join('')\n}\n\n/**\n * Detects a vertical merge starting at row `r`: new row R has a single\n * cell with rowspan=K (and any colspan ≥ 1), with rows R+1..R+K-1 empty\n * in new. Old rows R..R+K-1 must have a logical column width equal to\n * the new cell's colspan and contain no rowspan'd cells of their own.\n * This handles both single-column merges (old rows are 1-cell, new cell\n * rowspan=K) and rectangular merges (e.g. 2×2 merge into a single\n * colspan=2 rowspan=2 cell). Output: emit the merged cell with\n * `class='mod rowspan'` and the empty trailing rows unchanged.\n */\nfunction detectVerticalMerge(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n r: number\n): { diff: string; span: number } | null {\n const newRow = newTable.rows[r]\n if (newRow.cells.length !== 1) return null\n const cell = newRow.cells[0]\n const span = getRowspan(newHtml, cell)\n if (span <= 1) return null\n if (r + span > newTable.rows.length) return null\n\n const colspan = getColspan(newHtml, cell)\n\n for (let k = 1; k < span; k++) {\n if (newTable.rows[r + k].cells.length !== 0) return null\n }\n for (let k = 0; k < span; k++) {\n const oldRow = oldTable.rows[r + k]\n if (!oldRow) return null\n // The absorbed region's logical width must match the merged cell's\n // colspan; otherwise this isn't a clean rectangular merge and we let\n // the caller fall through.\n if (sumColspans(oldHtml, oldRow.cells) !== colspan) return null\n for (const c of oldRow.cells) {\n if (getRowspan(oldHtml, c) !== 1) return null\n }\n }\n\n const out: string[] = []\n out.push(rowHeaderSlice(newHtml, newRow))\n out.push(emitSpanChangedCell(newHtml, cell, 'rowspan'))\n out.push('</tr>')\n for (let k = 1; k < span; k++) {\n out.push(emitEmptyRow(newHtml, newTable.rows[r + k]))\n }\n return { diff: out.join(''), span }\n}\n\n/**\n * Detects a vertical split starting at row `r`: old row R has a single\n * cell with rowspan=K, old rows R+1..R+K-1 are empty. New rows R..R+K-1\n * each have a single cell. Output: emit each new row with the new cell\n * tagged `class='mod rowspan'`.\n */\nfunction detectVerticalSplit(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n r: number\n): { diff: string; span: number } | null {\n const oldRow = oldTable.rows[r]\n if (oldRow.cells.length !== 1) return null\n const oldCell = oldRow.cells[0]\n const span = getRowspan(oldHtml, oldCell)\n if (span <= 1) return null\n if (r + span > oldTable.rows.length) return null\n\n const colspan = getColspan(oldHtml, oldCell)\n\n for (let k = 1; k < span; k++) {\n if (oldTable.rows[r + k].cells.length !== 0) return null\n }\n for (let k = 0; k < span; k++) {\n const newRow = newTable.rows[r + k]\n if (!newRow) return null\n // New rows must collectively cover the same logical width as the old\n // merged cell's colspan, with no rowspan'd cells of their own.\n if (sumColspans(newHtml, newRow.cells) !== colspan) return null\n for (const c of newRow.cells) {\n if (getRowspan(newHtml, c) !== 1) return null\n }\n }\n\n const out: string[] = []\n for (let k = 0; k < span; k++) {\n const newRow = newTable.rows[r + k]\n out.push(rowHeaderSlice(newHtml, newRow))\n for (const c of newRow.cells) {\n out.push(emitSpanChangedCell(newHtml, c, 'rowspan'))\n }\n out.push('</tr>')\n }\n return { diff: out.join(''), span }\n}\n\nfunction emitEmptyRow(html: string, row: RowRange): string {\n // Re-emit the source row's `<tr ...></tr>` verbatim.\n return html.slice(row.rowStart, row.rowEnd)\n}\n\nexport function sameDimensions(a: TableRange, b: TableRange): boolean {\n if (a.rows.length !== b.rows.length) return false\n for (let i = 0; i < a.rows.length; i++) {\n if (a.rows[i].cells.length !== b.rows[i].cells.length) return false\n }\n return true\n}\n\n/**\n * Same-dimension path: walk the new table verbatim and substitute each\n * cell content range with the cell-level diff. The surrounding\n * `<thead>`/`<tbody>`/whitespace passes through untouched.\n */\nfunction diffPositionalTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n const out: string[] = []\n let cursor = newTable.tableStart\n for (let r = 0; r < newTable.rows.length; r++) {\n const oldRow = oldTable.rows[r]\n const newRow = newTable.rows[r]\n for (let c = 0; c < newRow.cells.length; c++) {\n const oldCell = oldRow.cells[c]\n const newCell = newRow.cells[c]\n out.push(newHtml.slice(cursor, newCell.contentStart))\n out.push(\n diffCell(\n oldHtml.slice(oldCell.contentStart, oldCell.contentEnd),\n newHtml.slice(newCell.contentStart, newCell.contentEnd)\n )\n )\n cursor = newCell.contentEnd\n }\n }\n out.push(newHtml.slice(cursor, newTable.tableEnd))\n return out.join('')\n}\n\n/**\n * Mismatched-dimensions path: row-level LCS to identify added/deleted rows,\n * then per preserved row a cell-level LCS to identify added/deleted cells.\n * Reconstructs the table from scratch — there's no \"single new structure\"\n * to walk verbatim, since we're stitching together kept rows from both\n * sides.\n */\nfunction diffStructurallyAlignedTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n const oldKeys = oldTable.rows.map(row => rowKey(oldHtml, row))\n const newKeys = newTable.rows.map(row => rowKey(newHtml, row))\n const exactAlignment = lcsAlign(oldKeys, newKeys)\n const paired = pairSimilarUnmatchedRows(exactAlignment, oldTable, newTable, oldHtml, newHtml)\n // Reorder so unpaired deleted rows appear at their *natural old-side\n // position* — immediately after the preserved/paired row that came\n // before them in old. Without this, runs of unpaired dels at low\n // alignment indices end up emitted before any preserved row (the\n // \"deleted rows out of order\" bug).\n const alignment = orderAlignmentForEmission(paired)\n\n // Walk new's tableStart→tableEnd, substituting rows with their diffed\n // form so `<thead>`/`<tbody>` wrappers and inter-row whitespace are\n // preserved verbatim. Deleted rows (no position in new) are injected\n // inline at the cursor's current position, which now corresponds to\n // their natural old-side slot thanks to the reordering above. If new\n // has no rows at all, fall back to a from-scratch reconstruction so\n // we still emit deleted rows.\n if (newTable.rows.length === 0) {\n return rebuildStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, alignment)\n }\n\n const out: string[] = []\n out.push(newHtml.slice(newTable.tableStart, newTable.rows[0].rowStart))\n let cursor = newTable.rows[0].rowStart\n for (const align of alignment) {\n if (align.newIdx !== null) {\n const newRow = newTable.rows[align.newIdx]\n out.push(newHtml.slice(cursor, newRow.rowStart))\n if (align.oldIdx !== null) {\n out.push(diffPreservedRow(oldHtml, newHtml, oldTable.rows[align.oldIdx], newRow, diffCell))\n } else {\n out.push(emitFullRow(newHtml, newRow, 'ins'))\n }\n cursor = newRow.rowEnd\n } else if (align.oldIdx !== null) {\n out.push(emitFullRow(oldHtml, oldTable.rows[align.oldIdx], 'del'))\n }\n }\n out.push(newHtml.slice(cursor, newTable.tableEnd))\n return out.join('')\n}\n\nfunction rebuildStructurallyAlignedTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n alignment: Alignment[]\n): string {\n // Used when new has no rows but old does — we lose the per-row\n // wrappers from new (there are none), so reconstruct from old's frame.\n const out: string[] = []\n out.push(headerSlice(newHtml, newTable, oldHtml, oldTable))\n for (const align of alignment) {\n if (align.oldIdx !== null) {\n out.push(emitFullRow(oldHtml, oldTable.rows[align.oldIdx], 'del'))\n } else if (align.newIdx !== null) {\n out.push(emitFullRow(newHtml, newTable.rows[align.newIdx], 'ins'))\n }\n }\n out.push('</table>')\n return out.join('')\n}\n\nfunction headerSlice(newHtml: string, newTable: TableRange, oldHtml: string, oldTable: TableRange): string {\n // Slice from <table> to the start of the first <tr>. Prefer new since\n // attribute changes on <table> itself should follow new.\n const newFirstRow = newTable.rows[0]?.rowStart ?? newTable.tableEnd - '</table>'.length\n if (newFirstRow > newTable.tableStart) {\n return newHtml.slice(newTable.tableStart, newFirstRow)\n }\n const oldFirstRow = oldTable.rows[0]?.rowStart ?? oldTable.tableEnd - '</table>'.length\n return oldHtml.slice(oldTable.tableStart, oldFirstRow)\n}\n\nexport function rowKey(html: string, row: RowRange): string {\n // Include cell tag text in the key so column-add doesn't accidentally\n // match a row to one with different cell counts. Whitespace-normalize to\n // tolerate formatting differences.\n return html.slice(row.rowStart, row.rowEnd).replace(/\\s+/g, ' ').trim()\n}\n\nfunction diffPreservedRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n if (oldRow.cells.length === newRow.cells.length) {\n return diffPositionalRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n }\n // Cell counts differ. Try to interpret it as a horizontal merge/split via\n // colspan first — preserving the new structure with `class='mod colspan'`\n // on each affected cell.\n const colspanAligned = diffColspanChangedRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n if (colspanAligned !== null) return colspanAligned\n // For column add/delete (cell counts differ), find the best insertion\n // or deletion positions via positional similarity scan and align the\n // remaining cells positionally. This handles content-edit alongside\n // column-add by keeping the edited cell in its column position rather\n // than orphaning it via the cell-LCS exact match.\n // Guardrail: O(M × N) DP scales fine within MAX_COLUMN_SEARCH_WIDTH;\n // wider rows fall through to cell-LCS so we don't run the per-row DP\n // on multi-thousand-cell exotica. MAX_COLUMN_DELTA stays as a\n // semantic guard — a delta > 6 usually means \"row rewrite\", not\n // \"column added\", and is better handled by cell-LCS.\n const delta = newRow.cells.length - oldRow.cells.length\n const absDelta = Math.abs(delta)\n if (\n absDelta > 0 &&\n absDelta <= MAX_COLUMN_DELTA &&\n Math.max(oldRow.cells.length, newRow.cells.length) <= MAX_COLUMN_SEARCH_WIDTH\n ) {\n if (delta > 0) return diffMultiColumnAddRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n return diffMultiColumnDeleteRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n }\n return diffStructurallyAlignedRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n}\n\n/**\n * For a row where new has more cells than old, find the column positions\n * in new where cells were inserted by running a monotonic-alignment DP\n * over the cell texts: pick the skip positions that maximise the sum-of-\n * similarities of the unskipped new cells aligned positionally against\n * the old cells. The inserted cells are emitted with diff markers; the\n * rest are aligned positionally with content diff for matched pairs.\n */\nfunction diffMultiColumnAddRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const insertedPositions = findBestColumnInsertPositions(oldRow, newRow, oldHtml, newHtml)\n const inserted = new Set(insertedPositions)\n const out: string[] = [rowHeaderSlice(newHtml, newRow)]\n let oldIdx = 0\n for (let c = 0; c < newRow.cells.length; c++) {\n if (inserted.has(c)) {\n out.push(emitFullCell(newHtml, newRow.cells[c], 'ins'))\n } else {\n out.push(emitDiffedCell(oldHtml, newHtml, oldRow.cells[oldIdx], newRow.cells[c], diffCell))\n oldIdx++\n }\n }\n out.push('</tr>')\n return out.join('')\n}\n\nfunction diffMultiColumnDeleteRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const deletedPositions = findBestColumnDeletePositions(oldRow, newRow, oldHtml, newHtml)\n const deleted = new Set(deletedPositions)\n const out: string[] = [rowHeaderSlice(newHtml, newRow)]\n let newIdx = 0\n for (let oldIdx = 0; oldIdx < oldRow.cells.length; oldIdx++) {\n if (deleted.has(oldIdx)) {\n out.push(emitFullCell(oldHtml, oldRow.cells[oldIdx], 'del'))\n continue\n }\n out.push(emitDiffedCell(oldHtml, newHtml, oldRow.cells[oldIdx], newRow.cells[newIdx], diffCell))\n newIdx++\n }\n out.push('</tr>')\n return out.join('')\n}\n\nfunction findBestColumnInsertPositions(oldRow: RowRange, newRow: RowRange, oldHtml: string, newHtml: string): number[] {\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n return findOptimalAlignmentSkips(oldTexts, newTexts, (oldIdx, newIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\nfunction findBestColumnDeletePositions(oldRow: RowRange, newRow: RowRange, oldHtml: string, newHtml: string): number[] {\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n return findOptimalAlignmentSkips(newTexts, oldTexts, (newIdx, oldIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\n/**\n * Try to align cells by logical column position (sum of colspans). When\n * one side has a colspan'd cell that absorbs multiple cells on the other\n * side, emit the new structure with `class='mod colspan'` on the\n * merged/split cells. Returns null if the rows don't align cleanly —\n * caller falls back to a generic cell-LCS.\n */\nfunction diffColspanChangedRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string | null {\n const oldWidth = sumColspans(oldHtml, oldRow.cells)\n const newWidth = sumColspans(newHtml, newRow.cells)\n if (oldWidth !== newWidth) return null\n\n const out: string[] = []\n out.push(rowHeaderSlice(newHtml, newRow))\n\n let oi = 0\n let ni = 0\n while (oi < oldRow.cells.length && ni < newRow.cells.length) {\n const oCell = oldRow.cells[oi]\n const nCell = newRow.cells[ni]\n const oSpan = getColspan(oldHtml, oCell)\n const nSpan = getColspan(newHtml, nCell)\n\n if (oSpan === nSpan) {\n out.push(emitDiffedCell(oldHtml, newHtml, oCell, nCell, diffCell))\n oi++\n ni++\n } else if (nSpan > oSpan) {\n // New cell absorbs multiple old cells — horizontal merge.\n let totalOldSpan = 0\n let oj = oi\n while (oj < oldRow.cells.length && totalOldSpan < nSpan) {\n totalOldSpan += getColspan(oldHtml, oldRow.cells[oj])\n oj++\n }\n if (totalOldSpan !== nSpan) return null\n out.push(emitSpanChangedCell(newHtml, nCell, 'colspan'))\n oi = oj\n ni++\n } else {\n // One old cell becomes multiple new cells — horizontal split.\n let totalNewSpan = 0\n let nj = ni\n while (nj < newRow.cells.length && totalNewSpan < oSpan) {\n totalNewSpan += getColspan(newHtml, newRow.cells[nj])\n nj++\n }\n if (totalNewSpan !== oSpan) return null\n for (let k = ni; k < nj; k++) {\n out.push(emitSpanChangedCell(newHtml, newRow.cells[k], 'colspan'))\n }\n oi++\n ni = nj\n }\n }\n\n // If we couldn't consume both sides cleanly, bail out.\n if (oi !== oldRow.cells.length || ni !== newRow.cells.length) return null\n\n out.push('</tr>')\n return out.join('')\n}\n\nfunction sumColspans(html: string, cells: CellRange[]): number {\n let total = 0\n for (const cell of cells) total += getColspan(html, cell)\n return total\n}\n\nfunction getColspan(html: string, cell: CellRange): number {\n return parseSpanAttribute(html.slice(cell.cellStart, cell.contentStart), 'colspan')\n}\n\nfunction getRowspan(html: string, cell: CellRange): number {\n return parseSpanAttribute(html.slice(cell.cellStart, cell.contentStart), 'rowspan')\n}\n\nfunction parseSpanAttribute(openingTag: string, name: 'colspan' | 'rowspan'): number {\n const re = name === 'colspan' ? /\\bcolspan\\s*=\\s*[\"']?(\\d+)[\"']?/i : /\\browspan\\s*=\\s*[\"']?(\\d+)[\"']?/i\n const m = re.exec(openingTag)\n if (!m) return 1\n const value = Number.parseInt(m[1], 10)\n return Number.isFinite(value) && value > 0 ? value : 1\n}\n\n/**\n * Emits a cell that's the merged/split product of a structural change,\n * tagged with `class='mod colspan'` or `class='mod rowspan'`. Content is\n * carried through unmodified — Word doesn't track these changes, and\n * inserting del/ins around content that didn't really change would be\n * misleading.\n */\nfunction emitSpanChangedCell(html: string, cell: CellRange, kind: 'colspan' | 'rowspan'): string {\n const tdOpening = parseOpeningTagAt(html, cell.cellStart)\n if (!tdOpening) return html.slice(cell.cellStart, cell.cellEnd)\n const tdOpenTag = injectClass(html.slice(cell.cellStart, tdOpening.end), `mod ${kind}`)\n return tdOpenTag + html.slice(cell.contentStart, cell.cellEnd)\n}\n\nfunction diffPositionalRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const out: string[] = []\n // Use new's <tr> opening tag (preserves attributes from new).\n const trHeader = rowHeaderSlice(newHtml, newRow)\n out.push(trHeader)\n\n let cursor = newRow.cells[0]?.cellStart ?? newRow.rowEnd\n for (let c = 0; c < newRow.cells.length; c++) {\n const oldCell = oldRow.cells[c]\n const newCell = newRow.cells[c]\n out.push(newHtml.slice(cursor, newCell.contentStart))\n out.push(\n diffCell(\n oldHtml.slice(oldCell.contentStart, oldCell.contentEnd),\n newHtml.slice(newCell.contentStart, newCell.contentEnd)\n )\n )\n cursor = newCell.contentEnd\n }\n out.push(newHtml.slice(cursor, newRow.rowEnd))\n return out.join('')\n}\n\nfunction diffStructurallyAlignedRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const oldKeys = oldRow.cells.map(cell => cellKey(oldHtml, cell))\n const newKeys = newRow.cells.map(cell => cellKey(newHtml, cell))\n const exactAlignment = lcsAlign(oldKeys, newKeys)\n // After exact LCS, fuzzy-pair adjacent unmatched old/new cells whose\n // content is similar enough — so a content-edit cell alongside a\n // column-add in the same row produces a content diff for the edited\n // cell rather than a phantom delete + insert + extra cell.\n const alignment = pairSimilarUnmatchedCells(exactAlignment, oldRow, newRow, oldHtml, newHtml)\n\n const out: string[] = []\n // Use new's <tr> if it exists; otherwise old's.\n out.push(rowHeaderSlice(newHtml, newRow))\n\n for (const align of alignment) {\n if (align.oldIdx !== null && align.newIdx !== null) {\n const oldCell = oldRow.cells[align.oldIdx]\n const newCell = newRow.cells[align.newIdx]\n out.push(emitDiffedCell(oldHtml, newHtml, oldCell, newCell, diffCell))\n } else if (align.newIdx !== null) {\n out.push(emitFullCell(newHtml, newRow.cells[align.newIdx], 'ins'))\n } else if (align.oldIdx !== null) {\n out.push(emitFullCell(oldHtml, oldRow.cells[align.oldIdx], 'del'))\n }\n }\n\n out.push('</tr>')\n return out.join('')\n}\n\nfunction cellKey(html: string, cell: CellRange): string {\n // Use cell content (not tag attributes) for matching, since column-add\n // typically changes content but not tag attributes — and matching purely\n // on attributes would mis-pair cells with the same content but different\n // styling.\n return html.slice(cell.contentStart, cell.contentEnd).replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * Emits a row with all cells either inserted (kind='ins') or deleted\n * (kind='del'). Adds `class='diffins'`/`'diffdel'` to the `<tr>` and to\n * each `<td>`, with an `<ins>`/`<del>` wrapper around any cell content\n * (empty cells get the class but no wrapper).\n */\nfunction emitFullRow(html: string, row: RowRange, kind: 'ins' | 'del'): string {\n const cls = kind === 'ins' ? 'diffins' : 'diffdel'\n const trOpening = parseOpeningTagAt(html, row.rowStart)\n if (!trOpening) return html.slice(row.rowStart, row.rowEnd)\n const trOpenTag = injectClass(html.slice(row.rowStart, trOpening.end), cls)\n\n const out: string[] = [trOpenTag]\n let cursor = trOpening.end\n for (const cell of row.cells) {\n out.push(html.slice(cursor, cell.cellStart))\n out.push(emitFullCell(html, cell, kind))\n cursor = cell.cellEnd\n }\n out.push(html.slice(cursor, row.rowEnd))\n return out.join('')\n}\n\n/**\n * Emits a fully-inserted or fully-deleted cell. Inner text runs are wrapped\n * with `<ins>`/`<del>` while formatting tags pass through unchanged, so\n * `<strong>B</strong>` renders as `<strong><ins>B</ins></strong>` —\n * matching htmldiff's general convention without the doubled-`<ins>` that\n * the full recursive diff would produce for newly-inserted formatting.\n * Empty cells get the class on the `<td>` but no inner wrapping.\n */\nfunction emitFullCell(html: string, cell: CellRange, kind: 'ins' | 'del'): string {\n const cls = kind === 'ins' ? 'diffins' : 'diffdel'\n const tdOpening = parseOpeningTagAt(html, cell.cellStart)\n if (!tdOpening) return html.slice(cell.cellStart, cell.cellEnd)\n const tdOpenTag = injectClass(html.slice(cell.cellStart, tdOpening.end), cls)\n\n const content = html.slice(cell.contentStart, cell.contentEnd)\n const wrapped = content.trim().length === 0 ? content : wrapInlineTextRuns(content, kind)\n const closing = html.slice(cell.contentEnd, cell.cellEnd)\n return tdOpenTag + wrapped + closing\n}\n\n/**\n * Wraps every non-whitespace text run in the given content with an\n * `<ins>`/`<del>` tag, leaving HTML tags untouched. This produces output\n * like `<strong><ins>X</ins></strong>` for fully-inserted formatted\n * content — the same shape the rest of htmldiff emits for content\n * insertions inside existing formatting.\n */\nfunction wrapInlineTextRuns(content: string, kind: 'ins' | 'del'): string {\n const tag = kind === 'ins' ? 'ins' : 'del'\n const cls = kind === 'ins' ? 'diffins' : 'diffdel'\n\n const out: string[] = []\n let i = 0\n while (i < content.length) {\n if (content[i] === '<') {\n const tagEnd = parseOpeningTagAt(content, i)\n if (!tagEnd) {\n // Malformed — pass the rest through verbatim.\n out.push(content.slice(i))\n break\n }\n out.push(content.slice(i, tagEnd.end))\n i = tagEnd.end\n continue\n }\n let j = i\n while (j < content.length && content[j] !== '<') j++\n const text = content.slice(i, j)\n if (text.trim().length > 0) {\n out.push(wrapText(text, tag, cls))\n } else {\n out.push(text)\n }\n i = j\n }\n return out.join('')\n}\n\nfunction emitDiffedCell(\n oldHtml: string,\n newHtml: string,\n oldCell: CellRange,\n newCell: CellRange,\n diffCell: DiffCellFn\n): string {\n const tdOpening = parseOpeningTagAt(newHtml, newCell.cellStart)\n if (!tdOpening) return newHtml.slice(newCell.cellStart, newCell.cellEnd)\n const tdOpenTag = newHtml.slice(newCell.cellStart, tdOpening.end)\n const content = diffCell(\n oldHtml.slice(oldCell.contentStart, oldCell.contentEnd),\n newHtml.slice(newCell.contentStart, newCell.contentEnd)\n )\n const closing = newHtml.slice(newCell.contentEnd, newCell.cellEnd)\n return tdOpenTag + content + closing\n}\n\nfunction rowHeaderSlice(html: string, row: RowRange): string {\n // Slice from <tr> to just before the first <td> opening tag. Preserves\n // the <tr ...> attributes plus any inter-tag whitespace. For a row with\n // no cells, we only want the `<tr ...>` opening — the caller appends the\n // closing `</tr>` explicitly, so taking the whole `<tr></tr>` here would\n // double the close.\n const opening = parseOpeningTagAt(html, row.rowStart)\n if (!opening) return ''\n if (row.cells.length === 0) return html.slice(row.rowStart, opening.end)\n return html.slice(row.rowStart, row.cells[0].cellStart)\n}\n\n/** Character-level similarity threshold above which we treat two rows as \"the same row, edited\". */\nconst ROW_FUZZY_THRESHOLD = 0.5\n\n/**\n * Threshold for \"this cell is a content-edit of that cell.\" Tuned the same\n * as ROW_FUZZY_THRESHOLD; cells in legal docs that share most of their\n * content typically ARE the same logical cell with a body edit, so 0.5\n * works for both granularities in practice.\n */\nconst CELL_FUZZY_THRESHOLD = 0.5\n\n/**\n * After exact LCS, scan the alignment for runs of \"old deleted, then new\n * inserted\" (or vice versa) and pair entries whose content is similar\n * enough to be treated as an edit rather than a delete+insert. This keeps\n * row-level edits (a typo fix, a single word change) from being shown as\n * an entire row vanishing and a new one appearing — matching what users\n * expect from a typical track-changes view.\n */\nfunction pairSimilarUnmatchedRows(\n alignment: Alignment[],\n oldTable: TableRange,\n newTable: TableRange,\n oldHtml: string,\n newHtml: string\n): Alignment[] {\n // Pre-compute row texts once; the similarity callback is invoked\n // O(D × I) times per unmatched run (every del × every ins), and\n // rowText walks every cell.\n const oldTexts = oldTable.rows.map(r => rowText(oldHtml, r))\n const newTexts = newTable.rows.map(r => rowText(newHtml, r))\n return pairSimilarUnmatched(alignment, ROW_FUZZY_THRESHOLD, (oldIdx, newIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\nfunction pairSimilarUnmatchedCells(\n alignment: Alignment[],\n oldRow: RowRange,\n newRow: RowRange,\n oldHtml: string,\n newHtml: string\n): Alignment[] {\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n return pairSimilarUnmatched(alignment, CELL_FUZZY_THRESHOLD, (oldIdx, newIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\nexport function rowText(html: string, row: RowRange): string {\n const parts: string[] = []\n for (const cell of row.cells) {\n parts.push(html.slice(cell.contentStart, cell.contentEnd).replace(/<[^>]+>/g, ' '))\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim().toLowerCase()\n}\n\nfunction cellText(html: string, cell: CellRange): string {\n return html\n .slice(cell.contentStart, cell.contentEnd)\n .replace(/<[^>]+>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .toLowerCase()\n}\n\n/**\n * Walks html and returns ranges for every top-level `<table>...</table>`\n * block. Nested tables aren't extracted as separate top-level entries —\n * they're captured inside the parent's content range and handled when the\n * cell-level diff recurses through them.\n */\nexport function findTopLevelTables(html: string): TableRange[] {\n const tables: TableRange[] = []\n let i = 0\n while (i < html.length) {\n if (matchesTagAt(html, i, 'table')) {\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const tableContentStart = opening.end\n const tableEnd = findMatchingClosingTag(html, tableContentStart, 'table')\n if (tableEnd === -1) {\n i = opening.end\n continue\n }\n const closingTagStart = tableEnd - '</table>'.length\n const rows = findTopLevelRows(html, tableContentStart, closingTagStart)\n tables.push({ tableStart: i, tableEnd, rows })\n i = tableEnd\n } else {\n i++\n }\n }\n return tables\n}\n\nfunction findTopLevelRows(html: string, start: number, end: number): RowRange[] {\n const rows: RowRange[] = []\n let i = start\n while (i < end) {\n if (matchesTagAt(html, i, 'tr')) {\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const rowContentStart = opening.end\n const rowEnd = findMatchingClosingTag(html, rowContentStart, 'tr', end)\n if (rowEnd === -1) {\n i = opening.end\n continue\n }\n const closingTagStart = rowEnd - '</tr>'.length\n const cells = findTopLevelCells(html, rowContentStart, closingTagStart)\n rows.push({ rowStart: i, rowEnd, cells })\n i = rowEnd\n } else if (matchesClosingTagAt(html, i, 'table')) {\n // Defensive: bail out if we encounter a closing </table> while\n // scanning rows (we should have stopped at `end` already).\n break\n } else {\n i++\n }\n }\n return rows\n}\n\nfunction findTopLevelCells(html: string, start: number, end: number): CellRange[] {\n const cells: CellRange[] = []\n let i = start\n while (i < end) {\n if (matchesTagAt(html, i, 'td') || matchesTagAt(html, i, 'th')) {\n const tagName = matchesTagAt(html, i, 'td') ? 'td' : 'th'\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const contentStart = opening.end\n const cellEnd = findMatchingClosingTag(html, contentStart, tagName, end)\n if (cellEnd === -1) {\n i = opening.end\n continue\n }\n const contentEnd = cellEnd - `</${tagName}>`.length\n cells.push({ cellStart: i, cellEnd, contentStart, contentEnd })\n i = cellEnd\n } else if (matchesClosingTagAt(html, i, 'tr')) {\n break\n } else {\n i++\n }\n }\n return cells\n}\n","import Action from './Action'\nimport type { AnalyzeResult } from './HtmlDiff'\nimport type Operation from './Operation'\nimport type { WrapMetadata } from './Utils'\n\n/**\n * Composes diff(genesis → cp-latest) (CP's accumulated changes from the\n * common ancestor) and diff(genesis → me-current) (Me's accumulated\n * changes from the common ancestor) into a single attributed segment\n * stream. The output is consumed by `HtmlDiff.executeThreeWay` for\n * emission.\n *\n * Genesis is the structural spine. Both pair-wise analyses must\n * tokenise genesis identically (`HtmlDiff.executeThreeWay` enforces\n * this via the symmetric-projection decision), so genesis-diff indices\n * are stable across the two streams.\n *\n * Per genesis token: classify by what each side did to it\n * (kept / deleted) and emit accordingly. Per genesis boundary: collect\n * each side's insertions and check for agreement — when both sides\n * inserted identical content, the insertion is treated as \"settled\"\n * and emitted unmarked (the reader sees the agreed-on text without\n * authorship markup, matching Word-style track-changes conventions\n * where both authors agreeing is silent).\n *\n * The emission order at a boundary mirrors the 2-way del-then-ins\n * convention: a Replace (genesis token deleted + a paired insertion)\n * reads as `<del>old</del><ins>new</ins>`. Pure insertions are\n * positioned at their natural boundary.\n */\n\nexport type Author = 'cp' | 'me'\n\n/**\n * Attribution assigned to each output segment.\n *\n * `equal` covers three cases: tokens both authors kept (rendered as the\n * genesis word), insertion spans both authors made identically (rendered\n * plain), and structural tags around both-deleted tokens (rendered to\n * keep layout intact while the content token itself is dropped).\n * Equal segments carry no markup.\n */\nexport type Attribution = { kind: 'equal' } | { kind: 'ins'; author: Author } | { kind: 'del'; author: Author }\n\nexport interface Segment {\n attr: Attribution\n /** Tokens to emit. For Equal segments these are original genesis words\n * (including structural tags); for ins/del they are diff-space tokens. */\n words: string[]\n}\n\n/**\n * Builds the attributed segment stream for a three-way diff.\n *\n * @param dCp analysis of diff(genesis → cp-latest)\n * @param dMe analysis of diff(genesis → me-current)\n *\n * Both analyses must share the same `oldDiffWords` (the genesis tokens)\n * — the caller guarantees this by passing the same genesis input and\n * the same `useProjections` decision to both `HtmlDiff.analyze` calls.\n */\nexport function buildSegments(dCp: AnalyzeResult, dMe: AnalyzeResult): Segment[] {\n const genesisLen = dCp.oldDiffWords.length\n\n // Per genesis token: did each author keep it or delete it?\n const cpFate = buildFateFromGenesis(dCp.operations, genesisLen)\n const meFate = buildFateFromGenesis(dMe.operations, genesisLen)\n\n // Per boundary: tokens each author inserted at that boundary. Keyed by\n // `endInOld` so a Replace's insertion sits AFTER the deleted genesis\n // token (visual del-then-ins). Pure Insert ops have endInOld ==\n // startInOld so they land at their natural between-tokens boundary.\n const cpInsAt = collectInsertionsKeyedByEnd(dCp)\n const meInsAt = collectInsertionsKeyedByEnd(dMe)\n\n // Inverse map genesis-diff-index → genesis-original-index. Identity when\n // no projection. Used to slice the original genesis words for Equal\n // segments so structural tags pass through verbatim.\n const diffToOriginal: readonly number[] = dCp.oldContentToOriginal ?? Array.from({ length: genesisLen }, (_, i) => i)\n const genesisOriginalLen = dCp.oldOriginalWords.length\n\n const segments: Segment[] = []\n let originalCursor = 0\n\n // Boundary 0 — pure insertions BEFORE genesis[0].\n emitBoundary(0, cpInsAt, meInsAt, dCp.newDiffWords, dMe.newDiffWords, segments)\n\n for (let i = 0; i < genesisLen; i++) {\n const cpDel = cpFate[i] === 'deleted'\n const meDel = meFate[i] === 'deleted'\n\n // Pick up structural tags from cursor through to this genesis token's\n // original index. Same cursor-based slicing as the 2-way path so a\n // `<p>` opening tag preceding a content token gets attributed with\n // that token's segment.\n const origIdx = diffToOriginal[i]\n const slice = dCp.oldOriginalWords.slice(originalCursor, origIdx + 1)\n originalCursor = origIdx + 1\n\n if (!cpDel && !meDel) {\n // Kept by both — equal. Emit the original-word slice (includes\n // any leading structural tags).\n appendSegment(segments, { kind: 'equal' }, slice)\n } else if (cpDel && meDel) {\n // Both deleted — settled. Filter at emission time; pass the\n // structural-tag-bearing slice through as equal so layout\n // survives. The content token itself is the LAST element of the\n // slice (since slice ends at origIdx+1); drop only that.\n // If slice has multiple elements (leading structural tags), they\n // belong to the surrounding flow and should remain.\n if (slice.length > 1) {\n appendSegment(segments, { kind: 'equal' }, slice.slice(0, slice.length - 1))\n }\n // The content token itself is silenced.\n } else if (cpDel) {\n // CP deleted, Me kept → render as <del cp>. Me's keeping means the\n // token is still in V_me; the markup tells the reader \"CP wanted\n // this gone, you've kept it.\"\n appendSegment(segments, { kind: 'del', author: 'cp' }, slice)\n } else {\n // Me deleted, CP kept → render as <del me>.\n appendSegment(segments, { kind: 'del', author: 'me' }, slice)\n }\n\n // Boundary i+1 — pure insertions between genesis[i] and genesis[i+1],\n // AND replace-insertions paired with genesis[i] (which we just\n // emitted as a deletion).\n emitBoundary(i + 1, cpInsAt, meInsAt, dCp.newDiffWords, dMe.newDiffWords, segments)\n }\n\n // Trailing original tokens (structural closing tags after the last\n // content word).\n if (originalCursor < genesisOriginalLen) {\n appendSegment(segments, { kind: 'equal' }, dCp.oldOriginalWords.slice(originalCursor))\n }\n\n return segments\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n\ntype GenesisFate = 'kept' | 'deleted'\n\n/**\n * Per genesis-diff-index, what did this side do to that token? Both\n * Delete and Replace ops remove the token from the side's output, so\n * both contribute `'deleted'`. Equal ops contribute `'kept'`. Insert\n * ops have an empty old range, so they don't touch the genesis fate\n * map.\n */\nfunction buildFateFromGenesis(ops: readonly Operation[], genesisLen: number): GenesisFate[] {\n const out: GenesisFate[] = new Array(genesisLen).fill('kept')\n for (const op of ops) {\n if (op.action !== Action.Delete && op.action !== Action.Replace) continue\n for (let i = op.startInOld; i < op.endInOld; i++) {\n if (i >= 0 && i < genesisLen) out[i] = 'deleted'\n }\n }\n return out\n}\n\n/**\n * Per genesis boundary `b`, collect tokens this side inserted at that\n * boundary. Keyed by `endInOld` so a Replace at genesis[k..k+1] has its\n * insertion at boundary k+1 (after the deleted token) rather than k\n * (before) — that produces the del-then-ins visual order.\n *\n * For pure Insert ops the old range is empty (endInOld == startInOld),\n * so the key is the same as the semantic between-tokens position.\n */\nfunction collectInsertionsKeyedByEnd(d: AnalyzeResult): Map<number, string[]> {\n const out = new Map<number, string[]>()\n for (const op of d.operations) {\n if (op.action !== Action.Insert && op.action !== Action.Replace) continue\n const words = d.newDiffWords.slice(op.startInNew, op.endInNew)\n if (words.length === 0) continue\n const key = op.endInOld\n const existing = out.get(key) ?? []\n existing.push(...words)\n out.set(key, existing)\n }\n return out\n}\n\n/**\n * Emit any insertions at boundary `b`. When both authors inserted at\n * the same boundary AND the inserted token sequences are textually\n * identical, the insertion is treated as agreed and emitted unmarked.\n * Otherwise each side's insertion is emitted with author attribution.\n *\n * The CP-then-Me ordering for disagreement is arbitrary but consistent;\n * callers don't depend on it.\n */\nfunction emitBoundary(\n b: number,\n cpInsAt: Map<number, string[]>,\n meInsAt: Map<number, string[]>,\n _cpDiffWords: readonly string[],\n _meDiffWords: readonly string[],\n segments: Segment[]\n) {\n const cpIns = cpInsAt.get(b)\n const meIns = meInsAt.get(b)\n const hasCp = !!cpIns && cpIns.length > 0\n const hasMe = !!meIns && meIns.length > 0\n if (!hasCp && !hasMe) return\n\n if (hasCp && hasMe && tokenArraysEqual(cpIns, meIns)) {\n // Both authors inserted the same content — settled. Emit unmarked.\n appendSegment(segments, { kind: 'equal' }, cpIns)\n return\n }\n\n if (hasCp) appendSegment(segments, { kind: 'ins', author: 'cp' }, cpIns)\n if (hasMe) appendSegment(segments, { kind: 'ins', author: 'me' }, meIns)\n}\n\nfunction tokenArraysEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false\n return true\n}\n\nfunction appendSegment(segments: Segment[], attr: Attribution, words: readonly string[]) {\n if (words.length === 0) return\n const last = segments[segments.length - 1]\n if (last && sameAttribution(last.attr, attr)) {\n last.words.push(...words)\n return\n }\n segments.push({ attr, words: [...words] })\n}\n\nfunction sameAttribution(a: Attribution, b: Attribution): boolean {\n if (a.kind === 'equal' && b.kind === 'equal') return true\n if (a.kind === 'ins' && b.kind === 'ins') return a.author === b.author\n if (a.kind === 'del' && b.kind === 'del') return a.author === b.author\n return false\n}\n\n/**\n * Build the `WrapMetadata` for an attribution. Single source of truth\n * for author-class / data-attr shape so the three emission paths\n * (word-level, table-level full-row/cell, multi-table whole-table\n * pre-wrap) stay consistent. A change here propagates to every author\n * marker in the output.\n */\nexport function authorAttribution(author: Author): WrapMetadata {\n return { extraClasses: author, dataAttrs: { author } }\n}\n\n/**\n * Resolve a segment's attribution into the wrapper-tag, base CSS class,\n * and `WrapMetadata` consumed by `Utils.wrapText` / `insertTag`. The\n * caller is `HtmlDiff.executeThreeWay`'s emission loop.\n *\n * `equal` segments don't go through this — they're emitted unmarked.\n */\nexport function segmentEmissionShape(attr: Exclude<Attribution, { kind: 'equal' }>): {\n tag: 'ins' | 'del'\n baseClass: 'diffins' | 'diffdel'\n metadata: WrapMetadata\n} {\n return {\n tag: attr.kind,\n baseClass: attr.kind === 'ins' ? 'diffins' : 'diffdel',\n metadata: authorAttribution(attr.author),\n }\n}\n","import { lcsAlign, textSimilarity } from './Alignment'\nimport { injectClass, parseOpeningTagAt } from './HtmlScanner'\nimport {\n type CellRange,\n exceedsSizeLimit,\n findTopLevelTables,\n makePlaceholderPrefix,\n PLACEHOLDER_SUFFIX,\n type RowRange,\n rowKey,\n sameDimensions,\n spliceString,\n type TableRange,\n} from './TableDiff'\nimport { type Author, authorAttribution } from './ThreeWayDiff'\nimport Utils from './Utils'\n\n/**\n * Three-way table preprocessing for the genesis-spine merge.\n *\n * Inputs: `genesis` (common ancestor), `cpLatest` (counterparty's\n * accumulated position), `meCurrent` (Me's accumulated position). All\n * three share a single placeholder nonce so genesis tokenises\n * identically across both pair-wise word-level analyses.\n *\n * Three paths:\n * 1. **Positional** — all three have the same table count AND each\n * positional triple's tableKey is similar enough that 1:1 pairing\n * by position is sound. Recurses cellDiff per cell, structural\n * layout from genesis.\n * 2. **Row-structural** — paired triples whose row/cell counts differ.\n * Per-table row-level LCS against genesis; recurse on preserved\n * rows, emit author-attributed full rows for the rest.\n * 3. **Multi-table by content** — table counts diverge across inputs.\n * Pair tables to genesis via content-LCS, then assign placeholders\n * such that each placeholder appears in exactly the inputs that\n * contain the underlying table. The word-level merger walks the\n * genesis spine and attributes unpaired tables naturally\n * (cp-only/me-only/both-agree).\n */\n\nexport interface ThreeWayPreprocessResult {\n modifiedGenesis: string\n modifiedCp: string\n modifiedMe: string\n placeholderToDiff: Map<string, string>\n}\n\nexport type ThreeWayDiffCellFn = (genesisCell: string, cpCell: string, meCell: string) => string\n\nexport function preprocessTablesThreeWay(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n cellDiff: ThreeWayDiffCellFn\n): ThreeWayPreprocessResult | null {\n const gTables = findTopLevelTables(genesis)\n const cTables = findTopLevelTables(cpLatest)\n const mTables = findTopLevelTables(meCurrent)\n\n if (gTables.length === 0 && cTables.length === 0 && mTables.length === 0) return null\n\n for (const t of gTables) if (exceedsSizeLimit(t)) return null\n for (const t of cTables) if (exceedsSizeLimit(t)) return null\n for (const t of mTables) if (exceedsSizeLimit(t)) return null\n\n const placeholderPrefix = makePlaceholderPrefix(genesis, cpLatest, meCurrent)\n\n if (positionallyAligned(genesis, cpLatest, meCurrent, gTables, cTables, mTables)) {\n return preprocessAlignedByPosition(\n genesis,\n cpLatest,\n meCurrent,\n gTables,\n cTables,\n mTables,\n cellDiff,\n placeholderPrefix\n )\n }\n\n return preprocessByContent(genesis, cpLatest, meCurrent, gTables, cTables, mTables, cellDiff, placeholderPrefix)\n}\n\nfunction preprocessAlignedByPosition(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n gTables: TableRange[],\n cTables: TableRange[],\n mTables: TableRange[],\n cellDiff: ThreeWayDiffCellFn,\n placeholderPrefix: string\n): ThreeWayPreprocessResult {\n const pairs: Array<{ g: TableRange; c: TableRange; m: TableRange; diffed: string }> = []\n for (let i = 0; i < gTables.length; i++) {\n pairs.push({\n g: gTables[i],\n c: cTables[i],\n m: mTables[i],\n diffed: diffTableThreeWay(genesis, cpLatest, meCurrent, gTables[i], cTables[i], mTables[i], cellDiff),\n })\n }\n let modifiedGenesis = genesis\n let modifiedCp = cpLatest\n let modifiedMe = meCurrent\n const placeholderToDiff = new Map<string, string>()\n for (let i = pairs.length - 1; i >= 0; i--) {\n const placeholder = `${placeholderPrefix}${i}${PLACEHOLDER_SUFFIX}`\n placeholderToDiff.set(placeholder, pairs[i].diffed)\n modifiedGenesis = spliceString(modifiedGenesis, pairs[i].g.tableStart, pairs[i].g.tableEnd, placeholder)\n modifiedCp = spliceString(modifiedCp, pairs[i].c.tableStart, pairs[i].c.tableEnd, placeholder)\n modifiedMe = spliceString(modifiedMe, pairs[i].m.tableStart, pairs[i].m.tableEnd, placeholder)\n }\n return { modifiedGenesis, modifiedCp, modifiedMe, placeholderToDiff }\n}\n\n/**\n * Multi-table handler. Tables are paired against `genesis` (the spine)\n * via content-LCS on each of cp and me. Placeholders are assigned so\n * each appears only in the inputs that actually contain the underlying\n * table. The word-level merger then attributes them naturally:\n *\n * - paired in genesis+cp+me → equal in both diffs → emit recursive 3-way diff\n * - in cp+me, not in genesis → both-agree insertion → emit plain\n * - in cp only → cp insertion → ins-cp wrapper (Me didn't take it)\n * - in me only → me insertion → ins-me wrapper\n * - in genesis+cp, not me → me deletion → del-me wrapper\n * - in genesis+me, not cp → cp deletion → del-cp wrapper\n * - in genesis only → both deleted, settled → silent (placeholder content empty)\n */\nfunction preprocessByContent(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n gTables: TableRange[],\n cTables: TableRange[],\n mTables: TableRange[],\n cellDiff: ThreeWayDiffCellFn,\n placeholderPrefix: string\n): ThreeWayPreprocessResult {\n const gKeys = gTables.map(t => tableKey(genesis, t))\n const cKeys = cTables.map(t => tableKey(cpLatest, t))\n const mKeys = mTables.map(t => tableKey(meCurrent, t))\n\n const alignCp = lcsAlign(gKeys, cKeys)\n const alignMe = lcsAlign(gKeys, mKeys)\n\n // Maps: genesisIdx → matching cpIdx (-1 if none); cpIdx → matching genesisIdx; etc.\n const gToCp = new Array<number>(gTables.length).fill(-1)\n const cpToG = new Array<number>(cTables.length).fill(-1)\n for (const a of alignCp) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n gToCp[a.oldIdx] = a.newIdx\n cpToG[a.newIdx] = a.oldIdx\n }\n }\n const gToMe = new Array<number>(gTables.length).fill(-1)\n const meToG = new Array<number>(mTables.length).fill(-1)\n for (const a of alignMe) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n gToMe[a.oldIdx] = a.newIdx\n meToG[a.newIdx] = a.oldIdx\n }\n }\n\n let nextId = 0\n const placeholderToDiff = new Map<string, string>()\n const placeholders = {\n g: new Array<string | null>(gTables.length).fill(null),\n c: new Array<string | null>(cTables.length).fill(null),\n m: new Array<string | null>(mTables.length).fill(null),\n }\n const allocate = (): string => `${placeholderPrefix}${nextId++}${PLACEHOLDER_SUFFIX}`\n\n // For unpaired-in-one-side placeholders, bake author attribution\n // into the placeholder content — the word-level merger emits tag\n // tokens (HTML comments) verbatim, so it can't wrap them itself.\n const wrapWhole = (tag: 'ins' | 'del', author: Author, tableHtml: string): string =>\n Utils.wrapText(tableHtml, tag, `diff${tag}`, authorAttribution(author))\n\n // 1. Triples paired in all three (genesis + cp + me) → recursive 3-way diff.\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n const cIdx = gToCp[gIdx]\n const mIdx = gToMe[gIdx]\n if (cIdx === -1 || mIdx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n diffTableThreeWay(genesis, cpLatest, meCurrent, gTables[gIdx], cTables[cIdx], mTables[mIdx], cellDiff)\n )\n placeholders.g[gIdx] = placeholder\n placeholders.c[cIdx] = placeholder\n placeholders.m[mIdx] = placeholder\n }\n\n // 2. Genesis + CP only (not in Me) → me deletion.\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n if (placeholders.g[gIdx] !== null) continue\n const cIdx = gToCp[gIdx]\n if (cIdx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('del', 'me', genesis.slice(gTables[gIdx].tableStart, gTables[gIdx].tableEnd))\n )\n placeholders.g[gIdx] = placeholder\n placeholders.c[cIdx] = placeholder\n }\n\n // 3. Genesis + Me only (not in CP) → cp deletion.\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n if (placeholders.g[gIdx] !== null) continue\n const mIdx = gToMe[gIdx]\n if (mIdx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('del', 'cp', genesis.slice(gTables[gIdx].tableStart, gTables[gIdx].tableEnd))\n )\n placeholders.g[gIdx] = placeholder\n placeholders.m[mIdx] = placeholder\n }\n\n // 4. Genesis only (not in CP, not in Me) → both deleted, settled, silent.\n // Placeholder ONLY in genesis; cp and me lack it. The word-level merger\n // sees it as \"deleted by both\" via the genesis-spine fate maps and\n // silences it via the settled-deletion rule (empty placeholder content).\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n if (placeholders.g[gIdx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, '')\n placeholders.g[gIdx] = placeholder\n }\n\n // 5. CP + Me both inserted (no genesis) — agreement check. If their\n // table content is textually identical, emit plain (settled). Otherwise\n // each side gets its own placeholder (cp-only / me-only treatment).\n for (let cIdx = 0; cIdx < cTables.length; cIdx++) {\n if (placeholders.c[cIdx] !== null) continue\n // CP table not paired to genesis. Is there an unpaired Me table with\n // matching content?\n const cText = cKeys[cIdx]\n let mIdx = -1\n for (let candidate = 0; candidate < mTables.length; candidate++) {\n if (placeholders.m[candidate] !== null) continue\n if (meToG[candidate] !== -1) continue\n if (mKeys[candidate] === cText) {\n mIdx = candidate\n break\n }\n }\n if (mIdx === -1) continue\n // Both inserted the same table content → settled insertion.\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, cpLatest.slice(cTables[cIdx].tableStart, cTables[cIdx].tableEnd))\n placeholders.c[cIdx] = placeholder\n placeholders.m[mIdx] = placeholder\n }\n\n // 6. Remaining CP-only tables (inserted by CP, Me didn't take).\n for (let cIdx = 0; cIdx < cTables.length; cIdx++) {\n if (placeholders.c[cIdx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('ins', 'cp', cpLatest.slice(cTables[cIdx].tableStart, cTables[cIdx].tableEnd))\n )\n placeholders.c[cIdx] = placeholder\n }\n\n // 7. Remaining Me-only tables (Me inserted, CP didn't).\n for (let mIdx = 0; mIdx < mTables.length; mIdx++) {\n if (placeholders.m[mIdx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('ins', 'me', meCurrent.slice(mTables[mIdx].tableStart, mTables[mIdx].tableEnd))\n )\n placeholders.m[mIdx] = placeholder\n }\n\n // Splice end → start per input.\n let modifiedGenesis = genesis\n for (let i = gTables.length - 1; i >= 0; i--) {\n const p = placeholders.g[i]\n if (p === null) continue\n modifiedGenesis = spliceString(modifiedGenesis, gTables[i].tableStart, gTables[i].tableEnd, p)\n }\n let modifiedCp = cpLatest\n for (let i = cTables.length - 1; i >= 0; i--) {\n const p = placeholders.c[i]\n if (p === null) continue\n modifiedCp = spliceString(modifiedCp, cTables[i].tableStart, cTables[i].tableEnd, p)\n }\n let modifiedMe = meCurrent\n for (let i = mTables.length - 1; i >= 0; i--) {\n const p = placeholders.m[i]\n if (p === null) continue\n modifiedMe = spliceString(modifiedMe, mTables[i].tableStart, mTables[i].tableEnd, p)\n }\n\n return { modifiedGenesis, modifiedCp, modifiedMe, placeholderToDiff }\n}\n\nconst POSITIONAL_PAIR_SIMILARITY_THRESHOLD = 0.5\n\nfunction positionallyAligned(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n gTables: TableRange[],\n cTables: TableRange[],\n mTables: TableRange[]\n): boolean {\n if (gTables.length !== cTables.length || cTables.length !== mTables.length) return false\n for (let i = 0; i < gTables.length; i++) {\n const kG = tableKey(genesis, gTables[i])\n const kC = tableKey(cpLatest, cTables[i])\n const kM = tableKey(meCurrent, mTables[i])\n if (textSimilarity(kG, kC) < POSITIONAL_PAIR_SIMILARITY_THRESHOLD) return false\n if (textSimilarity(kG, kM) < POSITIONAL_PAIR_SIMILARITY_THRESHOLD) return false\n }\n return true\n}\n\nfunction tableKey(html: string, table: TableRange): string {\n return html.slice(table.tableStart, table.tableEnd).replace(/\\s+/g, ' ').trim()\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Per-table diff: positional cells or row-level structural change.\n\nfunction diffTableThreeWay(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n tG: TableRange,\n tC: TableRange,\n tM: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n if (sameDimensions(tG, tC) && sameDimensions(tC, tM)) {\n return diffTablePositional(genesis, cpLatest, meCurrent, tG, tC, tM, cellDiff)\n }\n return diffTableStructural(genesis, cpLatest, meCurrent, tG, tC, tM, cellDiff)\n}\n\nfunction diffTablePositional(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n tG: TableRange,\n tC: TableRange,\n tM: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n // Walk genesis's table scaffolding verbatim — it's the common\n // ancestor. Cells are merged 3-way via cellDiff. Choosing genesis as\n // the spine keeps the table structure stable across both pair-wise\n // diffs that the word-level merger will see.\n const out: string[] = []\n let cursor = tG.tableStart\n for (let r = 0; r < tG.rows.length; r++) {\n const rG = tG.rows[r]\n const rC = tC.rows[r]\n const rM = tM.rows[r]\n for (let c = 0; c < rG.cells.length; c++) {\n const cG = rG.cells[c]\n const cC = rC.cells[c]\n const cM = rM.cells[c]\n out.push(genesis.slice(cursor, cG.contentStart))\n out.push(\n cellDiff(\n genesis.slice(cG.contentStart, cG.contentEnd),\n cpLatest.slice(cC.contentStart, cC.contentEnd),\n meCurrent.slice(cM.contentStart, cM.contentEnd)\n )\n )\n cursor = cG.contentEnd\n }\n }\n out.push(genesis.slice(cursor, tG.tableEnd))\n return out.join('')\n}\n\n/**\n * Row-level genesis-spine merge for tables with diverging row/cell\n * counts.\n *\n * 1. Align cp rows to genesis rows (alignCp), me rows to genesis rows\n * (alignMe), each via row-LCS over rowKeys.\n * 2. Per genesis row: cpFate (kept / deleted), meFate (kept / deleted).\n * Both kept → recurse cell diff (with structural-change cell handling\n * falling back to me-attribution Replace per the documented\n * limitation). One kept, other deleted → emit author-attributed full\n * row. Both deleted → silent.\n * 3. Off-spine rows: cp-only inserted rows + me-only inserted rows.\n * Check for content agreement at the same boundary; agreed\n * insertions emit plain.\n */\nfunction diffTableStructural(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n tG: TableRange,\n tC: TableRange,\n tM: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n const gKeys = tG.rows.map(r => rowKey(genesis, r))\n const cKeys = tC.rows.map(r => rowKey(cpLatest, r))\n const mKeys = tM.rows.map(r => rowKey(meCurrent, r))\n\n const alignCp = lcsAlign(gKeys, cKeys)\n const alignMe = lcsAlign(gKeys, mKeys)\n\n // genesisIdx → matching cpIdx (-1 if cp deleted this row)\n const gToCp = new Array<number>(tG.rows.length).fill(-1)\n for (const a of alignCp) {\n if (a.oldIdx !== null && a.newIdx !== null) gToCp[a.oldIdx] = a.newIdx\n }\n const gToMe = new Array<number>(tG.rows.length).fill(-1)\n for (const a of alignMe) {\n if (a.oldIdx !== null && a.newIdx !== null) gToMe[a.oldIdx] = a.newIdx\n }\n\n // Off-spine row collections: cp rows with no genesis counterpart, me rows with no genesis counterpart.\n // Keyed by \"the genesis row index they should appear before\" so emission interleaves correctly.\n const cpInsAt = collectInsertedRowsAtBoundary(alignCp, tG.rows.length)\n const meInsAt = collectInsertedRowsAtBoundary(alignMe, tG.rows.length)\n\n const out: string[] = []\n out.push(tableHeaderSlice(genesis, tG))\n\n const emitBoundaryInsertions = (b: number) => {\n const cIdxs = cpInsAt.get(b) ?? []\n const mIdxs = meInsAt.get(b) ?? []\n if (cIdxs.length === 0 && mIdxs.length === 0) return\n // Detect settled insertions (cp and me both inserted the same row content).\n // Pair by content key, in order of appearance.\n const remainingMe = new Set(mIdxs)\n for (const cIdx of cIdxs) {\n const cText = cKeys[cIdx]\n let agreedMeIdx: number | undefined\n for (const mIdx of remainingMe) {\n if (mKeys[mIdx] === cText) {\n agreedMeIdx = mIdx\n break\n }\n }\n if (agreedMeIdx !== undefined) {\n remainingMe.delete(agreedMeIdx)\n // Settled insertion — emit cp's row verbatim, unmarked.\n out.push(cpLatest.slice(tC.rows[cIdx].rowStart, tC.rows[cIdx].rowEnd))\n } else {\n out.push(emitFullRowAttributed(cpLatest, tC.rows[cIdx], 'ins', 'cp'))\n }\n }\n for (const mIdx of remainingMe) {\n out.push(emitFullRowAttributed(meCurrent, tM.rows[mIdx], 'ins', 'me'))\n }\n }\n\n for (let g = 0; g < tG.rows.length; g++) {\n emitBoundaryInsertions(g)\n\n const cIdx = gToCp[g]\n const mIdx = gToMe[g]\n const cpDel = cIdx === -1\n const meDel = mIdx === -1\n\n if (!cpDel && !meDel) {\n // Both kept — recurse cell-level diff against this row triple.\n out.push(emitPreservedRow(genesis, cpLatest, meCurrent, tG.rows[g], tC.rows[cIdx], tM.rows[mIdx], cellDiff))\n } else if (cpDel && meDel) {\n // Both deleted — silent (settled).\n } else if (cpDel) {\n // CP dropped, Me kept → emit Me's row attributed as cp-deletion. The\n // content shown is what Me has; the styling tells the reader CP\n // wanted it gone.\n out.push(emitFullRowAttributed(meCurrent, tM.rows[mIdx], 'del', 'cp'))\n } else {\n // Me dropped, CP kept → emit CP's row attributed as me-deletion.\n out.push(emitFullRowAttributed(cpLatest, tC.rows[cIdx], 'del', 'me'))\n }\n }\n emitBoundaryInsertions(tG.rows.length)\n out.push(tableFooterSlice(genesis, tG))\n return out.join('')\n}\n\nfunction emitPreservedRow(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n rG: RowRange,\n rC: RowRange,\n rM: RowRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n if (rG.cells.length === rC.cells.length && rC.cells.length === rM.cells.length) {\n // Same cell counts — positional cell diff.\n const out: string[] = []\n let cursor = rG.rowStart\n for (let c = 0; c < rG.cells.length; c++) {\n const cG = rG.cells[c]\n const cC = rC.cells[c]\n const cM = rM.cells[c]\n out.push(genesis.slice(cursor, cG.contentStart))\n out.push(\n cellDiff(\n genesis.slice(cG.contentStart, cG.contentEnd),\n cpLatest.slice(cC.contentStart, cC.contentEnd),\n meCurrent.slice(cM.contentStart, cM.contentEnd)\n )\n )\n cursor = cG.contentEnd\n }\n out.push(genesis.slice(cursor, rG.rowEnd))\n return out.join('')\n }\n // Cell-count mismatch within a preserved row — cell-level structural\n // change deferred. Fall back to me-attributed Replace (genesis row\n // removed, me row inserted). Lossy for CP within that row.\n return emitFullRowAttributed(genesis, rG, 'del', 'me') + emitFullRowAttributed(meCurrent, rM, 'ins', 'me')\n}\n\n/**\n * Returns map \"genesis-row-boundary → list of new-side row indices\n * inserted at that boundary\". Mirrors the word-level boundary collection\n * but at the row scale.\n */\nfunction collectInsertedRowsAtBoundary(\n align: ReturnType<typeof lcsAlign>,\n genesisRowCount: number\n): Map<number, number[]> {\n const out = new Map<number, number[]>()\n let nextGenesisBoundary = genesisRowCount\n const pending: number[] = []\n // Walk in reverse so nextGenesisBoundary tracks the next preserved row\n // we'll encounter; flush pending unpaired new rows at the appropriate\n // genesis boundary.\n for (let i = align.length - 1; i >= 0; i--) {\n const a = align[i]\n if (a.oldIdx !== null) {\n if (pending.length > 0) {\n const existing = out.get(nextGenesisBoundary) ?? []\n existing.unshift(...pending.toReversed())\n out.set(nextGenesisBoundary, existing)\n pending.length = 0\n }\n nextGenesisBoundary = a.oldIdx\n } else if (a.newIdx !== null) {\n pending.push(a.newIdx)\n }\n }\n if (pending.length > 0) {\n const existing = out.get(nextGenesisBoundary) ?? []\n existing.unshift(...pending.toReversed())\n out.set(nextGenesisBoundary, existing)\n }\n return out\n}\n\nfunction tableHeaderSlice(html: string, table: TableRange): string {\n const firstRow = table.rows[0]\n if (!firstRow) return html.slice(table.tableStart, table.tableEnd - '</table>'.length)\n return html.slice(table.tableStart, firstRow.rowStart)\n}\n\nfunction tableFooterSlice(html: string, table: TableRange): string {\n const lastRow = table.rows[table.rows.length - 1]\n if (!lastRow) return '</table>'\n return html.slice(lastRow.rowEnd, table.tableEnd)\n}\n\n/**\n * Emit a row fully attributed to one author. Wraps `<tr>` and each\n * `<td>` with the author's diffins/diffdel class and `data-author`\n * attribute; wraps cell content with an inner `<ins>`/`<del>` matching\n * the word-level emission shape.\n */\nfunction emitFullRowAttributed(html: string, row: RowRange, kind: 'ins' | 'del', author: Author): string {\n const trOpening = parseOpeningTagAt(html, row.rowStart)\n if (!trOpening) return html.slice(row.rowStart, row.rowEnd)\n const trWithAttrs = injectAuthorAttribution(html.slice(row.rowStart, trOpening.end), kind, author)\n\n const out: string[] = [trWithAttrs]\n let cursor = trOpening.end\n for (const cell of row.cells) {\n out.push(html.slice(cursor, cell.cellStart))\n out.push(emitFullCellAttributed(html, cell, kind, author))\n cursor = cell.cellEnd\n }\n out.push(html.slice(cursor, row.rowEnd))\n return out.join('')\n}\n\nfunction emitFullCellAttributed(html: string, cell: CellRange, kind: 'ins' | 'del', author: Author): string {\n const tdOpening = parseOpeningTagAt(html, cell.cellStart)\n if (!tdOpening) return html.slice(cell.cellStart, cell.cellEnd)\n const tdWithAttrs = injectAuthorAttribution(html.slice(cell.cellStart, tdOpening.end), kind, author)\n const innerContent = html.slice(cell.contentStart, cell.contentEnd)\n const innerWrapped =\n innerContent.trim().length === 0\n ? innerContent\n : Utils.wrapText(innerContent, kind, `diff${kind}`, authorAttribution(author))\n const closing = html.slice(cell.contentEnd, cell.cellEnd)\n return tdWithAttrs + innerWrapped + closing\n}\n\nfunction injectAuthorAttribution(openingTag: string, kind: 'ins' | 'del', author: Author): string {\n const meta = authorAttribution(author)\n const tagWithClass = injectClass(openingTag, `diff${kind} ${meta.extraClasses}`)\n return injectDataAttrs(tagWithClass, meta.dataAttrs ?? {})\n}\n\nfunction injectDataAttrs(openingTag: string, dataAttrs: Readonly<Record<string, string>>): string {\n const keys = Object.keys(dataAttrs)\n if (keys.length === 0) return openingTag\n const attrs = keys.map(k => ` data-${k}='${dataAttrs[k]}'`).join('')\n if (openingTag.endsWith('/>')) return `${openingTag.slice(0, -2)}${attrs}/>`\n return `${openingTag.slice(0, -1)}${attrs}>`\n}\n","import Mode from './Mode'\nimport Utils from './Utils'\n\nexport default class WordSplitter {\n private text: string\n private isBlockCheckRequired: boolean\n private blockLocations: BlockFinderResult\n private mode: Mode\n private isGrouping = false\n private globbingUntil: number\n private currentWord: string[]\n private words: string[]\n private static NotGlobbing = -1\n\n private get currentWordHasChars() {\n return this.currentWord.length > 0\n }\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockLocations = new BlockFinder(text, blockExpressions).findBlocks()\n this.isBlockCheckRequired = this.blockLocations.hasBlocks\n this.mode = Mode.Character\n this.globbingUntil = WordSplitter.NotGlobbing\n this.currentWord = []\n this.words = []\n }\n\n process(): string[] {\n for (let index = 0; index < this.text.length; index++) {\n const character = this.text.charAt(index)\n this.processCharacter(index, character)\n }\n\n this.appendCurrentWordToWords()\n return this.words\n }\n\n private processCharacter(index: number, character: string) {\n if (this.isGlobbing(index, character)) {\n return\n }\n\n switch (this.mode) {\n case Mode.Character:\n this.processTextCharacter(character)\n break\n case Mode.Tag:\n this.processHtmlTagContinuation(character)\n break\n case Mode.Whitespace:\n this.processWhiteSpaceContinuation(character)\n break\n case Mode.Entity:\n this.processEntityContinuation(character)\n break\n }\n }\n\n private processEntityContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (character.trim().length === 0) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (Utils.isEndOfEntity(character)) {\n let switchToNextMode = true\n if (this.currentWordHasChars) {\n this.currentWord.push(character)\n this.words.push(this.currentWord.join(''))\n\n //join &nbsp; entity with last whitespace\n if (\n this.words.length > 2 &&\n Utils.isWhiteSpace(this.words[this.words.length - 2]) &&\n Utils.isWhiteSpace(this.words[this.words.length - 1])\n ) {\n const w1 = this.words[this.words.length - 2]\n const w2 = this.words[this.words.length - 1]\n this.words.splice(this.words.length - 2, 2)\n this.currentWord = `${w1}${w2}`.split('')\n this.mode = Mode.Whitespace\n switchToNextMode = false\n }\n }\n\n if (switchToNextMode) {\n this.currentWord = []\n this.mode = Mode.Character\n }\n } else if (Utils.isWord(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processWhiteSpaceContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processHtmlTagContinuation(character: string) {\n if (Utils.isEndOfTag(character)) {\n this.currentWord.push(character)\n this.appendCurrentWordToWords()\n this.mode = Utils.isWhiteSpace(character) ? Mode.Whitespace : Mode.Character\n } else {\n this.currentWord.push(character)\n }\n }\n\n private processTextCharacter(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push('<')\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (\n Utils.isWord(character) &&\n (this.currentWord.length === 0 || Utils.isWord(this.currentWord[this.currentWord.length - 1]))\n ) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n }\n }\n\n private appendCurrentWordToWords() {\n if (this.currentWordHasChars) {\n this.words.push(this.currentWord.join(''))\n this.currentWord = []\n }\n }\n\n private isGlobbing(index: number, character: string): boolean {\n if (!this.isBlockCheckRequired) {\n return false\n }\n const isCurrentBlockTerminating = index === this.globbingUntil\n if (isCurrentBlockTerminating) {\n this.globbingUntil = WordSplitter.NotGlobbing\n this.isGrouping = false\n this.appendCurrentWordToWords()\n }\n\n const until = this.blockLocations.isInBlock(index)\n if (until) {\n this.isGrouping = true\n this.globbingUntil = until\n }\n if (this.isGrouping) {\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n return this.isGrouping\n }\n\n static convertHtmlToListOfWords(text: string, blockExpressions: RegExp[]): string[] {\n return new WordSplitter(text, blockExpressions).process()\n }\n}\n\nclass BlockFinderResult {\n private blocks: Map<number, number> = new Map()\n\n addBlock(from: number, to: number) {\n if (this.blocks.has(from)) {\n throw new ArgumentError('One or more block expressions result in a text sequence that overlaps.')\n }\n\n this.blocks.set(from, to)\n }\n\n isInBlock(location: number): number | null {\n return this.blocks.get(location) ?? null\n }\n\n get hasBlocks() {\n return this.blocks.size > 0\n }\n}\n\nclass ArgumentError extends Error {}\n\nclass BlockFinder {\n private text: string\n private blockExpressions: RegExp[]\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockExpressions = blockExpressions\n }\n\n findBlocks(): BlockFinderResult {\n const result = new BlockFinderResult()\n for (const expression of this.blockExpressions) {\n this.processBlockMatcher(expression, result)\n }\n return result\n }\n\n private processBlockMatcher(exp: RegExp, result: BlockFinderResult) {\n let match: RegExpExecArray | null\n // biome-ignore lint/suspicious/noAssignInExpressions: Couldn't think of a nicer way to do this\n while ((match = exp.exec(this.text)) !== null) {\n this.tryAddBlock(exp, match, result)\n }\n }\n\n private tryAddBlock(exp: RegExp, match: RegExpExecArray, result: BlockFinderResult) {\n try {\n const from = match.index\n const to = match.index + match[0].length\n result.addBlock(from, to)\n } catch {\n throw new ArgumentError(\n `One or more block expressions result in a text sequence that overlaps. Current expression: ${exp}`\n )\n }\n }\n}\n","import Action from './Action'\nimport Match from './Match'\nimport MatchFinder from './MatchFinder'\nimport Operation from './Operation'\nimport { preprocessTables, restoreTablePlaceholders } from './TableDiff'\nimport { buildSegments, type Segment, segmentEmissionShape } from './ThreeWayDiff'\nimport { preprocessTablesThreeWay } from './ThreeWayTable'\nimport Utils, { type WrapMetadata } from './Utils'\nimport WordSplitter from './WordSplitter'\n\n/**\n * State threaded into the recursive cell-level diff inside\n * `preprocessTables`. Bundles the nesting depth (for the recursion-cap\n * guard) with the caller-configurable settings that must propagate\n * unchanged to inner instances so cell-level output stays consistent\n * with the top-level call. Internal — never crosses the public API.\n */\ninterface RecursionContext {\n depth: number\n blockExpressions: readonly RegExp[]\n repeatingWordsAccuracy: number\n orphanMatchThreshold: number\n ignoreWhitespaceDifferences: boolean\n}\n\n/**\n * Options for the `HtmlDiff.analyze` static helper.\n *\n * `useProjections` controls structural-tag normalisation:\n * - `undefined` → use the same heuristic as `build()` (per-call decision)\n * - `true` → force projection on (skipped if either side has no content)\n * - `false` → force projection off (diff runs on raw word arrays)\n * Composers of multiple analyses (e.g. three-way diff) MUST pass the\n * same explicit boolean to all calls so shared inputs tokenise\n * identically across analyses.\n *\n * The remaining options mirror the per-instance fields on `HtmlDiff`\n * itself — they exist on the options bag because `analyze` constructs\n * the inner instance internally.\n */\nexport interface AnalyzeOptions {\n useProjections?: boolean\n blockExpressions?: readonly RegExp[]\n repeatingWordsAccuracy?: number\n orphanMatchThreshold?: number\n ignoreWhitespaceDifferences?: boolean\n}\n\nexport interface AnalyzeResult {\n /** Word array the `operations` index into (projected or raw). */\n readonly oldDiffWords: readonly string[]\n readonly newDiffWords: readonly string[]\n readonly operations: readonly Operation[]\n /** Original WordSplitter output, before any projection. */\n readonly oldOriginalWords: readonly string[]\n readonly newOriginalWords: readonly string[]\n /** Diff-index → original-word-index map; null when projections inactive. */\n readonly oldContentToOriginal: readonly number[] | null\n readonly newContentToOriginal: readonly number[] | null\n}\n\n/**\n * Options for `HtmlDiff.executeThreeWay`. Same shape as `AnalyzeOptions`\n * — the values flow unchanged into both internal `analyze` calls so V2's\n * tokenisation stays symmetric. Aliased so future divergence in either\n * direction lives in one place.\n *\n * `useProjections`: when undefined, `executeThreeWay` computes the\n * decision as the conjunction of both pair-wise\n * `evaluateProjectionApplicability` results.\n */\nexport type ThreeWayOptions = AnalyzeOptions\n\nexport default class HtmlDiff {\n /**\n * This value defines balance between speed and memory utilization. The higher it is the faster it works and more memory consumes.\n * @private\n */\n private static MatchGranularityMaximum = 4\n\n private static DelTag = 'del'\n private static InsTag = 'ins'\n\n // ignore case\n private static SpecialCaseClosingTags = [\n '</strong>',\n '</em>',\n '</b>',\n '</i>',\n '</big>',\n '</small>',\n '</u>',\n '</sub>',\n '</sup>',\n '</strike>',\n '</s>',\n '</span>',\n ]\n\n private static SpecialCaseClosingTagsSet = new Set([\n '</strong>',\n '</em>',\n '</b>',\n '</i>',\n '</big>',\n '</small>',\n '</u>',\n '</sub>',\n '</sup>',\n '</strike>',\n '</s>',\n '</span>',\n ])\n\n private static SpecialCaseOpeningTagRegex =\n /<((strong)|(b)|(i)|(em)|(big)|(small)|(u)|(sub)|(sup)|(strike)|(s)|(span))[>\\s]+/i\n\n private static FormattingTags = new Set([\n 'strong',\n 'em',\n 'b',\n 'i',\n 'big',\n 'small',\n 'u',\n 'sub',\n 'sup',\n 'strike',\n 's',\n 'span',\n ])\n\n /**\n * Hard cap on nested `HtmlDiff.execute` calls (table preprocessing\n * recurses through `diffCell` for cell content). Each level allocates\n * fresh DP matrices and word arrays; without a guard a maliciously\n * nested table-in-cell-in-table-in-cell input could blow stack and\n * memory. Set high enough to comfortably handle real legal documents\n * (tables nested 2-3 deep at most), low enough to short-circuit\n * pathological input.\n */\n private static MaxTablePreprocessDepth = 8\n\n /**\n * Mirror cap for the three-way path. The 2-way `MaxTablePreprocessDepth`\n * guards the recursion inside `executeWithContext`; the 3-way path has\n * its own recursion (`executeThreeWay` → `preprocessTablesThreeWay` →\n * `cellDiff` → `executeThreeWay`) which needs its own guard. Once the\n * cap is reached, `executeThreeWay` skips table preprocessing and\n * falls back to the word-level merge — same bail-out semantics as the\n * 2-way path.\n */\n private static MaxThreeWayDepth = 8\n\n private content: string[] = []\n private newText: string\n private oldText: string\n // Written exactly once, by `executeWithContext` on the inner instance\n // for a recursive cell-diff. Top-level instances stay at 0. Treated as\n // effectively-readonly elsewhere — we dropped the modifier only so\n // `executeWithContext` can populate it without needing a private\n // constructor overload that would re-leak the parameter we just hid.\n private tablePreprocessDepth = 0\n\n private specialTagDiffStack: string[] = []\n private newWords: string[] = []\n private oldWords: string[] = []\n /**\n * Content-only projections of oldWords/newWords (structural tags and adjacent whitespace removed).\n * When null, no structural normalization is applied (the word arrays are identical for diffing).\n */\n private oldContentWords: string[] | null = null\n private newContentWords: string[] | null = null\n /** Maps content-word index → original word index */\n private oldContentToOriginal: number[] | null = null\n private newContentToOriginal: number[] | null = null\n /**\n * Tracks the next unwritten word index in oldWords/newWords. Mutated only by\n * {@link sliceOriginalWordsForOp} (each op reads a slice and advances its cursor).\n * Advances monotonically. Used so:\n * - subsequent equal/delete ops know where in old to resume from\n * - subsequent insert ops know where in new to resume from\n * The two cursors are independent: equal/delete output from old and advance the old\n * cursor; insert outputs from new and advances the new cursor.\n */\n private lastOriginalOldOutputIndex = 0\n private lastOriginalNewOutputIndex = 0\n private matchGranularity = 0\n private blockExpressions: RegExp[] = []\n\n /**\n * Defines how to compare repeating words. Valid values are from 0 to 1.\n * This value allows to exclude some words from comparison that eventually\n * reduces the total time of the diff algorithm.\n * 0 means that all words are excluded so the diff will not find any matching words at all.\n * 1 (default value) means that all words participate in comparison so this is the most accurate case.\n * 0.5 means that any word that occurs more than 50% times may be excluded from comparison. This doesn't\n * mean that such words will definitely be excluded but only gives a permission to exclude them if necessary.\n */\n repeatingWordsAccuracy = 1.0\n\n /**\n * If true all whitespaces are considered as equal\n */\n ignoreWhitespaceDifferences = false\n\n /**\n * If some match is too small and located far from its neighbors then it is considered as orphan\n * and removed. For example:\n * <code>\n * aaaaa bb ccccccccc dddddd ee\n * 11111 bb 222222222 dddddd ee\n * </code>\n * will find two matches <code>bb</code> and <code>dddddd ee</code> but the first will be considered\n * as orphan and ignored, as result it will consider texts <code>aaaaa bb ccccccccc</code> and\n * <code>11111 bb 222222222</code> as single replacement:\n * <code>\n * &lt;del&gt;aaaaa bb ccccccccc&lt;/del&gt;&lt;ins&gt;11111 bb 222222222&lt;/ins&gt; dddddd ee\n * </code>\n * This property defines relative size of the match to be considered as orphan, from 0 to 1.\n * 1 means that all matches will be considered as orphans.\n * 0 (default) means that no match will be considered as orphan.\n * 0.2 means that if match length is less than 20% of distance between its neighbors it is considered as orphan.\n */\n orphanMatchThreshold = 0.0\n\n /**\n * Initializes a new instance of the class.\n * @param oldText The old text.\n * @param newText The new text.\n */\n constructor(oldText: string, newText: string) {\n this.oldText = oldText\n this.newText = newText\n }\n\n static execute(oldText: string, newText: string): string {\n return new HtmlDiff(oldText, newText).build()\n }\n\n /**\n * Analyse a two-way diff and return its raw building blocks: the word\n * arrays the diff ran against, the operations produced, the original\n * (pre-projection) word arrays, and the mappings from diff-index back\n * to original-word index when structural projection is active.\n * Consumed by `executeThreeWay` so it can compose two diffs by walking\n * their Operation streams.\n *\n * The caller is expected to coordinate `useProjections` symmetrically\n * across composed analyses — if V1↔V2 projects but V2↔V3 doesn't,\n * V2's \"new\" array in the first analysis won't equal V2's \"old\" array\n * in the second. `evaluateProjectionApplicability` exposes the same\n * heuristic `build()` uses internally, so the orchestrator can compute\n * a single decision and pass it into every `analyze` call.\n *\n * Table preprocessing is skipped here. Placeholders mutate the input\n * in ways that don't compose across two independent analyses; the\n * 3-way orchestrator handles tables explicitly before calling analyze.\n */\n static analyze(oldText: string, newText: string, options: AnalyzeOptions = {}): AnalyzeResult {\n const inner = new HtmlDiff(oldText, newText)\n // Bypass table preprocessing — the caller handles tables.\n inner.tablePreprocessDepth = HtmlDiff.MaxTablePreprocessDepth\n if (options.blockExpressions) {\n for (const expr of options.blockExpressions) inner.addBlockExpression(expr)\n }\n if (options.repeatingWordsAccuracy !== undefined) inner.repeatingWordsAccuracy = options.repeatingWordsAccuracy\n if (options.orphanMatchThreshold !== undefined) inner.orphanMatchThreshold = options.orphanMatchThreshold\n if (options.ignoreWhitespaceDifferences !== undefined) {\n inner.ignoreWhitespaceDifferences = options.ignoreWhitespaceDifferences\n }\n inner.splitInputsToWords()\n if (options.useProjections === undefined) {\n // Mirror build()'s heuristic — same behaviour as a standalone 2-way diff.\n inner.buildContentProjections()\n } else if (options.useProjections) {\n // Caller forced projections on. Still skip if either side has no\n // structural content, since projecting an empty side produces an\n // empty diff space and the merge degrades.\n const oldProj = HtmlDiff.createContentProjection(inner.oldWords)\n const newProj = HtmlDiff.createContentProjection(inner.newWords)\n if (oldProj.contentWords.length > 0 && newProj.contentWords.length > 0) {\n inner.oldContentWords = oldProj.contentWords\n inner.oldContentToOriginal = oldProj.contentToOriginal\n inner.newContentWords = newProj.contentWords\n inner.newContentToOriginal = newProj.contentToOriginal\n }\n }\n // useProjections === false: leave projections unset, diff runs on raw words.\n const wordsForDiffOld = inner.oldContentWords ?? inner.oldWords\n const wordsForDiffNew = inner.newContentWords ?? inner.newWords\n inner.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(wordsForDiffOld.length, wordsForDiffNew.length)\n )\n return {\n oldDiffWords: wordsForDiffOld,\n newDiffWords: wordsForDiffNew,\n operations: inner.operations(),\n oldOriginalWords: inner.oldWords,\n newOriginalWords: inner.newWords,\n oldContentToOriginal: inner.oldContentToOriginal,\n newContentToOriginal: inner.newContentToOriginal,\n }\n }\n\n /**\n * Whether content-projection (structural-tag normalisation) would\n * apply to this pair of inputs under `build()`'s default heuristic.\n * Exposed so composers of multiple analyses can compute a symmetric\n * decision before calling `analyze` — see `analyze`'s docstring for\n * why symmetry matters.\n */\n static evaluateProjectionApplicability(oldText: string, newText: string): boolean {\n const oldWords = WordSplitter.convertHtmlToListOfWords(oldText, [])\n const newWords = WordSplitter.convertHtmlToListOfWords(newText, [])\n if (!HtmlDiff.hasStructuralDifferences(oldWords, newWords)) return false\n const oldProj = HtmlDiff.createContentProjection(oldWords)\n const newProj = HtmlDiff.createContentProjection(newWords)\n return HtmlDiff.shouldUseContentProjections(oldWords, newWords, oldProj, newProj)\n }\n\n /**\n * Three-way HTML diff. Given V1 (the version Me last sent), V2 (the\n * version CP sent back), and V3 (Me's current draft), produces a\n * single attributed HTML output where CP's and Me's changes are\n * distinguished by `data-author` ('cp' or 'me') and matching\n * `class='diffins cp'` / `class='diffdel me'` etc. The \"Me rejected\n * CP's proposal\" case (Me deleted text CP had inserted) gets a\n * dedicated marker: `data-rejects='cp'` plus `class='... rejects-cp'`.\n *\n * Coordinates the symmetric-projection decision (D1) across both\n * internal `analyze` calls so V2 tokenises identically on each side\n * of the spine. When `useProjections` is left undefined, the decision\n * is the conjunction of both pair-wise heuristics — project iff both\n * pairs would project on their own. Pass an explicit boolean to\n * override.\n */\n /**\n * Three-way HTML diff against a shared genesis. Produces attributed\n * HTML that distinguishes CP's accumulated changes (genesis → cpLatest)\n * from Me's accumulated changes (genesis → meCurrent). Use this for\n * blackline UX where the negotiation has gone through multiple turns\n * and the reader wants to see \"who proposed what\" across the whole\n * history, not just the most recent round.\n *\n * When both parties happen to have made the same change (e.g. CP\n * proposed a wording change in turn N, Me adopted it in turn N+1),\n * the change reads as \"settled\" and is emitted unmarked — only\n * disagreements and pending proposals carry author attribution.\n *\n * @param genesis the shared common ancestor (per-user — the FE\n * picks between V1.0 and /preview/initialAnswers\n * based on `prefillReceiverAnswers`)\n * @param cpLatest the counterparty's current published version\n * @param meCurrent Me's current draft (the document on screen)\n */\n static executeThreeWay(genesis: string, cpLatest: string, meCurrent: string, options: ThreeWayOptions = {}): string {\n return HtmlDiff.executeThreeWayWithDepth(genesis, cpLatest, meCurrent, options, 0)\n }\n\n private static executeThreeWayWithDepth(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n options: ThreeWayOptions,\n depth: number\n ): string {\n // Table preprocessing first — replaces each genesis/cp/me table with a\n // shared-nonce placeholder, then the word-level merge runs over the\n // table-free inputs. Cells are diffed recursively via executeThreeWay\n // so the cell content is itself three-way attributed.\n //\n // Depth-cap the recursion so adversarially-nested input can't blow\n // stack/memory.\n const tablePreprocess =\n depth < HtmlDiff.MaxThreeWayDepth\n ? preprocessTablesThreeWay(genesis, cpLatest, meCurrent, (g, c, m) =>\n HtmlDiff.executeThreeWayWithDepth(g, c, m, options, depth + 1)\n )\n : null\n const inGenesis = tablePreprocess?.modifiedGenesis ?? genesis\n const inCp = tablePreprocess?.modifiedCp ?? cpLatest\n const inMe = tablePreprocess?.modifiedMe ?? meCurrent\n\n // Symmetric projection across both analyses. The genesis-spine\n // algorithm requires `genesis` to tokenise identically on each\n // pair-wise analysis (both have genesis as the OLD side), so the\n // useProjections decision must agree across both calls.\n const useProjections =\n options.useProjections ??\n (HtmlDiff.evaluateProjectionApplicability(inGenesis, inCp) &&\n HtmlDiff.evaluateProjectionApplicability(inGenesis, inMe))\n\n const analyzeOpts: AnalyzeOptions = {\n useProjections,\n blockExpressions: options.blockExpressions,\n repeatingWordsAccuracy: options.repeatingWordsAccuracy,\n orphanMatchThreshold: options.orphanMatchThreshold,\n ignoreWhitespaceDifferences: options.ignoreWhitespaceDifferences,\n }\n const dCp = HtmlDiff.analyze(inGenesis, inCp, analyzeOpts)\n const dMe = HtmlDiff.analyze(inGenesis, inMe, analyzeOpts)\n\n // Spine sanity check — both analyses must share an identical genesis\n // tokenisation. Symmetric useProjections guarantees this; if it ever\n // diverges, fail loudly rather than silently misattribute.\n if (dCp.oldDiffWords.length !== dMe.oldDiffWords.length) {\n throw new Error(\n 'HtmlDiff.executeThreeWay: genesis tokenisation diverged across pair-wise analyses ' +\n `(${dCp.oldDiffWords.length} vs ${dMe.oldDiffWords.length}). ` +\n 'This indicates the symmetric-projection coordination has a bug.'\n )\n }\n\n const segments = buildSegments(dCp, dMe)\n const merged = HtmlDiff.emitSegments(segments)\n return tablePreprocess ? restoreTablePlaceholders(merged, tablePreprocess.placeholderToDiff) : merged\n }\n\n /**\n * Drives a fresh `HtmlDiff` instance through `insertTag` for ins/del\n * segments and pushes equal segments straight to its `content`\n * buffer. Reusing the instance keeps the formatting-tag stack\n * (`specialTagDiffStack`) coherent across segments — a `<strong>`\n * opened in one segment and closed in another stays balanced.\n */\n private static emitSegments(segments: Segment[]): string {\n const emitter = new HtmlDiff('', '')\n for (const seg of segments) {\n if (seg.attr.kind === 'equal') {\n emitter.content.push(seg.words.join(''))\n continue\n }\n const { tag, baseClass, metadata } = segmentEmissionShape(seg.attr)\n // insertTag mutates its `words` array; pass a copy.\n emitter.insertTag(tag, baseClass, [...seg.words], metadata)\n }\n // Stack-balance invariant: every special-case opening tag pushed onto\n // `specialTagDiffStack` during emission must have been matched by a\n // closing tag. An unbalanced stack means the input had unbalanced\n // formatting tags AND a Replace at an inconvenient position — the\n // output would be silently malformed (half-closed `<ins>`). Fail\n // loudly so the caller can investigate rather than ship broken HTML.\n if (emitter.specialTagDiffStack.length > 0) {\n throw new Error(\n `HtmlDiff.executeThreeWay: emission left ${emitter.specialTagDiffStack.length} ` +\n 'unclosed formatting tag(s) on the stack — input may have unbalanced ' +\n '<strong>/<em>/etc. or there is a bug in segment emission.'\n )\n }\n return emitter.content.join('')\n }\n\n /**\n * Internal entry point used by the table-cell recursion. Constructs an\n * inner `HtmlDiff`, applies the caller's settings, and bumps the\n * recursion depth — keeping the public constructor signature clean\n * while still threading the configuration that's required for cell-\n * level output to match the top-level call's behaviour.\n */\n private static executeWithContext(oldText: string, newText: string, ctx: RecursionContext): string {\n const inner = new HtmlDiff(oldText, newText)\n inner.tablePreprocessDepth = ctx.depth\n for (const expr of ctx.blockExpressions) inner.addBlockExpression(expr)\n inner.repeatingWordsAccuracy = ctx.repeatingWordsAccuracy\n inner.orphanMatchThreshold = ctx.orphanMatchThreshold\n inner.ignoreWhitespaceDifferences = ctx.ignoreWhitespaceDifferences\n return inner.build()\n }\n\n /**\n * Builds the HTML diff output\n * @return HTML diff markup\n */\n build(): string {\n // If there is no difference, don't bother checking for differences\n if (this.oldText === this.newText) {\n return this.newText\n }\n\n // Table preprocessing: when both sides have matching `<table>` structures,\n // diff cells positionally so cross-cell content shifts produce one\n // independent del/ins per cell rather than cell-misaligned output.\n // Recursion is guarded by MaxTablePreprocessDepth — check the cap\n // first so we don't construct a context that will never be used.\n let tablePreprocess: ReturnType<typeof preprocessTables> = null\n if (this.tablePreprocessDepth < HtmlDiff.MaxTablePreprocessDepth) {\n // Caller-configured settings (block expressions, accuracy\n // thresholds) flow to the recursive cell diff via `RecursionContext`\n // so cell-level output is consistent with the top-level\n // configuration. The context is built once here and reused for\n // every cell-diff callback invocation.\n const ctx: RecursionContext = {\n depth: this.tablePreprocessDepth + 1,\n blockExpressions: this.blockExpressions,\n repeatingWordsAccuracy: this.repeatingWordsAccuracy,\n orphanMatchThreshold: this.orphanMatchThreshold,\n ignoreWhitespaceDifferences: this.ignoreWhitespaceDifferences,\n }\n tablePreprocess = preprocessTables(this.oldText, this.newText, (oldCell, newCell) =>\n HtmlDiff.executeWithContext(oldCell, newCell, ctx)\n )\n }\n if (tablePreprocess) {\n this.oldText = tablePreprocess.modifiedOld\n this.newText = tablePreprocess.modifiedNew\n }\n\n this.splitInputsToWords()\n this.buildContentProjections()\n\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n this.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(wordsForDiffOld.length, wordsForDiffNew.length)\n )\n\n const operations = this.operations()\n for (const op of operations) {\n this.performOperation(op)\n }\n\n const result = this.content.join('')\n return tablePreprocess ? restoreTablePlaceholders(result, tablePreprocess.placeholderToDiff) : result\n }\n\n /**\n * Uses {@link expression} to group text together so that any change detected within the group is treated as a single block\n * @param expression\n */\n addBlockExpression(expression: RegExp) {\n this.blockExpressions.push(expression)\n }\n\n private splitInputsToWords() {\n this.oldWords = WordSplitter.convertHtmlToListOfWords(this.oldText, this.blockExpressions)\n\n // free memory, allow it for GC\n this.oldText = ''\n\n this.newWords = WordSplitter.convertHtmlToListOfWords(this.newText, this.blockExpressions)\n\n // free memory, allow it for GC\n this.newText = ''\n }\n\n /**\n * Builds \"content projections\" — word arrays with structural wrapper tags stripped — when\n * structural normalization is appropriate for these inputs. The diff algorithm operates on\n * the projections so wrapper-tag differences (e.g. `<p>` vs `<div>`) don't appear as content\n * changes; structural tags are then folded back in at output time.\n */\n private buildContentProjections() {\n if (!HtmlDiff.hasStructuralDifferences(this.oldWords, this.newWords)) return\n\n const oldProjection = HtmlDiff.createContentProjection(this.oldWords)\n const newProjection = HtmlDiff.createContentProjection(this.newWords)\n\n if (!HtmlDiff.shouldUseContentProjections(this.oldWords, this.newWords, oldProjection, newProjection)) {\n return\n }\n\n this.oldContentWords = oldProjection.contentWords\n this.oldContentToOriginal = oldProjection.contentToOriginal\n this.newContentWords = newProjection.contentWords\n this.newContentToOriginal = newProjection.contentToOriginal\n }\n\n /**\n * Decides whether structural normalization should be activated for this pair of inputs.\n * Each clause is a distinct correctness or fitness check — extend by adding a named\n * sub-predicate rather than chaining ad-hoc conditions.\n */\n private static shouldUseContentProjections(\n oldWords: string[],\n newWords: string[],\n oldProjection: { contentWords: string[]; contentToOriginal: number[] },\n newProjection: { contentWords: string[]; contentToOriginal: number[] }\n ): boolean {\n // One side has no content at all: that's a genuine addition/deletion, not a wrapper rename.\n // Normalization would mis-attribute the wrappers as part of the diff.\n if (oldProjection.contentWords.length === 0 || newProjection.contentWords.length === 0) return false\n\n // Asymmetric structural state: one side has no structural wrappers at all (e.g. plain text\n // vs. wrapped HTML). Normalization would force the equal output to use the unwrapped side's\n // (missing) structure and emit dangling closing tags from the wrapped side. The plain\n // word-level diff handles this correctly without normalization.\n const oldHasStructuralTags = oldProjection.contentWords.length < oldWords.length\n const newHasStructuralTags = newProjection.contentWords.length < newWords.length\n if (oldHasStructuralTags !== newHasStructuralTags) return false\n\n return true\n }\n\n /**\n * Tags that commonly serve as content wrappers and may change structurally\n * without affecting the actual content. Only these tags are stripped during\n * structural normalization.\n */\n private static WrapperTags = new Set(['div', 'p', 'section', 'article', 'main', 'header', 'footer', 'aside', 'nav'])\n\n private static isStructuralTag(word: string): boolean {\n if (!Utils.isTag(word)) return false\n const tagName = Utils.getTagName(word)\n return HtmlDiff.WrapperTags.has(tagName)\n }\n\n /** True when the word is a structural opening tag (e.g. `<p>`, `<div>`). */\n private static isOpeningStructuralTag(word: string): boolean {\n return HtmlDiff.isStructuralTag(word) && !word.startsWith('</')\n }\n\n /**\n * Returns true if words between structural tags are just whitespace (indentation).\n */\n private static isStructuralWhitespace(words: string[], index: number): boolean {\n if (!Utils.isWhiteSpace(words[index])) return false\n\n // Check if this whitespace is adjacent to a structural tag on either side\n const prevIsStructural = index === 0 || HtmlDiff.isStructuralTag(words[index - 1])\n const nextIsStructural = index === words.length - 1 || HtmlDiff.isStructuralTag(words[index + 1])\n return prevIsStructural || nextIsStructural\n }\n\n private static createContentProjection(words: string[]): {\n contentWords: string[]\n contentToOriginal: number[]\n } {\n const contentWords: string[] = []\n const contentToOriginal: number[] = []\n\n for (let i = 0; i < words.length; i++) {\n if (HtmlDiff.isStructuralTag(words[i])) continue\n if (HtmlDiff.isStructuralWhitespace(words, i)) continue\n contentWords.push(words[i])\n contentToOriginal.push(i)\n }\n\n return { contentWords, contentToOriginal }\n }\n\n private static hasStructuralDifferences(oldWords: string[], newWords: string[]): boolean {\n const oldStructural: string[] = []\n const newStructural: string[] = []\n\n // Compare only tag names (stripped of attributes) since structural normalization\n // is about wrapper tag name changes (e.g. <p> vs <div>), not attribute differences.\n // Attribute changes on the same tag name don't need projection-based normalization.\n for (const w of oldWords) {\n if (HtmlDiff.isStructuralTag(w)) {\n oldStructural.push(Utils.stripTagAttributes(w))\n }\n }\n for (const w of newWords) {\n if (HtmlDiff.isStructuralTag(w)) {\n newStructural.push(Utils.stripTagAttributes(w))\n }\n }\n\n if (oldStructural.length !== newStructural.length) return true\n for (let i = 0; i < oldStructural.length; i++) {\n if (oldStructural[i] !== newStructural[i]) return true\n }\n return false\n }\n\n private performOperation(operation: Operation) {\n switch (operation.action) {\n case Action.Equal:\n this.processEqualOperation(operation)\n break\n case Action.Delete:\n this.processDeleteOperation(operation, 'diffdel')\n break\n case Action.Insert:\n this.processInsertOperation(operation, 'diffins')\n break\n case Action.None:\n break\n case Action.Replace:\n this.processReplaceOperation(operation)\n break\n }\n }\n\n private processReplaceOperation(operation: Operation) {\n this.processDeleteOperation(operation, 'diffmod')\n this.processInsertOperation(operation, 'diffmod')\n }\n\n private processInsertOperation(operation: Operation, cssClass: string) {\n const words = this.usingContentProjections()\n ? this.sliceOriginalWordsForOp('new', operation.startInNew, operation.endInNew)\n : this.newWords.slice(operation.startInNew, operation.endInNew)\n this.insertTag(HtmlDiff.InsTag, cssClass, words)\n }\n\n private processDeleteOperation(operation: Operation, cssClass: string) {\n const words = this.usingContentProjections()\n ? this.sliceOriginalWordsForOp('old', operation.startInOld, operation.endInOld)\n : this.oldWords.slice(operation.startInOld, operation.endInOld)\n this.insertTag(HtmlDiff.DelTag, cssClass, words)\n }\n\n private processEqualOperation(operation: Operation) {\n if (this.usingContentProjections()) {\n // Output from old to preserve old's HTML structure for the matched content.\n const result = this.sliceOriginalWordsForOp('old', operation.startInOld, operation.endInOld)\n this.content.push(result.join(''))\n\n // Advance new-side tracking past the equivalent range in new so the next insert op\n // resumes from the correct position. We compute new's range with the same rule used\n // for old (rather than mirroring old's count) so the two sides are independently sound\n // when their structural tags don't perfectly parallel each other.\n this.sliceOriginalWordsForOp('new', operation.startInNew, operation.endInNew)\n } else {\n const result = this.newWords.slice(operation.startInNew, operation.endInNew)\n this.content.push(result.join(''))\n }\n }\n\n /** True when content projections are active for both sides — i.e. structural normalization is in effect. */\n private usingContentProjections(): boolean {\n return this.oldContentToOriginal !== null && this.newContentToOriginal !== null\n }\n\n /**\n * Returns the slice of original (old or new) words covering a content-index range,\n * including the structural tags that surround the content. Advances the side's cursor\n * past the slice so the next op resumes correctly.\n *\n * The slice extends:\n * - LEADING: from the side's cursor (or the first content word's original index,\n * whichever is smaller) so structural tags that precede the first content word\n * are picked up by this op rather than left orphaned.\n * - TRAILING (non-last range): from just after the last content word, including\n * closing structural tags that close *this* op's paragraphs, but stopping at\n * the first opening structural tag — that opening tag belongs to the next\n * op's paragraph and would otherwise be emitted twice.\n * - TRAILING (last range): all the way to the end of words, since there is no next\n * op to claim the trailing tags.\n */\n private sliceOriginalWordsForOp(side: 'old' | 'new', contentStart: number, contentEnd: number): string[] {\n const words = side === 'old' ? this.oldWords : this.newWords\n const contentToOriginal = side === 'old' ? this.oldContentToOriginal : this.newContentToOriginal\n\n if (!contentToOriginal) return words.slice(contentStart, contentEnd)\n if (contentStart >= contentEnd) return []\n\n const firstContentOrigIdx = contentToOriginal[contentStart]\n const lastContentOrigIdx = contentToOriginal[contentEnd - 1]\n const cursor = side === 'old' ? this.lastOriginalOldOutputIndex : this.lastOriginalNewOutputIndex\n const origStart = Math.min(cursor, firstContentOrigIdx)\n\n let origEnd: number\n if (contentEnd < contentToOriginal.length) {\n // Non-last range: walk trailing tags after the last content word, stopping at the\n // first opening structural tag so it can be emitted by the next op.\n const limit = contentToOriginal[contentEnd]\n origEnd = lastContentOrigIdx + 1\n while (origEnd < limit && !HtmlDiff.isOpeningStructuralTag(words[origEnd])) {\n origEnd++\n }\n } else {\n // Last range: include everything to the end.\n origEnd = words.length\n }\n\n if (side === 'old') {\n this.lastOriginalOldOutputIndex = origEnd\n } else {\n this.lastOriginalNewOutputIndex = origEnd\n }\n\n return words.slice(origStart, origEnd)\n }\n\n /**\n * This method encloses words within a specified tag (ins or del), and adds this into \"content\",\n * with a twist: if there are words contain tags, it actually creates multiple ins or del,\n * so that they don't include any ins or del. This handles cases like\n * old: '<p>a</p>'\n * new: '<p>ab</p>\n * <p>\n * c</b>'\n * diff result: '<p>a<ins>b</ins></p>\n * <p>\n * <ins>c</ins>\n * </p>\n * '\n * this still doesn't guarantee valid HTML (hint: think about diffing a text containing ins or\n * del tags), but handles correctly more cases than the earlier version.\n * P.S.: Spare a thought for people who write HTML browsers. They live in this ... every day.\n * @param tag\n * @param cssClass\n * @param words\n * @private\n */\n private insertTag(tag: string, cssClass: string, words: string[], metadata?: WrapMetadata) {\n while (true) {\n if (words.length === 0) {\n break\n }\n\n const allWordsUntilFirstTag = this.extractConsecutiveWords(words, x => !Utils.isTag(x))\n if (allWordsUntilFirstTag.length > 0) {\n const text = Utils.wrapText(allWordsUntilFirstTag.join(''), tag, cssClass, metadata)\n this.content.push(text)\n }\n\n const isInsertOpCompleted = words.length === 0\n if (isInsertOpCompleted) {\n break\n }\n\n // if there are still words left, they must start with a tag, but still can contain nonTag entries.\n // e.g. </span></big>bar\n // the remaining words need to be handled separately divided in a tagBlock, which definitely contains\n // at least one word and a potentially existing second block which starts with a nonTag but may\n // contain tags later on.\n const indexOfFirstNonTag = words.findIndex(x => !Utils.isTag(x))\n\n // if there are no nonTags, the whole block is a tagBlock and the index of the last tag is the last index of the block.\n // if there are nonTags, the index of the last tag is the index before the first nonTag.\n const indexLastTagInFirstTagBlock = indexOfFirstNonTag === -1 ? words.length - 1 : indexOfFirstNonTag - 1\n\n let specialCaseTagInjection = ''\n let specialCaseTagInjectionIsBefore = false\n\n // handle opening tag\n if (HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {\n const tagNames = new Set<string>()\n for (const word of words) {\n if (Utils.isTag(word)) {\n tagNames.add(Utils.getTagName(word))\n }\n }\n const styledTagNames = Array.from(tagNames).join(' ')\n\n this.specialTagDiffStack.push(words[0])\n // Carry the caller's metadata into the formatting-tag wrapper so\n // a 3-way author tag survives a `<strong>`/`<em>` content edit.\n specialCaseTagInjection = `<ins${Utils.composeTagAttributes(`mod ${styledTagNames}`, metadata ?? {})}>`\n if (tag === HtmlDiff.DelTag) {\n words.shift()\n\n // following tags may be formatting tags as well, follow through\n while (words.length > 0 && HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {\n words.shift()\n }\n }\n }\n // handle closing tag\n else if (HtmlDiff.SpecialCaseClosingTagsSet.has(words[0].toLowerCase())) {\n const openingTag = this.specialTagDiffStack.length === 0 ? null : this.specialTagDiffStack.pop()\n // For delete operations: when the tag block contains a mix of formatting and\n // non-formatting closing tags (e.g. </strong></div>), compare against the first\n // closing tag (the formatting one) rather than the last tag in the block.\n // For purely formatting tag blocks (e.g. </i></strong>) or insert operations,\n // use the last tag as before to match against the outermost opening tag.\n let tagIndexToCompare = indexLastTagInFirstTagBlock\n if (tag === HtmlDiff.DelTag && indexOfFirstNonTag === -1) {\n const hasNonFormattingClosingTag = words\n .slice(0, indexLastTagInFirstTagBlock + 1)\n .some(w => !HtmlDiff.SpecialCaseClosingTagsSet.has(w.toLowerCase()))\n if (hasNonFormattingClosingTag) {\n tagIndexToCompare = 0\n }\n }\n const openingAndClosingTagsMatch =\n !!openingTag && Utils.getTagName(openingTag) === Utils.getTagName(words[tagIndexToCompare])\n\n if (openingTag && openingAndClosingTagsMatch) {\n specialCaseTagInjection = '</ins>'\n specialCaseTagInjectionIsBefore = true\n }\n\n // if the tag has a corresponding opening tag, but they don't match,\n // we need to push the opening tag back onto the stack\n else if (openingTag) {\n this.specialTagDiffStack.push(openingTag)\n }\n\n if (tag === HtmlDiff.DelTag) {\n words.shift()\n // following tags may be formatting tags as well, follow through\n while (words.length > 0 && HtmlDiff.SpecialCaseClosingTagsSet.has(words[0].toLowerCase())) {\n words.shift()\n }\n }\n }\n\n if (words.length === 0 && specialCaseTagInjection.length === 0) {\n break\n }\n\n // For delete operations, only extract non-formatting tags. Formatting tags (special case\n // opening/closing tags) need to be handled in the next loop iteration so they go through\n // the proper specialTagDiffStack logic. Otherwise, opening formatting tags get output as\n // plain tags and their corresponding closing tags are later discarded, producing invalid HTML.\n const isTagForExtraction =\n tag === HtmlDiff.DelTag\n ? (x: string) =>\n Utils.isTag(x) &&\n !HtmlDiff.SpecialCaseOpeningTagRegex.test(x) &&\n !HtmlDiff.SpecialCaseClosingTagsSet.has(x.toLowerCase())\n : Utils.isTag\n\n if (specialCaseTagInjectionIsBefore) {\n this.content.push(specialCaseTagInjection + this.extractConsecutiveWords(words, isTagForExtraction).join(''))\n } else {\n this.content.push(this.extractConsecutiveWords(words, isTagForExtraction).join('') + specialCaseTagInjection)\n }\n\n if (words.length === 0) continue\n\n // if there are still words left, they must start with a nonTag and need to be handled in the next iteration.\n this.insertTag(tag, cssClass, words, metadata)\n break\n }\n }\n\n private extractConsecutiveWords(words: string[], condition: (character: string) => boolean): string[] {\n let indexOfFirstTag: number | null = null\n for (let i = 0; i < words.length; i++) {\n const word = words[i]\n if (i === 0 && word === ' ') {\n words[i] = '&nbsp;'\n }\n if (!condition(word)) {\n indexOfFirstTag = i\n break\n }\n }\n\n if (indexOfFirstTag !== null) {\n const items = words.slice(0, indexOfFirstTag)\n if (indexOfFirstTag > 0) {\n words.splice(0, indexOfFirstTag)\n }\n return items\n }\n\n const items = words.slice(0)\n words.splice(0, words.length)\n return items\n }\n\n private operations(): Operation[] {\n let positionInOld = 0\n let positionInNew = 0\n const operations: Operation[] = []\n\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n const matches = this.matchingBlocks()\n matches.push(new Match(wordsForDiffOld.length, wordsForDiffNew.length, 0))\n\n //Remove orphans from matches.\n //If distance between left and right matches is 4 times longer than length of current match then it is considered as orphan\n const matchesWithoutOrphans = this.removeOrphans(matches)\n\n for (const match of matchesWithoutOrphans) {\n const matchStartsAtCurrentPositionInOld = positionInOld === match.startInOld\n const matchStartsAtCurrentPositionInNew = positionInNew === match.startInNew\n\n let action: Action\n\n if (!matchStartsAtCurrentPositionInOld && !matchStartsAtCurrentPositionInNew) {\n action = Action.Replace\n } else if (matchStartsAtCurrentPositionInOld && !matchStartsAtCurrentPositionInNew) {\n action = Action.Insert\n } else if (!matchStartsAtCurrentPositionInOld) {\n action = Action.Delete\n } // This occurs if the first few words are the same in both versions\n else {\n action = Action.None\n }\n\n if (action !== Action.None) {\n operations.push(new Operation(action, positionInOld, match.startInOld, positionInNew, match.startInNew))\n }\n\n if (match.size !== 0) {\n operations.push(new Operation(Action.Equal, match.startInOld, match.endInOld, match.startInNew, match.endInNew))\n }\n\n positionInOld = match.endInOld\n positionInNew = match.endInNew\n }\n\n return operations\n }\n\n private *removeOrphans(matches: Match[]) {\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n let prev: Match = new Match(0, 0, 0)\n let curr: Match | null = null\n\n for (const next of matches) {\n if (curr === null) {\n curr = next\n continue\n }\n\n if (\n (prev.endInOld === curr.startInOld && prev.endInNew === curr.startInNew) ||\n (curr.endInOld === next.startInOld && curr.endInNew === next.startInNew)\n ) {\n //if match has no diff on the left or on the right\n yield curr\n prev = curr\n curr = next\n continue\n }\n\n let oldDistanceInChars = 0\n for (let i = prev.endInOld; i < next.startInOld; i++) {\n oldDistanceInChars += wordsForDiffOld[i].length\n }\n let newDistanceInChars = 0\n for (let i = prev.endInNew; i < next.startInNew; i++) {\n newDistanceInChars += wordsForDiffNew[i].length\n }\n let currMatchLengthInChars = 0\n for (let i = curr.startInNew; i < curr.endInNew; i++) {\n currMatchLengthInChars += wordsForDiffNew[i].length\n }\n\n if (currMatchLengthInChars > Math.max(oldDistanceInChars, newDistanceInChars) * this.orphanMatchThreshold) {\n yield curr\n }\n\n prev = curr\n curr = next\n }\n\n if (curr !== null) {\n yield curr //assume that the last match is always vital\n }\n }\n\n private matchingBlocks(): Match[] {\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n const matchingBlocks: Match[] = []\n this.findMatchingBlocks(0, wordsForDiffOld.length, 0, wordsForDiffNew.length, matchingBlocks)\n return matchingBlocks\n }\n\n private findMatchingBlocks(\n startInOld: number,\n endInOld: number,\n startInNew: number,\n endInNew: number,\n matchingBlocks: Match[]\n ) {\n const match = this.findMatch(startInOld, endInOld, startInNew, endInNew)\n\n if (match !== null) {\n if (startInOld < match.startInOld && startInNew < match.startInNew) {\n this.findMatchingBlocks(startInOld, match.startInOld, startInNew, match.startInNew, matchingBlocks)\n }\n\n matchingBlocks.push(match)\n\n if (match.endInOld < endInOld && match.endInNew < endInNew) {\n this.findMatchingBlocks(match.endInOld, endInOld, match.endInNew, endInNew, matchingBlocks)\n }\n }\n }\n\n private findMatch(startInOld: number, endInOld: number, startInNew: number, endInNew: number): Match | null {\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n // For large texts it is more likely that there is a Match of size bigger than maximum granularity.\n // If not then go down and try to find it with smaller granularity.\n for (let i = this.matchGranularity; i > 0; i--) {\n const options = {\n blockSize: i,\n repeatingWordsAccuracy: this.repeatingWordsAccuracy,\n ignoreWhitespaceDifferences: this.ignoreWhitespaceDifferences,\n }\n const finder = new MatchFinder(\n wordsForDiffOld,\n wordsForDiffNew,\n startInOld,\n endInOld,\n startInNew,\n endInNew,\n options\n )\n const match = finder.findMatch()\n if (match !== null) return match\n }\n return null\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HtmlDiff.cjs","names":["Utils","Utils","Utils","Utils"],"sources":["../src/Match.ts","../src/Utils.ts","../src/MatchFinder.ts","../src/Operation.ts","../src/Alignment.ts","../src/HtmlScanner.ts","../src/TableDiff.ts","../src/ThreeWayDiff.ts","../src/ThreeWayTable.ts","../src/WordSplitter.ts","../src/HtmlDiff.ts"],"sourcesContent":["export default class Match {\n private _startInOld: number\n private _startInNew: number\n private _size: number\n\n constructor(startInOld: number, startInNew: number, size: number) {\n this._startInOld = startInOld\n this._startInNew = startInNew\n this._size = size\n }\n\n get startInOld() {\n return this._startInOld\n }\n\n get startInNew() {\n return this._startInNew\n }\n\n get size() {\n return this._size\n }\n\n get endInOld() {\n return this._startInOld + this._size\n }\n\n get endInNew() {\n return this._startInNew + this._size\n }\n}\n","const openingTagRegex = /^\\s*<[^>]+>\\s*$/\nconst closingTagTexRegex = /^\\s*<\\/[^>]+>\\s*$/\nconst tagWordRegex = /<[^\\s>]+/\nconst whitespaceRegex = /^(\\s|&nbsp;)+$/\nconst wordRegex = /[\\w#@]+/\nconst tagRegex = /<\\/?(?<name>[^\\s/>]+)[^>]*>/\n\nconst SpecialCaseWordTags: readonly string[] = ['<img']\n\nexport function isTag(item: string): boolean {\n if (SpecialCaseWordTags.some(re => item?.startsWith(re))) {\n return false\n }\n\n return isOpeningTag(item) || isClosingTag(item)\n}\n\nfunction isOpeningTag(item: string): boolean {\n return openingTagRegex.test(item)\n}\n\nfunction isClosingTag(item: string): boolean {\n return closingTagTexRegex.test(item)\n}\n\nexport function stripTagAttributes(word: string): string {\n const match = tagWordRegex.exec(word)\n if (match) {\n return `${match[0]}${word.endsWith('/>') ? '/>' : '>'}`\n }\n\n return word\n}\n\n/**\n * Optional metadata attached to a wrapped tag. Used by `executeThreeWay`\n * to colour diff segments with their author (CP vs Me) via extra classes\n * and `data-*` attributes; the two-way path passes nothing and gets the\n * unchanged historical output.\n */\nexport interface WrapMetadata {\n /** Space-separated classes appended after `cssClass`. */\n extraClasses?: string\n /** `data-*` attribute map, keyed by the attribute name *without* the `data-` prefix. */\n dataAttrs?: Readonly<Record<string, string>>\n}\n\nexport function wrapText(text: string, tagName: string, cssClass: string, metadata?: WrapMetadata): string {\n if (!metadata) return `<${tagName} class='${cssClass}'>${text}</${tagName}>`\n return `<${tagName}${composeTagAttributes(cssClass, metadata)}>${text}</${tagName}>`\n}\n\n/**\n * Build the attribute portion of an opening tag from a base class plus\n * optional metadata. Exposed so emission paths that build opening-tag\n * fragments by hand (e.g. the formatting-tag special-case in\n * `HtmlDiff.insertTag`) can stay consistent with `wrapText`.\n */\nexport function composeTagAttributes(cssClass: string, metadata: WrapMetadata): string {\n const classes = metadata.extraClasses ? `${cssClass} ${metadata.extraClasses}` : cssClass\n let out = ` class='${classes}'`\n if (metadata.dataAttrs) {\n for (const key of Object.keys(metadata.dataAttrs)) {\n out += ` data-${key}='${metadata.dataAttrs[key]}'`\n }\n }\n return out\n}\n\nexport function isStartOfTag(val: string): boolean {\n return val === '<'\n}\n\nexport function isEndOfTag(val: string): boolean {\n return val === '>'\n}\n\nexport function isStartOfEntity(val: string): boolean {\n return val === '&'\n}\n\nexport function isEndOfEntity(val: string): boolean {\n return val === ';'\n}\n\nexport function isWhiteSpace(value: string): boolean {\n return whitespaceRegex.test(value)\n}\n\nexport function stripAnyAttributes(word: string): string {\n if (isTag(word)) {\n return stripTagAttributes(word)\n }\n\n return word\n}\n\nexport function isWord(text: string): boolean {\n return wordRegex.test(text)\n}\n\nexport function getTagName(word: string | null): string {\n if (word === null) {\n return ''\n }\n\n const match = tagRegex.exec(word)\n if (match) {\n return match.groups?.name.toLowerCase() ?? match[1].toLowerCase()\n }\n\n return ''\n}\n\nexport default {\n isTag,\n stripTagAttributes,\n wrapText,\n composeTagAttributes,\n isStartOfTag,\n isEndOfTag,\n isStartOfEntity,\n isEndOfEntity,\n isWhiteSpace,\n stripAnyAttributes,\n isWord,\n getTagName,\n}\n","import Match from './Match'\nimport type MatchOptions from './MatchOptions'\nimport Utils from './Utils'\n\n/**\n * Finds the longest match in given texts. It uses indexing with fixed granularity that is used to compare blocks of text.\n */\nexport default class MatchFinder {\n private oldWords: string[]\n private newWords: string[]\n private startInOld: number\n private endInOld: number\n private startInNew: number\n private endInNew: number\n private wordIndices: { [word: string]: number[] } = {}\n private options: MatchOptions\n\n constructor(\n oldWords: string[],\n newWords: string[],\n startInOld: number,\n endInOld: number,\n startInNew: number,\n endInNew: number,\n options: MatchOptions\n ) {\n this.oldWords = oldWords\n this.newWords = newWords\n this.startInOld = startInOld\n this.endInOld = endInOld\n this.startInNew = startInNew\n this.endInNew = endInNew\n this.options = options\n }\n\n private indexNewWords() {\n this.wordIndices = {}\n const block: string[] = []\n for (let i = this.startInNew; i < this.endInNew; i++) {\n // if word is a tag, we should ignore attributes as attribute changes are not supported (yet)\n const word = this.normalizeForIndex(this.newWords[i])\n const key = MatchFinder.putNewWord(block, word, this.options.blockSize)\n\n if (key === null) {\n continue\n }\n\n if (!this.wordIndices[key]) {\n this.wordIndices[key] = []\n }\n this.wordIndices[key].push(i)\n }\n }\n\n private static putNewWord(block: string[], word: string, blockSize: number): string | null {\n block.push(word)\n\n if (block.length > blockSize) {\n block.shift()\n }\n\n if (block.length !== blockSize) {\n return null\n }\n\n return block.join('')\n }\n\n private normalizeForIndex(word: string): string {\n const output = Utils.stripAnyAttributes(word)\n if (this.options.ignoreWhitespaceDifferences && Utils.isWhiteSpace(output)) {\n return ' '\n }\n\n return output\n }\n\n findMatch(): Match | null {\n this.indexNewWords()\n this.removeRepeatingWords()\n\n let hasIndices = false\n for (const _key in this.wordIndices) {\n hasIndices = true\n break\n }\n if (!hasIndices) {\n return null\n }\n\n let bestMatchInOld = this.startInOld\n let bestMatchInNew = this.startInNew\n let bestMatchSize = 0\n\n let matchLengthAt: Map<number, number> = new Map()\n const block: string[] = []\n\n for (let indexInOld = this.startInOld; indexInOld < this.endInOld; indexInOld++) {\n const word = this.normalizeForIndex(this.oldWords[indexInOld])\n const index = MatchFinder.putNewWord(block, word, this.options.blockSize)\n\n if (index === null) {\n continue\n }\n\n const newMatchLengthAt: Map<number, number> = new Map()\n\n if (!this.wordIndices[index]) {\n matchLengthAt = newMatchLengthAt\n continue\n }\n\n for (const indexInNew of this.wordIndices[index]) {\n // biome-ignore lint/style/noNonNullAssertion: This is safe as guarded by has()\n const newMatchLength = (matchLengthAt.has(indexInNew - 1) ? matchLengthAt.get(indexInNew - 1)! : 0) + 1\n newMatchLengthAt.set(indexInNew, newMatchLength)\n\n if (newMatchLength > bestMatchSize) {\n bestMatchInOld = indexInOld - newMatchLength - this.options.blockSize + 2\n bestMatchInNew = indexInNew - newMatchLength - this.options.blockSize + 2\n bestMatchSize = newMatchLength\n }\n }\n\n matchLengthAt = newMatchLengthAt\n }\n\n return bestMatchSize !== 0\n ? new Match(bestMatchInOld, bestMatchInNew, bestMatchSize + this.options.blockSize - 1)\n : null\n }\n\n /**\n * This method removes words that occur too many times. This way it reduces total count of comparison operations\n * and as result the diff algorithm takes less time. But the side effect is that it may detect false differences of\n * the repeating words.\n * @private\n */\n private removeRepeatingWords() {\n const threshold = this.newWords.length * this.options.repeatingWordsAccuracy\n const repeatingWords = Object.entries(this.wordIndices)\n .filter(([, indices]) => indices.length > threshold)\n .map(([word]) => word)\n\n for (const w of repeatingWords) {\n delete this.wordIndices[w]\n }\n }\n}\n","import type Action from './Action'\n\nexport default class Operation {\n action: Action\n startInOld: number\n endInOld: number\n startInNew: number\n endInNew: number\n\n constructor(action: Action, startInOld: number, endInOld: number, startInNew: number, endInNew: number) {\n this.action = action\n this.startInOld = startInOld\n this.endInOld = endInOld\n this.startInNew = startInNew\n this.endInNew = endInNew\n }\n}\n","/**\n * Generic sequence-alignment primitives used by the table-aware diff and\n * potentially other granularities (rows, cells, list items, …). Nothing\n * here knows about tables or HTML — the caller passes string keys for\n * exact matching and a similarity callback for fuzzy pairing.\n */\n\nexport interface Alignment {\n oldIdx: number | null\n newIdx: number | null\n}\n\n/**\n * Standard LCS alignment: walks both sequences and emits a list of pairs\n * where `(oldIdx, newIdx)` are both set for matching positions, and one\n * side is null for an unmatched entry on the other side. Equality uses\n * strict ===.\n */\nexport function lcsAlign(oldKeys: string[], newKeys: string[]): Alignment[] {\n const m = oldKeys.length\n const n = newKeys.length\n const dp: number[][] = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0))\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (oldKeys[i - 1] === newKeys[j - 1]) {\n dp[i][j] = dp[i - 1][j - 1] + 1\n } else {\n dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])\n }\n }\n }\n\n // Backtrack and push; reverse at the end. `unshift` is O(n) per call\n // so the naive version was O(n²); push+reverse is O(n) total.\n const result: Alignment[] = []\n let i = m\n let j = n\n while (i > 0 || j > 0) {\n if (i > 0 && j > 0 && oldKeys[i - 1] === newKeys[j - 1]) {\n result.push({ oldIdx: i - 1, newIdx: j - 1 })\n i--\n j--\n } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {\n result.push({ oldIdx: null, newIdx: j - 1 })\n j--\n } else {\n result.push({ oldIdx: i - 1, newIdx: null })\n i--\n }\n }\n result.reverse()\n return result\n}\n\n/**\n * Given a shorter sequence (M items) and a longer sequence (N items, with\n * N > M), find the K = N - M positions in the longer sequence that should\n * be \"skipped\" so the unskipped longer items, aligned positionally with\n * the shorter items, maximise the sum of pairwise similarity.\n *\n * Solves the same problem as enumerating C(N, K) skip combinations and\n * picking the highest-scoring one, but in O(M × N) time via DP:\n *\n * f(i, j) = max similarity from consuming i shorter and j longer items\n * (defined for j >= i; entries below the diagonal are never\n * written or read).\n * f(0, j) = 0\n * f(i, j) = max(\n * f(i-1, j-1) + similarity(i-1, j-1), // pair\n * f(i, j-1) // skip longer[j-1]\n * )\n *\n * Tie-breaking prefers pairing over skipping, so ties resolve to skipping\n * EARLIER positions — matching the lex-first-combo behaviour of a full\n * combinatorial enumeration over which K positions to skip. Backtrack\n * re-asks the fill's pair-vs-skip question to preserve this direction\n * (the alternative — a `dp[i][j] > dp[i][j-1]` shortcut — would invert\n * the tie-breaking).\n *\n * Caller responsibility: ensure `longerTexts.length >= shorterTexts.length`.\n */\nexport function findOptimalAlignmentSkips(\n shorterTexts: string[],\n longerTexts: string[],\n similarity: (shorterIdx: number, longerIdx: number) => number\n): number[] {\n const m = shorterTexts.length\n const n = longerTexts.length\n // dp[i][j] is valid only for j >= i; entries below the diagonal are\n // allocated (uniform-shaped matrix keeps the indexing straight) but\n // never written or read. The wasted (M choose 2)-ish cells are not\n // worth \"optimising\" — a triangular layout would complicate the\n // backtrack's `dp[i][j-1]` reads and the (j > i ? skip : NEG_INF)\n // boundary handling, with no measurable win at the sizes this runs\n // on (capped by MAX_COLUMN_SEARCH_WIDTH).\n const dp: number[][] = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0))\n for (let i = 1; i <= m; i++) {\n for (let j = i; j <= n; j++) {\n const pair = dp[i - 1][j - 1] + similarity(i - 1, j - 1)\n const skip = j > i ? dp[i][j - 1] : Number.NEGATIVE_INFINITY\n dp[i][j] = pair >= skip ? pair : skip\n }\n }\n\n // Backtrack from (m, n). To preserve the fill's \"prefer pair on ties\"\n // direction we have to ask the same question the fill asked:\n // pair = dp[i-1][j-1] + similarity(i-1, j-1)\n // skip = dp[i][j-1]\n // and choose pair iff pair >= skip. A `dp[i][j] > dp[i][j-1]` shortcut\n // would invert the tie-breaking (it'd skip earlier positions on ties)\n // and shift outputs for score-tied scenarios — see the\n // `column-position search — score-tied inputs` regression tests in\n // `HtmlDiff.tables.spec.ts`. The extra similarity calls during\n // backtrack run O(M+N) times total, dwarfed by the O(M × N) fill.\n const skipped: number[] = []\n let i = m\n let j = n\n while (j > 0) {\n if (i === 0) {\n skipped.push(j - 1)\n j--\n continue\n }\n if (j === i) {\n // No slack left — every remaining move is a pair.\n i--\n j--\n continue\n }\n const pair = dp[i - 1][j - 1] + similarity(i - 1, j - 1)\n const skip = dp[i][j - 1]\n if (pair >= skip) {\n i--\n j--\n } else {\n skipped.push(j - 1)\n j--\n }\n }\n skipped.reverse()\n return skipped\n}\n\n/**\n * Identifies pairings inside each unmatched-only run, then builds the\n * output alignment by walking the original and substituting paired\n * entries at the *ins position* (not the del position). This keeps the\n * result monotonically non-decreasing in newIdx — required by any\n * downstream emission that walks the new sequence in order. Emitting at\n * the del position would be safe when del<ins in the alignment array\n * (the typical case), but can violate monotonicity when unpaired\n * entries interleave with paired ones in the same run.\n *\n * Greedy assignment: the first del in document order wins its best ins.\n * Suboptimal vs Hungarian on edge cases (two dels above threshold for\n * the same ins), but bounded — a losing del just emits as a full delete\n * rather than a content edit.\n */\nexport function pairSimilarUnmatched(\n alignment: Alignment[],\n threshold: number,\n similarity: (oldIdx: number, newIdx: number) => number\n): Alignment[] {\n const pairs = new Map<number, number>() // del-alignment-idx → ins-alignment-idx\n let i = 0\n while (i < alignment.length) {\n if (alignment[i].oldIdx !== null && alignment[i].newIdx !== null) {\n i++\n continue\n }\n const runStart = i\n while (i < alignment.length && (alignment[i].oldIdx === null) !== (alignment[i].newIdx === null)) i++\n const runEnd = i\n\n const delIndices: number[] = []\n const insIndices: number[] = []\n for (let k = runStart; k < runEnd; k++) {\n if (alignment[k].oldIdx !== null) delIndices.push(k)\n else insIndices.push(k)\n }\n\n const usedIns = new Set<number>()\n for (const di of delIndices) {\n let bestIi = -1\n let bestSim = threshold\n for (const ii of insIndices) {\n if (usedIns.has(ii)) continue\n const sim = similarity(alignment[di].oldIdx as number, alignment[ii].newIdx as number)\n if (sim > bestSim) {\n bestSim = sim\n bestIi = ii\n }\n }\n if (bestIi >= 0) {\n pairs.set(di, bestIi)\n usedIns.add(bestIi)\n }\n }\n }\n\n const insToDel = new Map<number, number>() // ins-alignment-idx → del-alignment-idx\n for (const [delAi, insAi] of pairs) insToDel.set(insAi, delAi)\n const pairedDels = new Set<number>(pairs.keys())\n\n const result: Alignment[] = []\n for (let k = 0; k < alignment.length; k++) {\n if (pairedDels.has(k)) continue // paired del — emitted when we reach its ins\n if (insToDel.has(k)) {\n const delAi = insToDel.get(k) as number\n result.push({ oldIdx: alignment[delAi].oldIdx, newIdx: alignment[k].newIdx })\n } else {\n result.push(alignment[k])\n }\n }\n return result\n}\n\n/**\n * Reorders the alignment so a cursor-based emission walking the new\n * sequence in order produces entries in their visually-correct\n * position. Each entry is assigned a fractional \"position\" in new's\n * flow:\n *\n * • Preserved/paired (oldIdx, newIdx): position = newIdx.\n * • Pure insert (null, newIdx): position = newIdx.\n * • Pure delete (oldIdx, null): position = newIdx-of-preserved-just-\n * before-this-oldIdx + 0.5. Dels at the same gap sort by oldIdx so\n * they appear in old's source order. The +0.5 places dels BEFORE\n * any insert at the same gap (insert at newIdx N1+1 has position\n * N1+1 which is > N1+0.5), giving the natural \"delete first, insert\n * second\" reading order at a replaced position.\n *\n * Handles the full range:\n * • Run of unpaired dels at the start (no preserved predecessor):\n * position -0.5, sorted by oldIdx.\n * • Dels in the middle: positioned right after their preceding\n * preserved entry.\n * • Dels at the end (no preserved successor): positioned after the\n * last preserved entry.\n *\n * Without this reordering, a run of unpaired deletes ahead of any\n * preserved entry would be emitted before the first preserved entry,\n * regardless of where they originated in old.\n *\n * NB: `0.5` is the ONLY fractional offset used. If another decoration\n * kind ever needs a fractional position too, redesign this scheme\n * (e.g. a discrete `(integerSlot, kind, secondary)` triple) rather than\n * picking another magic offset and hoping it doesn't collide.\n */\nexport function orderAlignmentForEmission(alignment: Alignment[]): Alignment[] {\n const preserved: Array<{ oldIdx: number; newIdx: number }> = []\n for (const a of alignment) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n preserved.push({ oldIdx: a.oldIdx, newIdx: a.newIdx })\n }\n }\n preserved.sort((a, b) => a.oldIdx - b.oldIdx)\n\n // For a deleted entry with oldIdx K, return the newIdx of the preserved\n // entry with the largest oldIdx less than K, or -1 if none.\n function newIdxOfPreservedBefore(oldIdx: number): number {\n let result = -1\n for (const p of preserved) {\n if (p.oldIdx >= oldIdx) break\n result = p.newIdx\n }\n return result\n }\n\n // Decorate each alignment with a fractional position. We use\n // (primary, secondary) tuples so dels at the same gap sort by oldIdx\n // (in old's source order) and inserts at the same newIdx stay stable.\n const decorated = alignment.map((a, i) => {\n let primary: number\n let secondary: number\n if (a.newIdx !== null) {\n primary = a.newIdx\n secondary = a.oldIdx === null ? 1 : 0 // preserved before pure-insert at same newIdx (rare)\n } else {\n // Pure delete\n primary = newIdxOfPreservedBefore(a.oldIdx as number) + 0.5\n secondary = a.oldIdx as number\n }\n return { entry: a, primary, secondary, originalIdx: i }\n })\n\n decorated.sort((a, b) => {\n if (a.primary !== b.primary) return a.primary - b.primary\n if (a.secondary !== b.secondary) return a.secondary - b.secondary\n return a.originalIdx - b.originalIdx // stable\n })\n\n return decorated.map(d => d.entry)\n}\n\n/**\n * Combined similarity metric used for fuzzy pairing. Returns the MAX of\n * two complementary metrics:\n *\n * 1. **Character prefix+suffix similarity** — fraction of the longer\n * string covered by shared prefix + shared suffix. Catches small\n * edits in the middle of a string (one word changed). Misses cases\n * where the bulk of common content is in the middle and the ends\n * differ.\n *\n * 2. **Token Jaccard similarity** — intersection-over-union of the\n * whitespace-split tokens. Catches \"most of the content is the\n * same but bookended by different bits\" — e.g. an edit where the\n * ~50 chars in the middle that DO match would be invisible to\n * prefix+suffix.\n *\n * Either metric exceeding the threshold means pair. Neither alone is\n * sufficient for the full range of legal-doc edits we see in\n * production tables.\n */\nexport function textSimilarity(a: string, b: string): number {\n if (a === b) return 1\n if (a.length === 0 || b.length === 0) return 0\n return Math.max(charPrefixSuffixSimilarity(a, b), tokenJaccardSimilarity(a, b))\n}\n\nfunction charPrefixSuffixSimilarity(a: string, b: string): number {\n let prefix = 0\n const minLen = Math.min(a.length, b.length)\n while (prefix < minLen && a[prefix] === b[prefix]) prefix++\n\n let suffix = 0\n while (\n suffix < a.length - prefix &&\n suffix < b.length - prefix &&\n a[a.length - 1 - suffix] === b[b.length - 1 - suffix]\n ) {\n suffix++\n }\n\n return (prefix + suffix) / Math.max(a.length, b.length)\n}\n\nfunction tokenJaccardSimilarity(a: string, b: string): number {\n const tokensA = new Set(a.split(/\\s+/).filter(Boolean))\n const tokensB = new Set(b.split(/\\s+/).filter(Boolean))\n if (tokensA.size === 0 && tokensB.size === 0) return 1\n let intersection = 0\n for (const t of tokensA) {\n if (tokensB.has(t)) intersection++\n }\n const union = tokensA.size + tokensB.size - intersection\n return union === 0 ? 0 : intersection / union\n}\n","/**\n * Low-level HTML tag-parsing primitives shared by the table-aware\n * preprocessing and (potentially) other consumers. These are deliberately\n * generic over the document type — no table-specific assumptions live\n * here.\n *\n * The goal is to walk HTML at the tag boundary level *without* parsing\n * into a full DOM, so we stay fast and never round-trip through\n * htmlparser2/DOMPurify in the diff hot path.\n */\n\nexport interface OpeningTag {\n /** Index just past the closing `>` of the opening tag. */\n end: number\n}\n\nexport interface ClassAttributeLocation {\n /** Index of the value's first character (inside the surrounding quotes). */\n valueStart: number\n /** Index just past the last character of the value (still inside quotes). */\n valueEnd: number\n /** The class attribute's value, with surrounding quotes stripped. */\n value: string\n}\n\n/**\n * Parses the opening tag (or comment/CDATA/PI) starting at `i`. Returns\n * the index just past the closing delimiter, or null if the tag is\n * malformed (unterminated). HTML comments, CDATA, processing\n * instructions, and DOCTYPE need their own terminators — a plain\n * `>`-walker would cut a comment like `<!-- a > b -->` at the first\n * inner `>`, treating the rest as text and corrupting downstream\n * offsets. Word-exported HTML routinely emits comments inside tables\n * (conditional comments, OLE markers) so these have to be handled.\n */\nexport function parseOpeningTagAt(html: string, i: number): OpeningTag | null {\n if (html.startsWith('<!--', i)) {\n const close = html.indexOf('-->', i + 4)\n return close === -1 ? null : { end: close + 3 }\n }\n if (html.startsWith('<![CDATA[', i)) {\n const close = html.indexOf(']]>', i + 9)\n return close === -1 ? null : { end: close + 3 }\n }\n if (html.startsWith('<?', i)) {\n const close = html.indexOf('?>', i + 2)\n return close === -1 ? null : { end: close + 2 }\n }\n // Walk to the next unquoted '>'. Handles attributes whose values contain\n // a literal '>' inside quotes, which a plain indexOf would mishandle.\n let j = i + 1\n let quote: string | null = null\n while (j < html.length) {\n const ch = html[j]\n if (quote) {\n if (ch === quote) quote = null\n } else if (ch === '\"' || ch === \"'\") {\n quote = ch\n } else if (ch === '>') {\n return { end: j + 1 }\n }\n j++\n }\n return null\n}\n\nexport function matchesTagAt(html: string, i: number, tagName: string): boolean {\n if (html[i] !== '<') return false\n const candidate = html.slice(i + 1, i + 1 + tagName.length).toLowerCase()\n if (candidate !== tagName) return false\n const after = html[i + 1 + tagName.length]\n return after === '>' || after === ' ' || after === '\\t' || after === '\\n' || after === '\\r' || after === '/'\n}\n\nexport function matchesClosingTagAt(html: string, i: number, tagName: string): boolean {\n if (html[i] !== '<' || html[i + 1] !== '/') return false\n const candidate = html.slice(i + 2, i + 2 + tagName.length).toLowerCase()\n if (candidate !== tagName) return false\n const after = html[i + 2 + tagName.length]\n return after === '>' || after === ' ' || after === '\\t' || after === '\\n' || after === '\\r'\n}\n\n/**\n * Returns the index just past the matching `</tagName>`, accounting for\n * nested tags of the same name. Returns -1 if no match before `limit`.\n */\nexport function findMatchingClosingTag(\n html: string,\n from: number,\n tagName: string,\n limit: number = html.length\n): number {\n let depth = 1\n let i = from\n while (i < limit) {\n if (matchesTagAt(html, i, tagName)) {\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const tagText = html.slice(i, opening.end)\n if (!tagText.endsWith('/>')) depth++\n i = opening.end\n } else if (matchesClosingTagAt(html, i, tagName)) {\n depth--\n const closing = parseOpeningTagAt(html, i)\n const closingEnd = closing?.end ?? i + `</${tagName}>`.length\n if (depth === 0) return closingEnd\n i = closingEnd\n } else {\n i++\n }\n }\n return -1\n}\n\n/**\n * Returns the opening tag with the given class injected. Locates the real\n * `class` attribute via attribute-aware walking (NOT a flat regex — that\n * would mis-match inside a foreign attribute value like\n * `title=\"see class='x'\"`). When the class already partially overlaps with\n * `cls` — e.g. existing `class=\"mod\"` and we're injecting `mod colspan` —\n * only the missing tokens get appended, so we never end up with\n * `class=\"mod mod colspan\"`.\n */\nexport function injectClass(openingTag: string, cls: string): string {\n const clsTokens = cls.split(/\\s+/).filter(Boolean)\n if (clsTokens.length === 0) return openingTag\n\n const classAttr = findClassAttribute(openingTag)\n if (classAttr) {\n const existingTokens = classAttr.value.split(/\\s+/).filter(Boolean)\n const missing = clsTokens.filter(t => !existingTokens.includes(t))\n if (missing.length === 0) return openingTag\n const updatedValue =\n existingTokens.length === 0 ? missing.join(' ') : `${existingTokens.join(' ')} ${missing.join(' ')}`\n return openingTag.slice(0, classAttr.valueStart) + updatedValue + openingTag.slice(classAttr.valueEnd)\n }\n\n const isSelfClosing = openingTag.endsWith('/>')\n const insertAt = isSelfClosing ? openingTag.length - 2 : openingTag.length - 1\n return `${openingTag.slice(0, insertAt).replace(/\\s*$/, '')} class='${cls}'${openingTag.slice(insertAt)}`\n}\n\n/**\n * Walks the opening tag's attributes (respecting quoted values) to find\n * the actual `class` attribute. Returns the value range (start/end of the\n * value content, *excluding* the surrounding quotes) and the value, or\n * null if no `class` attribute is present.\n */\nexport function findClassAttribute(openingTag: string): ClassAttributeLocation | null {\n // Skip past the tag name. Tag starts with `<`; first run of [A-Za-z0-9-]\n // is the tag name. Anything after is attribute territory.\n let i = 1\n while (i < openingTag.length && /[A-Za-z0-9_:-]/.test(openingTag[i])) i++\n\n while (i < openingTag.length) {\n // Skip whitespace\n while (i < openingTag.length && /\\s/.test(openingTag[i])) i++\n if (i >= openingTag.length) break\n if (openingTag[i] === '>' || openingTag[i] === '/') break\n\n // Read attribute name\n const nameStart = i\n while (i < openingTag.length && !/[\\s=>/]/.test(openingTag[i])) i++\n const name = openingTag.slice(nameStart, i)\n\n // Optional whitespace + '=' + optional whitespace + value\n while (i < openingTag.length && /\\s/.test(openingTag[i])) i++\n if (openingTag[i] !== '=') {\n // Bare attribute (no value) — not class\n continue\n }\n i++ // past '='\n while (i < openingTag.length && /\\s/.test(openingTag[i])) i++\n\n // Value: quoted or unquoted\n let valueStart: number\n let valueEnd: number\n if (openingTag[i] === '\"' || openingTag[i] === \"'\") {\n const quote = openingTag[i]\n i++\n valueStart = i\n while (i < openingTag.length && openingTag[i] !== quote) i++\n valueEnd = i\n if (i < openingTag.length) i++ // past closing quote\n } else {\n valueStart = i\n while (i < openingTag.length && !/[\\s>/]/.test(openingTag[i])) i++\n valueEnd = i\n }\n\n if (name.toLowerCase() === 'class') {\n return { valueStart, valueEnd, value: openingTag.slice(valueStart, valueEnd) }\n }\n }\n\n return null\n}\n","import {\n type Alignment,\n findOptimalAlignmentSkips,\n lcsAlign,\n orderAlignmentForEmission,\n pairSimilarUnmatched,\n textSimilarity,\n} from './Alignment'\nimport {\n findMatchingClosingTag,\n injectClass,\n matchesClosingTagAt,\n matchesTagAt,\n parseOpeningTagAt,\n} from './HtmlScanner'\nimport { wrapText } from './Utils'\n\n/**\n * Table-aware preprocessing for HtmlDiff.\n *\n * The word-level diff alone matches longest-common-subsequences across cell\n * boundaries and produces structurally wrong output for table edits — it\n * shuffles content between cells, introduces phantom `<td>`s, and provides\n * no signal that an entire row or column was added/deleted. We pre-process\n * the inputs to give Word-style results:\n *\n * • When dimensions match (same row count, same cell count per row), we\n * diff cell content positionally so cross-cell shifts produce one\n * independent del/ins per cell.\n * • When dimensions don't match (added/deleted row, added/deleted column),\n * we run a row-level LCS to identify structurally added/deleted rows,\n * then within preserved rows a cell-level LCS to identify added/deleted\n * columns. Structurally added rows/cells get `class='diffins'` on the\n * `<tr>`/`<td>`; deleted ones get `class='diffdel'`. Preserved cells\n * fall back to a content diff via the recursive HtmlDiff callback.\n *\n * Tables are spliced out into placeholders before the main diff runs and\n * spliced back in after, so the surrounding (non-table) content is diffed\n * by the normal word-level pipeline.\n */\n\nexport interface CellRange {\n /** Start index of the cell's opening tag in the original html. */\n cellStart: number\n /** Index just past the cell's closing tag. */\n cellEnd: number\n /** Cell content range — the slice we feed into the cell-level diff. */\n contentStart: number\n contentEnd: number\n}\n\nexport interface RowRange {\n rowStart: number\n rowEnd: number\n cells: CellRange[]\n}\n\nexport interface TableRange {\n tableStart: number\n tableEnd: number\n rows: RowRange[]\n}\n\nexport interface PreprocessResult {\n modifiedOld: string\n modifiedNew: string\n /** Maps placeholder marker → already-diffed table HTML to splice back in. */\n placeholderToDiff: Map<string, string>\n}\n\n// HTML comments survive WordSplitter as a single atomic token and are\n// treated as equal on both sides, so they pass through the diff\n// untouched and are easy to substitute back later. The nonce is generated\n// per call so a previously-diffed document being re-diffed (or any input\n// that legitimately contains an `<!--HTMLDIFF_TABLE_*-->` comment) can't\n// collide with the placeholder we substitute. We additionally regenerate\n// the nonce if it appears in either input.\nconst PLACEHOLDER_PREFIX_BASE = '<!--HTMLDIFF_TABLE_'\nconst PLACEHOLDER_SUFFIX = '-->'\n\n/**\n * Hard cap on table dimensions handled by the structural-aware path.\n * The row-LCS is O(rows²), the per-row cell-LCS is O(cells²), and each\n * comparison string-equals row content (potentially many KB). Without a\n * cap, a several-thousand-row table can pin a CPU for seconds. Tables\n * larger than this fall through to the word-level diff, which scales\n * linearly. Tuned to comfortably cover real-world ISDA schedules\n * (which routinely have 1000+ rows).\n */\nconst MAX_TABLE_ROWS = 1500\nconst MAX_TABLE_CELLS_PER_ROW = 200\n\n// Caps for the per-row column-position DP in\n// findBestColumnInsertPositions / findBestColumnDeletePositions.\n// MAX_COLUMN_DELTA is the *semantic* guard: a row with more than 6\n// columns added or deleted is almost always a row rewrite rather than\n// a structural column change, and is better handled by cell-LCS with\n// fuzzy pairing. MAX_COLUMN_SEARCH_WIDTH bounds the per-row DP at\n// O(MAX_COLUMN_SEARCH_WIDTH²) ≈ 40K ops; aligned with\n// MAX_TABLE_CELLS_PER_ROW so any row that survives the table-size cap\n// can still use the DP path.\nconst MAX_COLUMN_DELTA = 6\nconst MAX_COLUMN_SEARCH_WIDTH = 200\n\n/**\n * Generate a placeholder-prefix nonce that doesn't collide with any\n * existing content in the inputs. Variadic so callers with N inputs\n * (e.g. three-way diff with V1/V2/V3) check across all of them.\n */\nexport function makePlaceholderPrefix(...inputs: string[]): string {\n // 4 random bytes → 8 hex chars → 16^8 ≈ 4.3 billion combinations. We\n // also retry if the generated nonce happens to occur in any input.\n // Using `Math.random` here is fine: we're not defending against a\n // malicious adversary, just avoiding accidental collisions.\n for (let attempt = 0; attempt < 8; attempt++) {\n const nonce = Math.floor(Math.random() * 0xffffffff)\n .toString(16)\n .padStart(8, '0')\n const prefix = `${PLACEHOLDER_PREFIX_BASE}${nonce}_`\n if (inputs.every(input => !input.includes(prefix))) {\n return prefix\n }\n }\n // Astronomically unlikely. Falling back to a counter ensures progress\n // rather than an infinite loop, and any remaining collision will simply\n // surface as a malformed diff that the caller can detect.\n return `${PLACEHOLDER_PREFIX_BASE}fallback_${Date.now()}_`\n}\n\nexport { PLACEHOLDER_SUFFIX }\n\ntype DiffCellFn = (oldCellContent: string, newCellContent: string) => string\n\n/**\n * Diffs every paired-by-position table in the inputs and replaces each\n * source table with a placeholder, returning the modified inputs plus the\n * placeholder→diff mapping. Returns null when there are no tables to\n * preprocess or the table counts don't line up.\n */\nexport function preprocessTables(oldHtml: string, newHtml: string, diffCell: DiffCellFn): PreprocessResult | null {\n const oldTables = findTopLevelTables(oldHtml)\n const newTables = findTopLevelTables(newHtml)\n\n if (oldTables.length === 0 && newTables.length === 0) return null\n if (oldTables.length !== newTables.length) return null\n\n // Bail out on pathologically large tables — see MAX_TABLE_ROWS comment.\n for (let i = 0; i < oldTables.length; i++) {\n if (exceedsSizeLimit(oldTables[i]) || exceedsSizeLimit(newTables[i])) return null\n }\n\n const pairs: Array<{ oldTable: TableRange; newTable: TableRange; diffed: string }> = []\n for (let i = 0; i < oldTables.length; i++) {\n pairs.push({\n oldTable: oldTables[i],\n newTable: newTables[i],\n diffed: diffTable(oldHtml, newHtml, oldTables[i], newTables[i], diffCell),\n })\n }\n\n // Splice from end → start so earlier offsets stay valid.\n let modifiedOld = oldHtml\n let modifiedNew = newHtml\n const placeholderPrefix = makePlaceholderPrefix(oldHtml, newHtml)\n const placeholderToDiff = new Map<string, string>()\n for (let i = pairs.length - 1; i >= 0; i--) {\n const placeholder = `${placeholderPrefix}${i}${PLACEHOLDER_SUFFIX}`\n placeholderToDiff.set(placeholder, pairs[i].diffed)\n modifiedOld = spliceString(modifiedOld, pairs[i].oldTable.tableStart, pairs[i].oldTable.tableEnd, placeholder)\n modifiedNew = spliceString(modifiedNew, pairs[i].newTable.tableStart, pairs[i].newTable.tableEnd, placeholder)\n }\n\n return { modifiedOld, modifiedNew, placeholderToDiff }\n}\n\nexport function restoreTablePlaceholders(diffOutput: string, placeholderToDiff: Map<string, string>): string {\n let result = diffOutput\n for (const [placeholder, html] of placeholderToDiff) {\n result = result.split(placeholder).join(html)\n }\n return result\n}\n\nexport function spliceString(s: string, start: number, end: number, replacement: string): string {\n return s.slice(0, start) + replacement + s.slice(end)\n}\n\nexport function exceedsSizeLimit(table: TableRange): boolean {\n if (table.rows.length > MAX_TABLE_ROWS) return true\n for (const row of table.rows) {\n if (row.cells.length > MAX_TABLE_CELLS_PER_ROW) return true\n }\n return false\n}\n\nfunction diffTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n if (sameDimensions(oldTable, newTable)) {\n return diffPositionalTable(oldHtml, newHtml, oldTable, newTable, diffCell)\n }\n if (oldTable.rows.length === newTable.rows.length) {\n // Same row count, different cell counts: column add/delete only.\n // Aligning rows positionally avoids the LCS row-key mismatch that\n // happens when rows have different cell counts.\n return diffSameRowCountTable(oldHtml, newHtml, oldTable, newTable, diffCell)\n }\n return diffStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, diffCell)\n}\n\nfunction diffSameRowCountTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n // Walk the new table verbatim (preserving `<thead>`/`<tbody>` wrappers,\n // whitespace, etc.) and substitute each row's content with the diffed\n // form. The cursor-based emission keeps everything between rows intact.\n const out: string[] = []\n let cursor = newTable.tableStart\n let r = 0\n while (r < newTable.rows.length) {\n const merge = detectVerticalMerge(oldHtml, newHtml, oldTable, newTable, r)\n if (merge) {\n out.push(newHtml.slice(cursor, newTable.rows[r].rowStart))\n out.push(merge.diff)\n cursor = newTable.rows[r + merge.span - 1].rowEnd\n r += merge.span\n continue\n }\n const split = detectVerticalSplit(oldHtml, newHtml, oldTable, newTable, r)\n if (split) {\n out.push(newHtml.slice(cursor, newTable.rows[r].rowStart))\n out.push(split.diff)\n cursor = newTable.rows[r + split.span - 1].rowEnd\n r += split.span\n continue\n }\n const newRow = newTable.rows[r]\n out.push(newHtml.slice(cursor, newRow.rowStart))\n out.push(diffPreservedRow(oldHtml, newHtml, oldTable.rows[r], newRow, diffCell))\n cursor = newRow.rowEnd\n r++\n }\n out.push(newHtml.slice(cursor, newTable.tableEnd))\n return out.join('')\n}\n\n/**\n * Detects a vertical merge starting at row `r`: new row R has a single\n * cell with rowspan=K (and any colspan ≥ 1), with rows R+1..R+K-1 empty\n * in new. Old rows R..R+K-1 must have a logical column width equal to\n * the new cell's colspan and contain no rowspan'd cells of their own.\n * This handles both single-column merges (old rows are 1-cell, new cell\n * rowspan=K) and rectangular merges (e.g. 2×2 merge into a single\n * colspan=2 rowspan=2 cell). Output: emit the merged cell with\n * `class='mod rowspan'` and the empty trailing rows unchanged.\n */\nfunction detectVerticalMerge(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n r: number\n): { diff: string; span: number } | null {\n const newRow = newTable.rows[r]\n if (newRow.cells.length !== 1) return null\n const cell = newRow.cells[0]\n const span = getRowspan(newHtml, cell)\n if (span <= 1) return null\n if (r + span > newTable.rows.length) return null\n\n const colspan = getColspan(newHtml, cell)\n\n for (let k = 1; k < span; k++) {\n if (newTable.rows[r + k].cells.length !== 0) return null\n }\n for (let k = 0; k < span; k++) {\n const oldRow = oldTable.rows[r + k]\n if (!oldRow) return null\n // The absorbed region's logical width must match the merged cell's\n // colspan; otherwise this isn't a clean rectangular merge and we let\n // the caller fall through.\n if (sumColspans(oldHtml, oldRow.cells) !== colspan) return null\n for (const c of oldRow.cells) {\n if (getRowspan(oldHtml, c) !== 1) return null\n }\n }\n\n const out: string[] = []\n out.push(rowHeaderSlice(newHtml, newRow))\n out.push(emitSpanChangedCell(newHtml, cell, 'rowspan'))\n out.push('</tr>')\n for (let k = 1; k < span; k++) {\n out.push(emitEmptyRow(newHtml, newTable.rows[r + k]))\n }\n return { diff: out.join(''), span }\n}\n\n/**\n * Detects a vertical split starting at row `r`: old row R has a single\n * cell with rowspan=K, old rows R+1..R+K-1 are empty. New rows R..R+K-1\n * each have a single cell. Output: emit each new row with the new cell\n * tagged `class='mod rowspan'`.\n */\nfunction detectVerticalSplit(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n r: number\n): { diff: string; span: number } | null {\n const oldRow = oldTable.rows[r]\n if (oldRow.cells.length !== 1) return null\n const oldCell = oldRow.cells[0]\n const span = getRowspan(oldHtml, oldCell)\n if (span <= 1) return null\n if (r + span > oldTable.rows.length) return null\n\n const colspan = getColspan(oldHtml, oldCell)\n\n for (let k = 1; k < span; k++) {\n if (oldTable.rows[r + k].cells.length !== 0) return null\n }\n for (let k = 0; k < span; k++) {\n const newRow = newTable.rows[r + k]\n if (!newRow) return null\n // New rows must collectively cover the same logical width as the old\n // merged cell's colspan, with no rowspan'd cells of their own.\n if (sumColspans(newHtml, newRow.cells) !== colspan) return null\n for (const c of newRow.cells) {\n if (getRowspan(newHtml, c) !== 1) return null\n }\n }\n\n const out: string[] = []\n for (let k = 0; k < span; k++) {\n const newRow = newTable.rows[r + k]\n out.push(rowHeaderSlice(newHtml, newRow))\n for (const c of newRow.cells) {\n out.push(emitSpanChangedCell(newHtml, c, 'rowspan'))\n }\n out.push('</tr>')\n }\n return { diff: out.join(''), span }\n}\n\nfunction emitEmptyRow(html: string, row: RowRange): string {\n // Re-emit the source row's `<tr ...></tr>` verbatim.\n return html.slice(row.rowStart, row.rowEnd)\n}\n\nexport function sameDimensions(a: TableRange, b: TableRange): boolean {\n if (a.rows.length !== b.rows.length) return false\n for (let i = 0; i < a.rows.length; i++) {\n if (a.rows[i].cells.length !== b.rows[i].cells.length) return false\n }\n return true\n}\n\n/**\n * Same-dimension path: walk the new table verbatim and substitute each\n * cell content range with the cell-level diff. The surrounding\n * `<thead>`/`<tbody>`/whitespace passes through untouched.\n */\nfunction diffPositionalTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n const out: string[] = []\n let cursor = newTable.tableStart\n for (let r = 0; r < newTable.rows.length; r++) {\n const oldRow = oldTable.rows[r]\n const newRow = newTable.rows[r]\n for (let c = 0; c < newRow.cells.length; c++) {\n const oldCell = oldRow.cells[c]\n const newCell = newRow.cells[c]\n out.push(newHtml.slice(cursor, newCell.contentStart))\n out.push(\n diffCell(\n oldHtml.slice(oldCell.contentStart, oldCell.contentEnd),\n newHtml.slice(newCell.contentStart, newCell.contentEnd)\n )\n )\n cursor = newCell.contentEnd\n }\n }\n out.push(newHtml.slice(cursor, newTable.tableEnd))\n return out.join('')\n}\n\n/**\n * Mismatched-dimensions path: row-level LCS to identify added/deleted rows,\n * then per preserved row a cell-level LCS to identify added/deleted cells.\n * Reconstructs the table from scratch — there's no \"single new structure\"\n * to walk verbatim, since we're stitching together kept rows from both\n * sides.\n */\nfunction diffStructurallyAlignedTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n diffCell: DiffCellFn\n): string {\n const oldKeys = oldTable.rows.map(row => rowKey(oldHtml, row))\n const newKeys = newTable.rows.map(row => rowKey(newHtml, row))\n const exactAlignment = lcsAlign(oldKeys, newKeys)\n const paired = pairSimilarUnmatchedRows(exactAlignment, oldTable, newTable, oldHtml, newHtml)\n // Reorder so unpaired deleted rows appear at their *natural old-side\n // position* — immediately after the preserved/paired row that came\n // before them in old. Without this, runs of unpaired dels at low\n // alignment indices end up emitted before any preserved row (the\n // \"deleted rows out of order\" bug).\n const alignment = orderAlignmentForEmission(paired)\n\n // Walk new's tableStart→tableEnd, substituting rows with their diffed\n // form so `<thead>`/`<tbody>` wrappers and inter-row whitespace are\n // preserved verbatim. Deleted rows (no position in new) are injected\n // inline at the cursor's current position, which now corresponds to\n // their natural old-side slot thanks to the reordering above. If new\n // has no rows at all, fall back to a from-scratch reconstruction so\n // we still emit deleted rows.\n if (newTable.rows.length === 0) {\n return rebuildStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, alignment)\n }\n\n const out: string[] = []\n out.push(newHtml.slice(newTable.tableStart, newTable.rows[0].rowStart))\n let cursor = newTable.rows[0].rowStart\n for (const align of alignment) {\n if (align.newIdx !== null) {\n const newRow = newTable.rows[align.newIdx]\n out.push(newHtml.slice(cursor, newRow.rowStart))\n if (align.oldIdx !== null) {\n out.push(diffPreservedRow(oldHtml, newHtml, oldTable.rows[align.oldIdx], newRow, diffCell))\n } else {\n out.push(emitFullRow(newHtml, newRow, 'ins'))\n }\n cursor = newRow.rowEnd\n } else if (align.oldIdx !== null) {\n out.push(emitFullRow(oldHtml, oldTable.rows[align.oldIdx], 'del'))\n }\n }\n out.push(newHtml.slice(cursor, newTable.tableEnd))\n return out.join('')\n}\n\nfunction rebuildStructurallyAlignedTable(\n oldHtml: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange,\n alignment: Alignment[]\n): string {\n // Used when new has no rows but old does — we lose the per-row\n // wrappers from new (there are none), so reconstruct from old's frame.\n const out: string[] = []\n out.push(headerSlice(newHtml, newTable, oldHtml, oldTable))\n for (const align of alignment) {\n if (align.oldIdx !== null) {\n out.push(emitFullRow(oldHtml, oldTable.rows[align.oldIdx], 'del'))\n } else if (align.newIdx !== null) {\n out.push(emitFullRow(newHtml, newTable.rows[align.newIdx], 'ins'))\n }\n }\n out.push('</table>')\n return out.join('')\n}\n\nfunction headerSlice(newHtml: string, newTable: TableRange, oldHtml: string, oldTable: TableRange): string {\n // Slice from <table> to the start of the first <tr>. Prefer new since\n // attribute changes on <table> itself should follow new.\n const newFirstRow = newTable.rows[0]?.rowStart ?? newTable.tableEnd - '</table>'.length\n if (newFirstRow > newTable.tableStart) {\n return newHtml.slice(newTable.tableStart, newFirstRow)\n }\n const oldFirstRow = oldTable.rows[0]?.rowStart ?? oldTable.tableEnd - '</table>'.length\n return oldHtml.slice(oldTable.tableStart, oldFirstRow)\n}\n\nexport function rowKey(html: string, row: RowRange): string {\n // Include cell tag text in the key so column-add doesn't accidentally\n // match a row to one with different cell counts. Whitespace-normalize to\n // tolerate formatting differences.\n return html.slice(row.rowStart, row.rowEnd).replace(/\\s+/g, ' ').trim()\n}\n\nfunction diffPreservedRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n if (oldRow.cells.length === newRow.cells.length) {\n return diffPositionalRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n }\n // Cell counts differ. Try to interpret it as a horizontal merge/split via\n // colspan first — preserving the new structure with `class='mod colspan'`\n // on each affected cell.\n const colspanAligned = diffColspanChangedRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n if (colspanAligned !== null) return colspanAligned\n // For column add/delete (cell counts differ), find the best insertion\n // or deletion positions via positional similarity scan and align the\n // remaining cells positionally. This handles content-edit alongside\n // column-add by keeping the edited cell in its column position rather\n // than orphaning it via the cell-LCS exact match.\n // Guardrail: O(M × N) DP scales fine within MAX_COLUMN_SEARCH_WIDTH;\n // wider rows fall through to cell-LCS so we don't run the per-row DP\n // on multi-thousand-cell exotica. MAX_COLUMN_DELTA stays as a\n // semantic guard — a delta > 6 usually means \"row rewrite\", not\n // \"column added\", and is better handled by cell-LCS.\n const delta = newRow.cells.length - oldRow.cells.length\n const absDelta = Math.abs(delta)\n if (\n absDelta > 0 &&\n absDelta <= MAX_COLUMN_DELTA &&\n Math.max(oldRow.cells.length, newRow.cells.length) <= MAX_COLUMN_SEARCH_WIDTH\n ) {\n if (delta > 0) return diffMultiColumnAddRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n return diffMultiColumnDeleteRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n }\n return diffStructurallyAlignedRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n}\n\n/**\n * For a row where new has more cells than old, find the column positions\n * in new where cells were inserted by running a monotonic-alignment DP\n * over the cell texts: pick the skip positions that maximise the sum-of-\n * similarities of the unskipped new cells aligned positionally against\n * the old cells. The inserted cells are emitted with diff markers; the\n * rest are aligned positionally with content diff for matched pairs.\n */\nfunction diffMultiColumnAddRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const insertedPositions = findBestColumnInsertPositions(oldRow, newRow, oldHtml, newHtml)\n const inserted = new Set(insertedPositions)\n const out: string[] = [rowHeaderSlice(newHtml, newRow)]\n let oldIdx = 0\n for (let c = 0; c < newRow.cells.length; c++) {\n if (inserted.has(c)) {\n out.push(emitFullCell(newHtml, newRow.cells[c], 'ins'))\n } else {\n out.push(emitDiffedCell(oldHtml, newHtml, oldRow.cells[oldIdx], newRow.cells[c], diffCell))\n oldIdx++\n }\n }\n out.push('</tr>')\n return out.join('')\n}\n\nfunction diffMultiColumnDeleteRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const deletedPositions = findBestColumnDeletePositions(oldRow, newRow, oldHtml, newHtml)\n const deleted = new Set(deletedPositions)\n const out: string[] = [rowHeaderSlice(newHtml, newRow)]\n let newIdx = 0\n for (let oldIdx = 0; oldIdx < oldRow.cells.length; oldIdx++) {\n if (deleted.has(oldIdx)) {\n out.push(emitFullCell(oldHtml, oldRow.cells[oldIdx], 'del'))\n continue\n }\n out.push(emitDiffedCell(oldHtml, newHtml, oldRow.cells[oldIdx], newRow.cells[newIdx], diffCell))\n newIdx++\n }\n out.push('</tr>')\n return out.join('')\n}\n\nfunction findBestColumnInsertPositions(oldRow: RowRange, newRow: RowRange, oldHtml: string, newHtml: string): number[] {\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n return findOptimalAlignmentSkips(oldTexts, newTexts, (oldIdx, newIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\nfunction findBestColumnDeletePositions(oldRow: RowRange, newRow: RowRange, oldHtml: string, newHtml: string): number[] {\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n return findOptimalAlignmentSkips(newTexts, oldTexts, (newIdx, oldIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\n/**\n * Try to align cells by logical column position (sum of colspans). When\n * one side has a colspan'd cell that absorbs multiple cells on the other\n * side, emit the new structure with `class='mod colspan'` on the\n * merged/split cells. Returns null if the rows don't align cleanly —\n * caller falls back to a generic cell-LCS.\n */\nfunction diffColspanChangedRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string | null {\n const oldWidth = sumColspans(oldHtml, oldRow.cells)\n const newWidth = sumColspans(newHtml, newRow.cells)\n if (oldWidth !== newWidth) return null\n\n const out: string[] = []\n out.push(rowHeaderSlice(newHtml, newRow))\n\n let oi = 0\n let ni = 0\n while (oi < oldRow.cells.length && ni < newRow.cells.length) {\n const oCell = oldRow.cells[oi]\n const nCell = newRow.cells[ni]\n const oSpan = getColspan(oldHtml, oCell)\n const nSpan = getColspan(newHtml, nCell)\n\n if (oSpan === nSpan) {\n out.push(emitDiffedCell(oldHtml, newHtml, oCell, nCell, diffCell))\n oi++\n ni++\n } else if (nSpan > oSpan) {\n // New cell absorbs multiple old cells — horizontal merge.\n let totalOldSpan = 0\n let oj = oi\n while (oj < oldRow.cells.length && totalOldSpan < nSpan) {\n totalOldSpan += getColspan(oldHtml, oldRow.cells[oj])\n oj++\n }\n if (totalOldSpan !== nSpan) return null\n out.push(emitSpanChangedCell(newHtml, nCell, 'colspan'))\n oi = oj\n ni++\n } else {\n // One old cell becomes multiple new cells — horizontal split.\n let totalNewSpan = 0\n let nj = ni\n while (nj < newRow.cells.length && totalNewSpan < oSpan) {\n totalNewSpan += getColspan(newHtml, newRow.cells[nj])\n nj++\n }\n if (totalNewSpan !== oSpan) return null\n for (let k = ni; k < nj; k++) {\n out.push(emitSpanChangedCell(newHtml, newRow.cells[k], 'colspan'))\n }\n oi++\n ni = nj\n }\n }\n\n // If we couldn't consume both sides cleanly, bail out.\n if (oi !== oldRow.cells.length || ni !== newRow.cells.length) return null\n\n out.push('</tr>')\n return out.join('')\n}\n\nfunction sumColspans(html: string, cells: CellRange[]): number {\n let total = 0\n for (const cell of cells) total += getColspan(html, cell)\n return total\n}\n\nfunction getColspan(html: string, cell: CellRange): number {\n return parseSpanAttribute(html.slice(cell.cellStart, cell.contentStart), 'colspan')\n}\n\nfunction getRowspan(html: string, cell: CellRange): number {\n return parseSpanAttribute(html.slice(cell.cellStart, cell.contentStart), 'rowspan')\n}\n\nfunction parseSpanAttribute(openingTag: string, name: 'colspan' | 'rowspan'): number {\n const re = name === 'colspan' ? /\\bcolspan\\s*=\\s*[\"']?(\\d+)[\"']?/i : /\\browspan\\s*=\\s*[\"']?(\\d+)[\"']?/i\n const m = re.exec(openingTag)\n if (!m) return 1\n const value = Number.parseInt(m[1], 10)\n return Number.isFinite(value) && value > 0 ? value : 1\n}\n\n/**\n * Emits a cell that's the merged/split product of a structural change,\n * tagged with `class='mod colspan'` or `class='mod rowspan'`. Content is\n * carried through unmodified — Word doesn't track these changes, and\n * inserting del/ins around content that didn't really change would be\n * misleading.\n */\nfunction emitSpanChangedCell(html: string, cell: CellRange, kind: 'colspan' | 'rowspan'): string {\n const tdOpening = parseOpeningTagAt(html, cell.cellStart)\n if (!tdOpening) return html.slice(cell.cellStart, cell.cellEnd)\n const tdOpenTag = injectClass(html.slice(cell.cellStart, tdOpening.end), `mod ${kind}`)\n return tdOpenTag + html.slice(cell.contentStart, cell.cellEnd)\n}\n\nfunction diffPositionalRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const out: string[] = []\n // Use new's <tr> opening tag (preserves attributes from new).\n const trHeader = rowHeaderSlice(newHtml, newRow)\n out.push(trHeader)\n\n let cursor = newRow.cells[0]?.cellStart ?? newRow.rowEnd\n for (let c = 0; c < newRow.cells.length; c++) {\n const oldCell = oldRow.cells[c]\n const newCell = newRow.cells[c]\n out.push(newHtml.slice(cursor, newCell.contentStart))\n out.push(\n diffCell(\n oldHtml.slice(oldCell.contentStart, oldCell.contentEnd),\n newHtml.slice(newCell.contentStart, newCell.contentEnd)\n )\n )\n cursor = newCell.contentEnd\n }\n out.push(newHtml.slice(cursor, newRow.rowEnd))\n return out.join('')\n}\n\nfunction diffStructurallyAlignedRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n diffCell: DiffCellFn\n): string {\n const oldKeys = oldRow.cells.map(cell => cellKey(oldHtml, cell))\n const newKeys = newRow.cells.map(cell => cellKey(newHtml, cell))\n const exactAlignment = lcsAlign(oldKeys, newKeys)\n // After exact LCS, fuzzy-pair adjacent unmatched old/new cells whose\n // content is similar enough — so a content-edit cell alongside a\n // column-add in the same row produces a content diff for the edited\n // cell rather than a phantom delete + insert + extra cell.\n const alignment = pairSimilarUnmatchedCells(exactAlignment, oldRow, newRow, oldHtml, newHtml)\n\n const out: string[] = []\n // Use new's <tr> if it exists; otherwise old's.\n out.push(rowHeaderSlice(newHtml, newRow))\n\n for (const align of alignment) {\n if (align.oldIdx !== null && align.newIdx !== null) {\n const oldCell = oldRow.cells[align.oldIdx]\n const newCell = newRow.cells[align.newIdx]\n out.push(emitDiffedCell(oldHtml, newHtml, oldCell, newCell, diffCell))\n } else if (align.newIdx !== null) {\n out.push(emitFullCell(newHtml, newRow.cells[align.newIdx], 'ins'))\n } else if (align.oldIdx !== null) {\n out.push(emitFullCell(oldHtml, oldRow.cells[align.oldIdx], 'del'))\n }\n }\n\n out.push('</tr>')\n return out.join('')\n}\n\nfunction cellKey(html: string, cell: CellRange): string {\n // Use cell content (not tag attributes) for matching, since column-add\n // typically changes content but not tag attributes — and matching purely\n // on attributes would mis-pair cells with the same content but different\n // styling.\n return html.slice(cell.contentStart, cell.contentEnd).replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * Emits a row with all cells either inserted (kind='ins') or deleted\n * (kind='del'). Adds `class='diffins'`/`'diffdel'` to the `<tr>` and to\n * each `<td>`, with an `<ins>`/`<del>` wrapper around any cell content\n * (empty cells get the class but no wrapper).\n */\nfunction emitFullRow(html: string, row: RowRange, kind: 'ins' | 'del'): string {\n const cls = kind === 'ins' ? 'diffins' : 'diffdel'\n const trOpening = parseOpeningTagAt(html, row.rowStart)\n if (!trOpening) return html.slice(row.rowStart, row.rowEnd)\n const trOpenTag = injectClass(html.slice(row.rowStart, trOpening.end), cls)\n\n const out: string[] = [trOpenTag]\n let cursor = trOpening.end\n for (const cell of row.cells) {\n out.push(html.slice(cursor, cell.cellStart))\n out.push(emitFullCell(html, cell, kind))\n cursor = cell.cellEnd\n }\n out.push(html.slice(cursor, row.rowEnd))\n return out.join('')\n}\n\n/**\n * Emits a fully-inserted or fully-deleted cell. Inner text runs are wrapped\n * with `<ins>`/`<del>` while formatting tags pass through unchanged, so\n * `<strong>B</strong>` renders as `<strong><ins>B</ins></strong>` —\n * matching htmldiff's general convention without the doubled-`<ins>` that\n * the full recursive diff would produce for newly-inserted formatting.\n * Empty cells get the class on the `<td>` but no inner wrapping.\n */\nfunction emitFullCell(html: string, cell: CellRange, kind: 'ins' | 'del'): string {\n const cls = kind === 'ins' ? 'diffins' : 'diffdel'\n const tdOpening = parseOpeningTagAt(html, cell.cellStart)\n if (!tdOpening) return html.slice(cell.cellStart, cell.cellEnd)\n const tdOpenTag = injectClass(html.slice(cell.cellStart, tdOpening.end), cls)\n\n const content = html.slice(cell.contentStart, cell.contentEnd)\n const wrapped = content.trim().length === 0 ? content : wrapInlineTextRuns(content, kind)\n const closing = html.slice(cell.contentEnd, cell.cellEnd)\n return tdOpenTag + wrapped + closing\n}\n\n/**\n * Wraps every non-whitespace text run in the given content with an\n * `<ins>`/`<del>` tag, leaving HTML tags untouched. This produces output\n * like `<strong><ins>X</ins></strong>` for fully-inserted formatted\n * content — the same shape the rest of htmldiff emits for content\n * insertions inside existing formatting.\n */\nfunction wrapInlineTextRuns(content: string, kind: 'ins' | 'del'): string {\n const tag = kind === 'ins' ? 'ins' : 'del'\n const cls = kind === 'ins' ? 'diffins' : 'diffdel'\n\n const out: string[] = []\n let i = 0\n while (i < content.length) {\n if (content[i] === '<') {\n const tagEnd = parseOpeningTagAt(content, i)\n if (!tagEnd) {\n // Malformed — pass the rest through verbatim.\n out.push(content.slice(i))\n break\n }\n out.push(content.slice(i, tagEnd.end))\n i = tagEnd.end\n continue\n }\n let j = i\n while (j < content.length && content[j] !== '<') j++\n const text = content.slice(i, j)\n if (text.trim().length > 0) {\n out.push(wrapText(text, tag, cls))\n } else {\n out.push(text)\n }\n i = j\n }\n return out.join('')\n}\n\nfunction emitDiffedCell(\n oldHtml: string,\n newHtml: string,\n oldCell: CellRange,\n newCell: CellRange,\n diffCell: DiffCellFn\n): string {\n const tdOpening = parseOpeningTagAt(newHtml, newCell.cellStart)\n if (!tdOpening) return newHtml.slice(newCell.cellStart, newCell.cellEnd)\n const tdOpenTag = newHtml.slice(newCell.cellStart, tdOpening.end)\n const content = diffCell(\n oldHtml.slice(oldCell.contentStart, oldCell.contentEnd),\n newHtml.slice(newCell.contentStart, newCell.contentEnd)\n )\n const closing = newHtml.slice(newCell.contentEnd, newCell.cellEnd)\n return tdOpenTag + content + closing\n}\n\nfunction rowHeaderSlice(html: string, row: RowRange): string {\n // Slice from <tr> to just before the first <td> opening tag. Preserves\n // the <tr ...> attributes plus any inter-tag whitespace. For a row with\n // no cells, we only want the `<tr ...>` opening — the caller appends the\n // closing `</tr>` explicitly, so taking the whole `<tr></tr>` here would\n // double the close.\n const opening = parseOpeningTagAt(html, row.rowStart)\n if (!opening) return ''\n if (row.cells.length === 0) return html.slice(row.rowStart, opening.end)\n return html.slice(row.rowStart, row.cells[0].cellStart)\n}\n\n/** Character-level similarity threshold above which we treat two rows as \"the same row, edited\". */\nconst ROW_FUZZY_THRESHOLD = 0.5\n\n/**\n * Threshold for \"this cell is a content-edit of that cell.\" Tuned the same\n * as ROW_FUZZY_THRESHOLD; cells in legal docs that share most of their\n * content typically ARE the same logical cell with a body edit, so 0.5\n * works for both granularities in practice.\n */\nconst CELL_FUZZY_THRESHOLD = 0.5\n\n/**\n * After exact LCS, scan the alignment for runs of \"old deleted, then new\n * inserted\" (or vice versa) and pair entries whose content is similar\n * enough to be treated as an edit rather than a delete+insert. This keeps\n * row-level edits (a typo fix, a single word change) from being shown as\n * an entire row vanishing and a new one appearing — matching what users\n * expect from a typical track-changes view.\n */\nfunction pairSimilarUnmatchedRows(\n alignment: Alignment[],\n oldTable: TableRange,\n newTable: TableRange,\n oldHtml: string,\n newHtml: string\n): Alignment[] {\n // Pre-compute row texts once; the similarity callback is invoked\n // O(D × I) times per unmatched run (every del × every ins), and\n // rowText walks every cell.\n const oldTexts = oldTable.rows.map(r => rowText(oldHtml, r))\n const newTexts = newTable.rows.map(r => rowText(newHtml, r))\n return pairSimilarUnmatched(alignment, ROW_FUZZY_THRESHOLD, (oldIdx, newIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\nfunction pairSimilarUnmatchedCells(\n alignment: Alignment[],\n oldRow: RowRange,\n newRow: RowRange,\n oldHtml: string,\n newHtml: string\n): Alignment[] {\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n return pairSimilarUnmatched(alignment, CELL_FUZZY_THRESHOLD, (oldIdx, newIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\nexport function rowText(html: string, row: RowRange): string {\n const parts: string[] = []\n for (const cell of row.cells) {\n parts.push(html.slice(cell.contentStart, cell.contentEnd).replace(/<[^>]+>/g, ' '))\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim().toLowerCase()\n}\n\nfunction cellText(html: string, cell: CellRange): string {\n return html\n .slice(cell.contentStart, cell.contentEnd)\n .replace(/<[^>]+>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .toLowerCase()\n}\n\n/**\n * Walks html and returns ranges for every top-level `<table>...</table>`\n * block. Nested tables aren't extracted as separate top-level entries —\n * they're captured inside the parent's content range and handled when the\n * cell-level diff recurses through them.\n */\nexport function findTopLevelTables(html: string): TableRange[] {\n const tables: TableRange[] = []\n let i = 0\n while (i < html.length) {\n if (matchesTagAt(html, i, 'table')) {\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const tableContentStart = opening.end\n const tableEnd = findMatchingClosingTag(html, tableContentStart, 'table')\n if (tableEnd === -1) {\n i = opening.end\n continue\n }\n const closingTagStart = tableEnd - '</table>'.length\n const rows = findTopLevelRows(html, tableContentStart, closingTagStart)\n tables.push({ tableStart: i, tableEnd, rows })\n i = tableEnd\n } else {\n i++\n }\n }\n return tables\n}\n\nfunction findTopLevelRows(html: string, start: number, end: number): RowRange[] {\n const rows: RowRange[] = []\n let i = start\n while (i < end) {\n if (matchesTagAt(html, i, 'tr')) {\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const rowContentStart = opening.end\n const rowEnd = findMatchingClosingTag(html, rowContentStart, 'tr', end)\n if (rowEnd === -1) {\n i = opening.end\n continue\n }\n const closingTagStart = rowEnd - '</tr>'.length\n const cells = findTopLevelCells(html, rowContentStart, closingTagStart)\n rows.push({ rowStart: i, rowEnd, cells })\n i = rowEnd\n } else if (matchesClosingTagAt(html, i, 'table')) {\n // Defensive: bail out if we encounter a closing </table> while\n // scanning rows (we should have stopped at `end` already).\n break\n } else {\n i++\n }\n }\n return rows\n}\n\nfunction findTopLevelCells(html: string, start: number, end: number): CellRange[] {\n const cells: CellRange[] = []\n let i = start\n while (i < end) {\n if (matchesTagAt(html, i, 'td') || matchesTagAt(html, i, 'th')) {\n const tagName = matchesTagAt(html, i, 'td') ? 'td' : 'th'\n const opening = parseOpeningTagAt(html, i)\n if (!opening) {\n i++\n continue\n }\n const contentStart = opening.end\n const cellEnd = findMatchingClosingTag(html, contentStart, tagName, end)\n if (cellEnd === -1) {\n i = opening.end\n continue\n }\n const contentEnd = cellEnd - `</${tagName}>`.length\n cells.push({ cellStart: i, cellEnd, contentStart, contentEnd })\n i = cellEnd\n } else if (matchesClosingTagAt(html, i, 'tr')) {\n break\n } else {\n i++\n }\n }\n return cells\n}\n","import Action from './Action'\nimport { lcsAlign } from './Alignment'\nimport type { AnalyzeResult } from './HtmlDiff'\nimport type Operation from './Operation'\nimport type { WrapMetadata } from './Utils'\n\n/**\n * Composes diff(genesis → cp-latest) (CP's accumulated changes from the\n * common ancestor) and diff(genesis → me-current) (Me's accumulated\n * changes from the common ancestor) into a single attributed segment\n * stream. The output is consumed by `HtmlDiff.executeThreeWay` for\n * emission.\n *\n * Genesis is the structural spine. Both pair-wise analyses must\n * tokenise genesis identically (`HtmlDiff.executeThreeWay` enforces\n * this via the symmetric-projection decision), so genesis-diff indices\n * are stable across the two streams.\n *\n * Per genesis token: classify by what each side did to it\n * (kept / deleted) and emit accordingly. Per genesis boundary: collect\n * each side's insertions and check for agreement — when both sides\n * inserted identical content, the insertion is treated as \"settled\"\n * and emitted unmarked (the reader sees the agreed-on text without\n * authorship markup, matching Word-style track-changes conventions\n * where both authors agreeing is silent).\n *\n * The emission order at a boundary mirrors the 2-way del-then-ins\n * convention: a Replace (genesis token deleted + a paired insertion)\n * reads as `<del>old</del><ins>new</ins>`. Pure insertions are\n * positioned at their natural boundary.\n */\n\nexport type Author = 'cp' | 'me'\n\n/**\n * Attribution assigned to each output segment.\n *\n * `equal` covers three cases: tokens both authors kept (rendered as the\n * genesis word), insertion spans both authors made identically (rendered\n * plain), and structural tags around both-deleted tokens (rendered to\n * keep layout intact while the content token itself is dropped).\n * Equal segments carry no markup.\n */\nexport type Attribution = { kind: 'equal' } | { kind: 'ins'; author: Author } | { kind: 'del'; author: Author }\n\nexport interface Segment {\n attr: Attribution\n /** Tokens to emit. For Equal segments these are original genesis words\n * (including structural tags); for ins/del they are diff-space tokens. */\n words: string[]\n}\n\n/**\n * Builds the attributed segment stream for a three-way diff.\n *\n * @param dCp analysis of diff(genesis → cp-latest)\n * @param dMe analysis of diff(genesis → me-current)\n *\n * Both analyses must share the same `oldDiffWords` (the genesis tokens)\n * — the caller guarantees this by passing the same genesis input and\n * the same `useProjections` decision to both `HtmlDiff.analyze` calls.\n */\nexport function buildSegments(dCp: AnalyzeResult, dMe: AnalyzeResult): Segment[] {\n const genesisLen = dCp.oldDiffWords.length\n\n // Per genesis token: did each author keep it or delete it?\n const cpFate = buildFateFromGenesis(dCp.operations, genesisLen)\n const meFate = buildFateFromGenesis(dMe.operations, genesisLen)\n\n // Per boundary: tokens each author inserted at that boundary. Keyed by\n // `endInOld` so a Replace's insertion sits AFTER the deleted genesis\n // token (visual del-then-ins). Pure Insert ops have endInOld ==\n // startInOld so they land at their natural between-tokens boundary.\n const cpInsAt = collectInsertionsKeyedByEnd(dCp)\n const meInsAt = collectInsertionsKeyedByEnd(dMe)\n\n // Inverse map genesis-diff-index → genesis-original-index. Identity when\n // no projection. Used to slice the original genesis words for Equal\n // segments so structural tags pass through verbatim.\n const diffToOriginal: readonly number[] = dCp.oldContentToOriginal ?? Array.from({ length: genesisLen }, (_, i) => i)\n const genesisOriginalLen = dCp.oldOriginalWords.length\n\n const segments: Segment[] = []\n let originalCursor = 0\n\n // Boundary 0 — pure insertions BEFORE genesis[0].\n emitBoundary(0, cpInsAt, meInsAt, dCp.newDiffWords, dMe.newDiffWords, segments)\n\n for (let i = 0; i < genesisLen; i++) {\n const cpDel = cpFate[i] === 'deleted'\n const meDel = meFate[i] === 'deleted'\n\n // Pick up structural tags from cursor through to this genesis token's\n // original index. Same cursor-based slicing as the 2-way path so a\n // `<p>` opening tag preceding a content token gets attributed with\n // that token's segment.\n const origIdx = diffToOriginal[i]\n const slice = dCp.oldOriginalWords.slice(originalCursor, origIdx + 1)\n originalCursor = origIdx + 1\n\n if (!cpDel && !meDel) {\n // Kept by both — equal. Emit the original-word slice (includes\n // any leading structural tags).\n appendSegment(segments, { kind: 'equal' }, slice)\n } else if (cpDel && meDel) {\n // Both deleted — settled. Filter at emission time; pass the\n // structural-tag-bearing slice through as equal so layout\n // survives. The content token itself is the LAST element of the\n // slice (since slice ends at origIdx+1); drop only that.\n // If slice has multiple elements (leading structural tags), they\n // belong to the surrounding flow and should remain.\n if (slice.length > 1) {\n appendSegment(segments, { kind: 'equal' }, slice.slice(0, slice.length - 1))\n }\n // The content token itself is silenced.\n } else if (cpDel) {\n // CP deleted, Me kept → render as <del cp>. Me's keeping means the\n // token is still in V_me; the markup tells the reader \"CP wanted\n // this gone, you've kept it.\"\n appendSegment(segments, { kind: 'del', author: 'cp' }, slice)\n } else {\n // Me deleted, CP kept → render as <del me>.\n appendSegment(segments, { kind: 'del', author: 'me' }, slice)\n }\n\n // Boundary i+1 — pure insertions between genesis[i] and genesis[i+1],\n // AND replace-insertions paired with genesis[i] (which we just\n // emitted as a deletion).\n emitBoundary(i + 1, cpInsAt, meInsAt, dCp.newDiffWords, dMe.newDiffWords, segments)\n }\n\n // Trailing original tokens (structural closing tags after the last\n // content word).\n if (originalCursor < genesisOriginalLen) {\n appendSegment(segments, { kind: 'equal' }, dCp.oldOriginalWords.slice(originalCursor))\n }\n\n return segments\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n\ntype GenesisFate = 'kept' | 'deleted'\n\n/**\n * Per genesis-diff-index, what did this side do to that token? Both\n * Delete and Replace ops remove the token from the side's output, so\n * both contribute `'deleted'`. Equal ops contribute `'kept'`. Insert\n * ops have an empty old range, so they don't touch the genesis fate\n * map.\n */\nfunction buildFateFromGenesis(ops: readonly Operation[], genesisLen: number): GenesisFate[] {\n const out: GenesisFate[] = new Array(genesisLen).fill('kept')\n for (const op of ops) {\n if (op.action !== Action.Delete && op.action !== Action.Replace) continue\n for (let i = op.startInOld; i < op.endInOld; i++) {\n if (i >= 0 && i < genesisLen) out[i] = 'deleted'\n }\n }\n return out\n}\n\n/**\n * Per genesis boundary `b`, collect tokens this side inserted at that\n * boundary. Keyed by `endInOld` so a Replace at genesis[k..k+1] has its\n * insertion at boundary k+1 (after the deleted token) rather than k\n * (before) — that produces the del-then-ins visual order.\n *\n * For pure Insert ops the old range is empty (endInOld == startInOld),\n * so the key is the same as the semantic between-tokens position.\n */\nfunction collectInsertionsKeyedByEnd(d: AnalyzeResult): Map<number, string[]> {\n const out = new Map<number, string[]>()\n for (const op of d.operations) {\n if (op.action !== Action.Insert && op.action !== Action.Replace) continue\n const words = d.newDiffWords.slice(op.startInNew, op.endInNew)\n if (words.length === 0) continue\n const key = op.endInOld\n const existing = out.get(key) ?? []\n existing.push(...words)\n out.set(key, existing)\n }\n return out\n}\n\n/**\n * Emit any insertions at boundary `b`.\n *\n * Reading model: a legal reviewer wants to see CP's INTENT relative\n * to Me's current content. Me's content is the base; CP's deltas are\n * what they need to act on. Under that framing:\n * - tokens both authors inserted at the same boundary → settled\n * - tokens CP inserted that Me doesn't have → ins-cp (CP wants\n * this added)\n * - tokens Me inserted that CP doesn't have → del-cp (CP wants\n * this removed from Me's content)\n *\n * The third case is the load-bearing attribution flip. The\n * genesis-spine view technically labels me-only-at-boundary tokens\n * as \"ins-me\" (Me added them; CP didn't), but that's confusing to\n * a reviewer: they see \"Me added X\" alongside \"CP added Y\" and have\n * to mentally derive \"CP wants X gone, replaced with Y\". Surfacing\n * me-only tokens as `del-cp` shows CP's intent directly:\n * - \"CP accepted Me's text minus `things`\": settled bulk + del-cp\n * `things` (no parallel redundant insertions)\n * - \"CP wants `cruel` where Me wrote `brave`\": ins-cp `cruel` +\n * del-cp `brave` (the substitution intent reads directly)\n * - \"CP added extra words\": cp-extras stay as ins-cp (same as\n * before; the cp-only direction was always intent-correct)\n *\n * Pure single-side insertions (Me added text CP doesn't engage\n * with at all, or vice versa) keep their genesis-spine attribution\n * — these aren't refinement cases, just Me's own content additions.\n */\nfunction emitBoundary(\n b: number,\n cpInsAt: Map<number, string[]>,\n meInsAt: Map<number, string[]>,\n _cpDiffWords: readonly string[],\n _meDiffWords: readonly string[],\n segments: Segment[]\n) {\n const cpIns = cpInsAt.get(b)\n const meIns = meInsAt.get(b)\n const hasCp = !!cpIns && cpIns.length > 0\n const hasMe = !!meIns && meIns.length > 0\n if (!hasCp && !hasMe) return\n\n // Only-one-side: emit verbatim with that side's attribution.\n // Genuine single-author additions stay author-attributed.\n if (!hasCp) {\n appendSegment(segments, { kind: 'ins', author: 'me' }, meIns!)\n return\n }\n if (!hasMe) {\n appendSegment(segments, { kind: 'ins', author: 'cp' }, cpIns!)\n return\n }\n\n // Both sides inserted. Identical → settled. Otherwise LCS-align\n // and apply the asymmetric intent reading.\n if (tokenArraysEqual(cpIns!, meIns!)) {\n appendSegment(segments, { kind: 'equal' }, cpIns!)\n return\n }\n\n const alignment = lcsAlign(cpIns! as string[], meIns! as string[])\n for (const a of alignment) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n // Token appears in both insertions → settled.\n appendSegment(segments, { kind: 'equal' }, [cpIns![a.oldIdx]])\n } else if (a.oldIdx !== null) {\n // Token in cp's insertion only → CP wants this added.\n appendSegment(segments, { kind: 'ins', author: 'cp' }, [cpIns![a.oldIdx]])\n } else if (a.newIdx !== null) {\n // Token in me's insertion only → CP wants this removed from\n // Me's content. (Genesis-spine would label this ins-me, but\n // that reading is misleading for a reviewer at this kind of\n // shared boundary — see the function-level comment.)\n appendSegment(segments, { kind: 'del', author: 'cp' }, [meIns![a.newIdx]])\n }\n }\n}\n\nfunction tokenArraysEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false\n return true\n}\n\nfunction appendSegment(segments: Segment[], attr: Attribution, words: readonly string[]) {\n if (words.length === 0) return\n const last = segments[segments.length - 1]\n if (last && sameAttribution(last.attr, attr)) {\n last.words.push(...words)\n return\n }\n segments.push({ attr, words: [...words] })\n}\n\nfunction sameAttribution(a: Attribution, b: Attribution): boolean {\n if (a.kind === 'equal' && b.kind === 'equal') return true\n if (a.kind === 'ins' && b.kind === 'ins') return a.author === b.author\n if (a.kind === 'del' && b.kind === 'del') return a.author === b.author\n return false\n}\n\n/**\n * Build the `WrapMetadata` for an attribution. Single source of truth\n * for author-class / data-attr shape so the three emission paths\n * (word-level, table-level full-row/cell, multi-table whole-table\n * pre-wrap) stay consistent. A change here propagates to every author\n * marker in the output.\n */\nexport function authorAttribution(author: Author): WrapMetadata {\n return { extraClasses: author, dataAttrs: { author } }\n}\n\n/**\n * Resolve a segment's attribution into the wrapper-tag, base CSS class,\n * and `WrapMetadata` consumed by `Utils.wrapText` / `insertTag`. The\n * caller is `HtmlDiff.executeThreeWay`'s emission loop.\n *\n * `equal` segments don't go through this — they're emitted unmarked.\n */\nexport function segmentEmissionShape(attr: Exclude<Attribution, { kind: 'equal' }>): {\n tag: 'ins' | 'del'\n baseClass: 'diffins' | 'diffdel'\n metadata: WrapMetadata\n} {\n return {\n tag: attr.kind,\n baseClass: attr.kind === 'ins' ? 'diffins' : 'diffdel',\n metadata: authorAttribution(attr.author),\n }\n}\n","import { type Alignment, lcsAlign, pairSimilarUnmatched, textSimilarity } from './Alignment'\nimport { injectClass, parseOpeningTagAt } from './HtmlScanner'\nimport {\n type CellRange,\n exceedsSizeLimit,\n findTopLevelTables,\n makePlaceholderPrefix,\n PLACEHOLDER_SUFFIX,\n type RowRange,\n rowKey,\n rowText,\n sameDimensions,\n spliceString,\n type TableRange,\n} from './TableDiff'\nimport { type Author, authorAttribution } from './ThreeWayDiff'\nimport Utils from './Utils'\n\n/**\n * Three-way table preprocessing for the genesis-spine merge.\n *\n * Inputs: `genesis` (common ancestor), `cpLatest` (counterparty's\n * accumulated position), `meCurrent` (Me's accumulated position). All\n * three share a single placeholder nonce so genesis tokenises\n * identically across both pair-wise word-level analyses.\n *\n * Three paths:\n * 1. **Positional** — all three have the same table count AND each\n * positional triple's tableKey is similar enough that 1:1 pairing\n * by position is sound. Recurses cellDiff per cell, structural\n * layout from genesis.\n * 2. **Row-structural** — paired triples whose row/cell counts differ.\n * Per-table row-level LCS against genesis; recurse on preserved\n * rows, emit author-attributed full rows for the rest.\n * 3. **Multi-table by content** — table counts diverge across inputs.\n * Pair tables to genesis via content-LCS, then assign placeholders\n * such that each placeholder appears in exactly the inputs that\n * contain the underlying table. The word-level merger walks the\n * genesis spine and attributes unpaired tables naturally\n * (cp-only/me-only/both-agree).\n */\n\nexport interface ThreeWayPreprocessResult {\n modifiedGenesis: string\n modifiedCp: string\n modifiedMe: string\n placeholderToDiff: Map<string, string>\n}\n\nexport type ThreeWayDiffCellFn = (genesisCell: string, cpCell: string, meCell: string) => string\n\nexport function preprocessTablesThreeWay(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n cellDiff: ThreeWayDiffCellFn\n): ThreeWayPreprocessResult | null {\n const gTables = findTopLevelTables(genesis)\n const cTables = findTopLevelTables(cpLatest)\n const mTables = findTopLevelTables(meCurrent)\n\n if (gTables.length === 0 && cTables.length === 0 && mTables.length === 0) return null\n\n for (const t of gTables) if (exceedsSizeLimit(t)) return null\n for (const t of cTables) if (exceedsSizeLimit(t)) return null\n for (const t of mTables) if (exceedsSizeLimit(t)) return null\n\n const placeholderPrefix = makePlaceholderPrefix(genesis, cpLatest, meCurrent)\n\n if (positionallyAligned(genesis, cpLatest, meCurrent, gTables, cTables, mTables)) {\n return preprocessAlignedByPosition(\n genesis,\n cpLatest,\n meCurrent,\n gTables,\n cTables,\n mTables,\n cellDiff,\n placeholderPrefix\n )\n }\n\n return preprocessByContent(genesis, cpLatest, meCurrent, gTables, cTables, mTables, cellDiff, placeholderPrefix)\n}\n\nfunction preprocessAlignedByPosition(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n gTables: TableRange[],\n cTables: TableRange[],\n mTables: TableRange[],\n cellDiff: ThreeWayDiffCellFn,\n placeholderPrefix: string\n): ThreeWayPreprocessResult {\n const pairs: Array<{ g: TableRange; c: TableRange; m: TableRange; diffed: string }> = []\n for (let i = 0; i < gTables.length; i++) {\n pairs.push({\n g: gTables[i],\n c: cTables[i],\n m: mTables[i],\n diffed: diffTableThreeWay(genesis, cpLatest, meCurrent, gTables[i], cTables[i], mTables[i], cellDiff),\n })\n }\n let modifiedGenesis = genesis\n let modifiedCp = cpLatest\n let modifiedMe = meCurrent\n const placeholderToDiff = new Map<string, string>()\n for (let i = pairs.length - 1; i >= 0; i--) {\n const placeholder = `${placeholderPrefix}${i}${PLACEHOLDER_SUFFIX}`\n placeholderToDiff.set(placeholder, pairs[i].diffed)\n modifiedGenesis = spliceString(modifiedGenesis, pairs[i].g.tableStart, pairs[i].g.tableEnd, placeholder)\n modifiedCp = spliceString(modifiedCp, pairs[i].c.tableStart, pairs[i].c.tableEnd, placeholder)\n modifiedMe = spliceString(modifiedMe, pairs[i].m.tableStart, pairs[i].m.tableEnd, placeholder)\n }\n return { modifiedGenesis, modifiedCp, modifiedMe, placeholderToDiff }\n}\n\n/**\n * Multi-table handler. Tables are paired against `genesis` (the spine)\n * via content-LCS on each of cp and me. Placeholders are assigned so\n * each appears only in the inputs that actually contain the underlying\n * table. The word-level merger then attributes them naturally:\n *\n * - paired in genesis+cp+me → equal in both diffs → emit recursive 3-way diff\n * - in cp+me, not in genesis → both-agree insertion → emit plain\n * - in cp only → cp insertion → ins-cp wrapper (Me didn't take it)\n * - in me only → me insertion → ins-me wrapper\n * - in genesis+cp, not me → me deletion → del-me wrapper\n * - in genesis+me, not cp → cp deletion → del-cp wrapper\n * - in genesis only → both deleted, settled → silent (placeholder content empty)\n */\nfunction preprocessByContent(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n gTables: TableRange[],\n cTables: TableRange[],\n mTables: TableRange[],\n cellDiff: ThreeWayDiffCellFn,\n placeholderPrefix: string\n): ThreeWayPreprocessResult {\n const gKeys = gTables.map(t => tableKey(genesis, t))\n const cKeys = cTables.map(t => tableKey(cpLatest, t))\n const mKeys = mTables.map(t => tableKey(meCurrent, t))\n\n // Exact tableKey LCS, then fuzzy-pair unmatched runs by content\n // similarity. Without this, a table whose cells were edited (but\n // not its overall shape) fails the exact tableKey match and the\n // table-level aligner pulls it apart into a whole-table del + a\n // whole-table ins. Same fuzzy pass `TableDiff` uses for the 2-way\n // path — `pairSimilarTablesThreeWay` is defined below.\n const alignCp = pairSimilarTablesThreeWay(lcsAlign(gKeys, cKeys), genesis, cpLatest, gTables, cTables)\n const alignMe = pairSimilarTablesThreeWay(lcsAlign(gKeys, mKeys), genesis, meCurrent, gTables, mTables)\n\n // Maps: genesisIdx → matching cpIdx (-1 if none); cpIdx → matching genesisIdx; etc.\n const gToCp = new Array<number>(gTables.length).fill(-1)\n const cpToG = new Array<number>(cTables.length).fill(-1)\n for (const a of alignCp) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n gToCp[a.oldIdx] = a.newIdx\n cpToG[a.newIdx] = a.oldIdx\n }\n }\n const gToMe = new Array<number>(gTables.length).fill(-1)\n const meToG = new Array<number>(mTables.length).fill(-1)\n for (const a of alignMe) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n gToMe[a.oldIdx] = a.newIdx\n meToG[a.newIdx] = a.oldIdx\n }\n }\n\n let nextId = 0\n const placeholderToDiff = new Map<string, string>()\n const placeholders = {\n g: new Array<string | null>(gTables.length).fill(null),\n c: new Array<string | null>(cTables.length).fill(null),\n m: new Array<string | null>(mTables.length).fill(null),\n }\n const allocate = (): string => `${placeholderPrefix}${nextId++}${PLACEHOLDER_SUFFIX}`\n\n // For unpaired-in-one-side placeholders, bake author attribution\n // into the placeholder content — the word-level merger emits tag\n // tokens (HTML comments) verbatim, so it can't wrap them itself.\n const wrapWhole = (tag: 'ins' | 'del', author: Author, tableHtml: string): string =>\n Utils.wrapText(tableHtml, tag, `diff${tag}`, authorAttribution(author))\n\n // 1. Triples paired in all three (genesis + cp + me) → recursive 3-way diff.\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n const cIdx = gToCp[gIdx]\n const mIdx = gToMe[gIdx]\n if (cIdx === -1 || mIdx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n diffTableThreeWay(genesis, cpLatest, meCurrent, gTables[gIdx], cTables[cIdx], mTables[mIdx], cellDiff)\n )\n placeholders.g[gIdx] = placeholder\n placeholders.c[cIdx] = placeholder\n placeholders.m[mIdx] = placeholder\n }\n\n // 2. Genesis + CP only (not in Me) → me deletion.\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n if (placeholders.g[gIdx] !== null) continue\n const cIdx = gToCp[gIdx]\n if (cIdx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('del', 'me', genesis.slice(gTables[gIdx].tableStart, gTables[gIdx].tableEnd))\n )\n placeholders.g[gIdx] = placeholder\n placeholders.c[cIdx] = placeholder\n }\n\n // 3. Genesis + Me only (not in CP) → cp deletion.\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n if (placeholders.g[gIdx] !== null) continue\n const mIdx = gToMe[gIdx]\n if (mIdx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('del', 'cp', genesis.slice(gTables[gIdx].tableStart, gTables[gIdx].tableEnd))\n )\n placeholders.g[gIdx] = placeholder\n placeholders.m[mIdx] = placeholder\n }\n\n // 4. Genesis only (not in CP, not in Me) → both deleted, settled, silent.\n // Placeholder ONLY in genesis; cp and me lack it. The word-level merger\n // sees it as \"deleted by both\" via the genesis-spine fate maps and\n // silences it via the settled-deletion rule (empty placeholder content).\n for (let gIdx = 0; gIdx < gTables.length; gIdx++) {\n if (placeholders.g[gIdx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, '')\n placeholders.g[gIdx] = placeholder\n }\n\n // 5. CP + Me both inserted (no genesis) — agreement check. If their\n // table content is textually identical, emit plain (settled). Otherwise\n // each side gets its own placeholder (cp-only / me-only treatment).\n for (let cIdx = 0; cIdx < cTables.length; cIdx++) {\n if (placeholders.c[cIdx] !== null) continue\n // CP table not paired to genesis. Is there an unpaired Me table with\n // matching content?\n const cText = cKeys[cIdx]\n let mIdx = -1\n for (let candidate = 0; candidate < mTables.length; candidate++) {\n if (placeholders.m[candidate] !== null) continue\n if (meToG[candidate] !== -1) continue\n if (mKeys[candidate] === cText) {\n mIdx = candidate\n break\n }\n }\n if (mIdx === -1) continue\n // Both inserted the same table content → settled insertion.\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, cpLatest.slice(cTables[cIdx].tableStart, cTables[cIdx].tableEnd))\n placeholders.c[cIdx] = placeholder\n placeholders.m[mIdx] = placeholder\n }\n\n // 6. Remaining CP-only tables (inserted by CP, Me didn't take).\n for (let cIdx = 0; cIdx < cTables.length; cIdx++) {\n if (placeholders.c[cIdx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('ins', 'cp', cpLatest.slice(cTables[cIdx].tableStart, cTables[cIdx].tableEnd))\n )\n placeholders.c[cIdx] = placeholder\n }\n\n // 7. Remaining Me-only tables (Me inserted, CP didn't).\n for (let mIdx = 0; mIdx < mTables.length; mIdx++) {\n if (placeholders.m[mIdx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('ins', 'me', meCurrent.slice(mTables[mIdx].tableStart, mTables[mIdx].tableEnd))\n )\n placeholders.m[mIdx] = placeholder\n }\n\n // Splice end → start per input.\n let modifiedGenesis = genesis\n for (let i = gTables.length - 1; i >= 0; i--) {\n const p = placeholders.g[i]\n if (p === null) continue\n modifiedGenesis = spliceString(modifiedGenesis, gTables[i].tableStart, gTables[i].tableEnd, p)\n }\n let modifiedCp = cpLatest\n for (let i = cTables.length - 1; i >= 0; i--) {\n const p = placeholders.c[i]\n if (p === null) continue\n modifiedCp = spliceString(modifiedCp, cTables[i].tableStart, cTables[i].tableEnd, p)\n }\n let modifiedMe = meCurrent\n for (let i = mTables.length - 1; i >= 0; i--) {\n const p = placeholders.m[i]\n if (p === null) continue\n modifiedMe = spliceString(modifiedMe, mTables[i].tableStart, mTables[i].tableEnd, p)\n }\n\n return { modifiedGenesis, modifiedCp, modifiedMe, placeholderToDiff }\n}\n\n// Positional pairing is the strict-default for three-way table merge:\n// when all three inputs have the same number of tables in the same\n// order, we pair them by index and let `diffTableThreeWay` handle\n// per-table cell/row level differences. The similarity guard below\n// only kicks in to *reject* positional alignment when a pair is\n// SO dissimilar that it's near-certainly a table reorder/rename\n// where content-LCS pairing would be materially better. The\n// threshold is intentionally low — the 2-way path has no such guard\n// and pairs purely by index (its `diffTable` falls back through\n// same-dimension → equal-row-count → row-LCS → whole-table on its\n// own), so the three-way path was stricter than its sibling and\n// silently dropped to whole-table del+ins for legitimate edits\n// like \"rename one column and tweak its values\". Aligning the\n// threshold here keeps the two-way and three-way paths in step.\nconst POSITIONAL_PAIR_SIMILARITY_THRESHOLD = 0.15\n\nfunction positionallyAligned(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n gTables: TableRange[],\n cTables: TableRange[],\n mTables: TableRange[]\n): boolean {\n if (gTables.length !== cTables.length || cTables.length !== mTables.length) return false\n for (let i = 0; i < gTables.length; i++) {\n const kG = tableKey(genesis, gTables[i])\n const kC = tableKey(cpLatest, cTables[i])\n const kM = tableKey(meCurrent, mTables[i])\n if (textSimilarity(kG, kC) < POSITIONAL_PAIR_SIMILARITY_THRESHOLD) return false\n if (textSimilarity(kG, kM) < POSITIONAL_PAIR_SIMILARITY_THRESHOLD) return false\n }\n return true\n}\n\nfunction tableKey(html: string, table: TableRange): string {\n return html.slice(table.tableStart, table.tableEnd).replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * Character-level similarity above which the three-way aligner treats\n * two rows / tables as \"the same logical entry, edited\" rather than\n * an unrelated delete + insert. Matched to TableDiff's\n * `ROW_FUZZY_THRESHOLD` / `CELL_FUZZY_THRESHOLD` so 2-way and 3-way\n * agree on which pairings are reachable; if a row's content overlap\n * is enough to fool the 2-way diff into pairing, it should also be\n * enough for 3-way.\n */\nconst THREE_WAY_FUZZY_THRESHOLD = 0.5\n\n/**\n * Run the same fuzzy-pairing pass `TableDiff.pairSimilarUnmatchedRows`\n * applies after its exact-LCS, but against one side of the genesis\n * spine (either cp or me). The genesis tables/rows are always the\n * \"old\" side; `newTable` is the cp or me table being aligned. Returns\n * the enriched alignment with additional paired entries.\n *\n * Cell-count guard: only fuzzy-pair when both rows have the same cell\n * count. Without this guard an asymmetric restructure — e.g. CP and\n * Me both added a different column — leads to ONE side fuzzy-pairing\n * its row with genesis (content overlap above threshold) while the\n * other side falls below threshold. That mismatch routes through\n * `diffTableStructural`'s \"Me dropped, CP kept\" (or the mirror)\n * branch, which emits CP's row as a Me-attributed deletion. In\n * cp-only mode `stripMeAttributedMarkers` then removes the row\n * entirely and CP's edit vanishes from the view — exactly the\n * content-loss case we're meant to prevent. Restricting fuzzy\n * pairing to same-shape rows preserves the common case (single cell\n * edit, identical row shape) while pushing structural mismatches\n * back to the boundary-insertion path that emits both sides\n * explicitly.\n */\nfunction pairSimilarRowsThreeWay(\n alignment: Alignment[],\n genesis: string,\n newHtml: string,\n oldTable: TableRange,\n newTable: TableRange\n): Alignment[] {\n const oldTexts = oldTable.rows.map(r => rowText(genesis, r))\n const newTexts = newTable.rows.map(r => rowText(newHtml, r))\n return pairSimilarUnmatched(alignment, THREE_WAY_FUZZY_THRESHOLD, (oldIdx, newIdx) => {\n // Returning 0 sits below any positive threshold so\n // `pairSimilarUnmatched` won't pair these rows; the guard remains\n // defensive should the threshold ever be lowered to 0.\n if (oldTable.rows[oldIdx].cells.length !== newTable.rows[newIdx].cells.length) return 0\n return textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n })\n}\n\n/**\n * Table-level counterpart: after `lcsAlign(gKeys, otherKeys)` over\n * full table HTML keys, fuzzy-pair unmatched table runs by their\n * row-text-concatenated content. Without this, a table whose body\n * was edited (but not its outer shape) fails the exact-key match\n * and the preprocessing emits whole-table del + whole-table ins\n * instead of recursing into per-cell three-way diffs.\n */\nfunction pairSimilarTablesThreeWay(\n alignment: Alignment[],\n oldHtml: string,\n newHtml: string,\n oldTables: TableRange[],\n newTables: TableRange[]\n): Alignment[] {\n const oldTexts = oldTables.map(t => t.rows.map(r => rowText(oldHtml, r)).join(' '))\n const newTexts = newTables.map(t => t.rows.map(r => rowText(newHtml, r)).join(' '))\n return pairSimilarUnmatched(alignment, THREE_WAY_FUZZY_THRESHOLD, (oldIdx, newIdx) =>\n textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n )\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Per-table diff: positional cells or row-level structural change.\n\nfunction diffTableThreeWay(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n tG: TableRange,\n tC: TableRange,\n tM: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n if (sameDimensions(tG, tC) && sameDimensions(tC, tM)) {\n return diffTablePositional(genesis, cpLatest, meCurrent, tG, tC, tM, cellDiff)\n }\n return diffTableStructural(genesis, cpLatest, meCurrent, tG, tC, tM, cellDiff)\n}\n\nfunction diffTablePositional(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n tG: TableRange,\n tC: TableRange,\n tM: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n // Walk genesis's table scaffolding verbatim — it's the common\n // ancestor. Cells are merged 3-way via cellDiff. Choosing genesis as\n // the spine keeps the table structure stable across both pair-wise\n // diffs that the word-level merger will see.\n const out: string[] = []\n let cursor = tG.tableStart\n for (let r = 0; r < tG.rows.length; r++) {\n const rG = tG.rows[r]\n const rC = tC.rows[r]\n const rM = tM.rows[r]\n for (let c = 0; c < rG.cells.length; c++) {\n const cG = rG.cells[c]\n const cC = rC.cells[c]\n const cM = rM.cells[c]\n out.push(genesis.slice(cursor, cG.contentStart))\n out.push(\n cellDiff(\n genesis.slice(cG.contentStart, cG.contentEnd),\n cpLatest.slice(cC.contentStart, cC.contentEnd),\n meCurrent.slice(cM.contentStart, cM.contentEnd)\n )\n )\n cursor = cG.contentEnd\n }\n }\n out.push(genesis.slice(cursor, tG.tableEnd))\n return out.join('')\n}\n\n/**\n * Row-level genesis-spine merge for tables with diverging row/cell\n * counts.\n *\n * 1. Align cp rows to genesis rows (alignCp), me rows to genesis rows\n * (alignMe), each via row-LCS over rowKeys.\n * 2. Per genesis row: cpFate (kept / deleted), meFate (kept / deleted).\n * Both kept → recurse cell diff (with structural-change cell handling\n * falling back to me-attribution Replace per the documented\n * limitation). One kept, other deleted → emit author-attributed full\n * row. Both deleted → silent.\n * 3. Off-spine rows: cp-only inserted rows + me-only inserted rows.\n * Check for content agreement at the same boundary; agreed\n * insertions emit plain.\n */\nfunction diffTableStructural(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n tG: TableRange,\n tC: TableRange,\n tM: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n const gKeys = tG.rows.map(r => rowKey(genesis, r))\n const cKeys = tC.rows.map(r => rowKey(cpLatest, r))\n const mKeys = tM.rows.map(r => rowKey(meCurrent, r))\n\n // Exact LCS first, then fuzzy-pair remaining unmatched runs. Without\n // the fuzzy pass, a row where CP edited just a single cell's text\n // produces no key match — the row aligner emits the genesis row as\n // CP-deleted AND CP's reshaped row as inserted, when a cell-level\n // diff against the paired row would render the edit far more\n // legibly. The 2-way path (`TableDiff.pairSimilarUnmatchedRows`)\n // has done this since inception; bringing the three-way path in\n // step removes the asymmetry where the cp-only / all-changes view\n // looks markedly worse than plain 2-way for ordinary cell edits.\n const alignCp = pairSimilarRowsThreeWay(lcsAlign(gKeys, cKeys), genesis, cpLatest, tG, tC)\n const alignMe = pairSimilarRowsThreeWay(lcsAlign(gKeys, mKeys), genesis, meCurrent, tG, tM)\n\n // genesisIdx → matching cpIdx (-1 if cp deleted this row)\n const gToCp = new Array<number>(tG.rows.length).fill(-1)\n for (const a of alignCp) {\n if (a.oldIdx !== null && a.newIdx !== null) gToCp[a.oldIdx] = a.newIdx\n }\n const gToMe = new Array<number>(tG.rows.length).fill(-1)\n for (const a of alignMe) {\n if (a.oldIdx !== null && a.newIdx !== null) gToMe[a.oldIdx] = a.newIdx\n }\n\n // Off-spine row collections: cp rows with no genesis counterpart, me rows with no genesis counterpart.\n // Keyed by \"the genesis row index they should appear before\" so emission interleaves correctly.\n const cpInsAt = collectInsertedRowsAtBoundary(alignCp, tG.rows.length)\n const meInsAt = collectInsertedRowsAtBoundary(alignMe, tG.rows.length)\n\n const out: string[] = []\n out.push(tableHeaderSlice(genesis, tG))\n\n const emitBoundaryInsertions = (b: number) => {\n const cIdxs = cpInsAt.get(b) ?? []\n const mIdxs = meInsAt.get(b) ?? []\n if (cIdxs.length === 0 && mIdxs.length === 0) return\n // Detect settled insertions (cp and me both inserted the same row content).\n // Pair by content key, in order of appearance.\n const remainingMe = new Set(mIdxs)\n for (const cIdx of cIdxs) {\n const cText = cKeys[cIdx]\n let agreedMeIdx: number | undefined\n for (const mIdx of remainingMe) {\n if (mKeys[mIdx] === cText) {\n agreedMeIdx = mIdx\n break\n }\n }\n if (agreedMeIdx !== undefined) {\n remainingMe.delete(agreedMeIdx)\n // Settled insertion — emit cp's row verbatim, unmarked.\n out.push(cpLatest.slice(tC.rows[cIdx].rowStart, tC.rows[cIdx].rowEnd))\n } else {\n out.push(emitFullRowAttributed(cpLatest, tC.rows[cIdx], 'ins', 'cp'))\n }\n }\n for (const mIdx of remainingMe) {\n out.push(emitFullRowAttributed(meCurrent, tM.rows[mIdx], 'ins', 'me'))\n }\n }\n\n for (let g = 0; g < tG.rows.length; g++) {\n emitBoundaryInsertions(g)\n\n const cIdx = gToCp[g]\n const mIdx = gToMe[g]\n const cpDel = cIdx === -1\n const meDel = mIdx === -1\n\n if (!cpDel && !meDel) {\n // Both kept — recurse cell-level diff against this row triple.\n out.push(emitPreservedRow(genesis, cpLatest, meCurrent, tG.rows[g], tC.rows[cIdx], tM.rows[mIdx], cellDiff))\n } else if (cpDel && meDel) {\n // Both deleted — silent (settled).\n } else if (cpDel) {\n // CP dropped, Me kept → emit Me's row attributed as cp-deletion. The\n // content shown is what Me has; the styling tells the reader CP\n // wanted it gone.\n out.push(emitFullRowAttributed(meCurrent, tM.rows[mIdx], 'del', 'cp'))\n } else {\n // Me dropped, CP kept → emit CP's row attributed as me-deletion.\n out.push(emitFullRowAttributed(cpLatest, tC.rows[cIdx], 'del', 'me'))\n }\n }\n emitBoundaryInsertions(tG.rows.length)\n out.push(tableFooterSlice(genesis, tG))\n return out.join('')\n}\n\nfunction emitPreservedRow(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n rG: RowRange,\n rC: RowRange,\n rM: RowRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n if (rG.cells.length === rC.cells.length && rC.cells.length === rM.cells.length) {\n // Same cell counts — positional cell diff.\n const out: string[] = []\n let cursor = rG.rowStart\n for (let c = 0; c < rG.cells.length; c++) {\n const cG = rG.cells[c]\n const cC = rC.cells[c]\n const cM = rM.cells[c]\n out.push(genesis.slice(cursor, cG.contentStart))\n out.push(\n cellDiff(\n genesis.slice(cG.contentStart, cG.contentEnd),\n cpLatest.slice(cC.contentStart, cC.contentEnd),\n meCurrent.slice(cM.contentStart, cM.contentEnd)\n )\n )\n cursor = cG.contentEnd\n }\n out.push(genesis.slice(cursor, rG.rowEnd))\n return out.join('')\n }\n // Cell-count mismatch within a preserved row — cell-level structural\n // alignment is non-trivial (which Me cell maps to which CP cell when\n // the counts diverge?). The previous fallback emitted only\n // genesis-as-del + me-as-ins, which silently destroyed CP's row\n // content whenever CP changed the cell count — a content-loss bug\n // (a row where CP added a column would disappear from the rendered\n // diff entirely). Emit each side's row as a distinct attributed\n // block so neither party's restructure can vanish:\n // - if both restructured (different shapes on both sides) the\n // genesis row is settled-deleted (silent) and we emit cp + me\n // rows side by side, each attributed to its author;\n // - if only one restructured, the genesis row is del-attributed to\n // the restructuring author so the reader sees what was there\n // before, then the new shape ins-attributed to the same author.\n //\n // Content edits inside a side that DID keep the genesis cell count\n // are not surfaced here (no positional path is available across\n // mismatched shapes); the underlying data is still present in the\n // source document but the visual diff doesn't decompose it. That is\n // a degradation of detail, not content loss — symmetric for cp/me.\n const cpRestructured = rC.cells.length !== rG.cells.length\n const meRestructured = rM.cells.length !== rG.cells.length\n const blocks: string[] = []\n if (cpRestructured && meRestructured) {\n // Both sides restructured; genesis shape retained by neither.\n blocks.push(emitFullRowAttributed(cpLatest, rC, 'ins', 'cp'))\n blocks.push(emitFullRowAttributed(meCurrent, rM, 'ins', 'me'))\n } else if (cpRestructured) {\n blocks.push(emitFullRowAttributed(genesis, rG, 'del', 'cp'))\n blocks.push(emitFullRowAttributed(cpLatest, rC, 'ins', 'cp'))\n } else {\n blocks.push(emitFullRowAttributed(genesis, rG, 'del', 'me'))\n blocks.push(emitFullRowAttributed(meCurrent, rM, 'ins', 'me'))\n }\n return blocks.join('')\n}\n\n/**\n * Returns map \"genesis-row-boundary → list of new-side row indices\n * inserted at that boundary\". Mirrors the word-level boundary collection\n * but at the row scale.\n */\nfunction collectInsertedRowsAtBoundary(\n align: ReturnType<typeof lcsAlign>,\n genesisRowCount: number\n): Map<number, number[]> {\n const out = new Map<number, number[]>()\n let nextGenesisBoundary = genesisRowCount\n const pending: number[] = []\n // Walk in reverse so nextGenesisBoundary tracks the next preserved row\n // we'll encounter; flush pending unpaired new rows at the appropriate\n // genesis boundary.\n for (let i = align.length - 1; i >= 0; i--) {\n const a = align[i]\n if (a.oldIdx !== null) {\n if (pending.length > 0) {\n const existing = out.get(nextGenesisBoundary) ?? []\n existing.unshift(...pending.toReversed())\n out.set(nextGenesisBoundary, existing)\n pending.length = 0\n }\n nextGenesisBoundary = a.oldIdx\n } else if (a.newIdx !== null) {\n pending.push(a.newIdx)\n }\n }\n if (pending.length > 0) {\n const existing = out.get(nextGenesisBoundary) ?? []\n existing.unshift(...pending.toReversed())\n out.set(nextGenesisBoundary, existing)\n }\n return out\n}\n\nfunction tableHeaderSlice(html: string, table: TableRange): string {\n const firstRow = table.rows[0]\n if (!firstRow) return html.slice(table.tableStart, table.tableEnd - '</table>'.length)\n return html.slice(table.tableStart, firstRow.rowStart)\n}\n\nfunction tableFooterSlice(html: string, table: TableRange): string {\n const lastRow = table.rows[table.rows.length - 1]\n if (!lastRow) return '</table>'\n return html.slice(lastRow.rowEnd, table.tableEnd)\n}\n\n/**\n * Emit a row fully attributed to one author. Wraps `<tr>` and each\n * `<td>` with the author's diffins/diffdel class and `data-author`\n * attribute; wraps cell content with an inner `<ins>`/`<del>` matching\n * the word-level emission shape.\n */\nfunction emitFullRowAttributed(html: string, row: RowRange, kind: 'ins' | 'del', author: Author): string {\n const trOpening = parseOpeningTagAt(html, row.rowStart)\n if (!trOpening) return html.slice(row.rowStart, row.rowEnd)\n const trWithAttrs = injectAuthorAttribution(html.slice(row.rowStart, trOpening.end), kind, author)\n\n const out: string[] = [trWithAttrs]\n let cursor = trOpening.end\n for (const cell of row.cells) {\n out.push(html.slice(cursor, cell.cellStart))\n out.push(emitFullCellAttributed(html, cell, kind, author))\n cursor = cell.cellEnd\n }\n out.push(html.slice(cursor, row.rowEnd))\n return out.join('')\n}\n\nfunction emitFullCellAttributed(html: string, cell: CellRange, kind: 'ins' | 'del', author: Author): string {\n const tdOpening = parseOpeningTagAt(html, cell.cellStart)\n if (!tdOpening) return html.slice(cell.cellStart, cell.cellEnd)\n const tdWithAttrs = injectAuthorAttribution(html.slice(cell.cellStart, tdOpening.end), kind, author)\n const innerContent = html.slice(cell.contentStart, cell.contentEnd)\n const innerWrapped =\n innerContent.trim().length === 0\n ? innerContent\n : Utils.wrapText(innerContent, kind, `diff${kind}`, authorAttribution(author))\n const closing = html.slice(cell.contentEnd, cell.cellEnd)\n return tdWithAttrs + innerWrapped + closing\n}\n\nfunction injectAuthorAttribution(openingTag: string, kind: 'ins' | 'del', author: Author): string {\n const meta = authorAttribution(author)\n const tagWithClass = injectClass(openingTag, `diff${kind} ${meta.extraClasses}`)\n return injectDataAttrs(tagWithClass, meta.dataAttrs ?? {})\n}\n\nfunction injectDataAttrs(openingTag: string, dataAttrs: Readonly<Record<string, string>>): string {\n const keys = Object.keys(dataAttrs)\n if (keys.length === 0) return openingTag\n const attrs = keys.map(k => ` data-${k}='${dataAttrs[k]}'`).join('')\n if (openingTag.endsWith('/>')) return `${openingTag.slice(0, -2)}${attrs}/>`\n return `${openingTag.slice(0, -1)}${attrs}>`\n}\n","import Mode from './Mode'\nimport Utils from './Utils'\n\nexport default class WordSplitter {\n private text: string\n private isBlockCheckRequired: boolean\n private blockLocations: BlockFinderResult\n private mode: Mode\n private isGrouping = false\n private globbingUntil: number\n private currentWord: string[]\n private words: string[]\n private static NotGlobbing = -1\n\n private get currentWordHasChars() {\n return this.currentWord.length > 0\n }\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockLocations = new BlockFinder(text, blockExpressions).findBlocks()\n this.isBlockCheckRequired = this.blockLocations.hasBlocks\n this.mode = Mode.Character\n this.globbingUntil = WordSplitter.NotGlobbing\n this.currentWord = []\n this.words = []\n }\n\n process(): string[] {\n for (let index = 0; index < this.text.length; index++) {\n const character = this.text.charAt(index)\n this.processCharacter(index, character)\n }\n\n this.appendCurrentWordToWords()\n return this.words\n }\n\n private processCharacter(index: number, character: string) {\n if (this.isGlobbing(index, character)) {\n return\n }\n\n switch (this.mode) {\n case Mode.Character:\n this.processTextCharacter(character)\n break\n case Mode.Tag:\n this.processHtmlTagContinuation(character)\n break\n case Mode.Whitespace:\n this.processWhiteSpaceContinuation(character)\n break\n case Mode.Entity:\n this.processEntityContinuation(character)\n break\n }\n }\n\n private processEntityContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (character.trim().length === 0) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (Utils.isEndOfEntity(character)) {\n let switchToNextMode = true\n if (this.currentWordHasChars) {\n this.currentWord.push(character)\n this.words.push(this.currentWord.join(''))\n\n //join &nbsp; entity with last whitespace\n if (\n this.words.length > 2 &&\n Utils.isWhiteSpace(this.words[this.words.length - 2]) &&\n Utils.isWhiteSpace(this.words[this.words.length - 1])\n ) {\n const w1 = this.words[this.words.length - 2]\n const w2 = this.words[this.words.length - 1]\n this.words.splice(this.words.length - 2, 2)\n this.currentWord = `${w1}${w2}`.split('')\n this.mode = Mode.Whitespace\n switchToNextMode = false\n }\n }\n\n if (switchToNextMode) {\n this.currentWord = []\n this.mode = Mode.Character\n }\n } else if (Utils.isWord(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processWhiteSpaceContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processHtmlTagContinuation(character: string) {\n if (Utils.isEndOfTag(character)) {\n this.currentWord.push(character)\n this.appendCurrentWordToWords()\n this.mode = Utils.isWhiteSpace(character) ? Mode.Whitespace : Mode.Character\n } else {\n this.currentWord.push(character)\n }\n }\n\n private processTextCharacter(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push('<')\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (\n Utils.isWord(character) &&\n (this.currentWord.length === 0 || Utils.isWord(this.currentWord[this.currentWord.length - 1]))\n ) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n }\n }\n\n private appendCurrentWordToWords() {\n if (this.currentWordHasChars) {\n this.words.push(this.currentWord.join(''))\n this.currentWord = []\n }\n }\n\n private isGlobbing(index: number, character: string): boolean {\n if (!this.isBlockCheckRequired) {\n return false\n }\n const isCurrentBlockTerminating = index === this.globbingUntil\n if (isCurrentBlockTerminating) {\n this.globbingUntil = WordSplitter.NotGlobbing\n this.isGrouping = false\n this.appendCurrentWordToWords()\n }\n\n const until = this.blockLocations.isInBlock(index)\n if (until) {\n this.isGrouping = true\n this.globbingUntil = until\n }\n if (this.isGrouping) {\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n return this.isGrouping\n }\n\n static convertHtmlToListOfWords(text: string, blockExpressions: RegExp[]): string[] {\n return new WordSplitter(text, blockExpressions).process()\n }\n}\n\nclass BlockFinderResult {\n private blocks: Map<number, number> = new Map()\n\n addBlock(from: number, to: number) {\n if (this.blocks.has(from)) {\n throw new ArgumentError('One or more block expressions result in a text sequence that overlaps.')\n }\n\n this.blocks.set(from, to)\n }\n\n isInBlock(location: number): number | null {\n return this.blocks.get(location) ?? null\n }\n\n get hasBlocks() {\n return this.blocks.size > 0\n }\n}\n\nclass ArgumentError extends Error {}\n\nclass BlockFinder {\n private text: string\n private blockExpressions: RegExp[]\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockExpressions = blockExpressions\n }\n\n findBlocks(): BlockFinderResult {\n const result = new BlockFinderResult()\n for (const expression of this.blockExpressions) {\n this.processBlockMatcher(expression, result)\n }\n return result\n }\n\n private processBlockMatcher(exp: RegExp, result: BlockFinderResult) {\n let match: RegExpExecArray | null\n // biome-ignore lint/suspicious/noAssignInExpressions: Couldn't think of a nicer way to do this\n while ((match = exp.exec(this.text)) !== null) {\n this.tryAddBlock(exp, match, result)\n }\n }\n\n private tryAddBlock(exp: RegExp, match: RegExpExecArray, result: BlockFinderResult) {\n try {\n const from = match.index\n const to = match.index + match[0].length\n result.addBlock(from, to)\n } catch {\n throw new ArgumentError(\n `One or more block expressions result in a text sequence that overlaps. Current expression: ${exp}`\n )\n }\n }\n}\n","import Action from './Action'\nimport Match from './Match'\nimport MatchFinder from './MatchFinder'\nimport Operation from './Operation'\nimport { preprocessTables, restoreTablePlaceholders } from './TableDiff'\nimport { buildSegments, type Segment, segmentEmissionShape } from './ThreeWayDiff'\nimport { preprocessTablesThreeWay } from './ThreeWayTable'\nimport Utils, { type WrapMetadata } from './Utils'\nimport WordSplitter from './WordSplitter'\n\n/**\n * State threaded into the recursive cell-level diff inside\n * `preprocessTables`. Bundles the nesting depth (for the recursion-cap\n * guard) with the caller-configurable settings that must propagate\n * unchanged to inner instances so cell-level output stays consistent\n * with the top-level call. Internal — never crosses the public API.\n */\ninterface RecursionContext {\n depth: number\n blockExpressions: readonly RegExp[]\n repeatingWordsAccuracy: number\n orphanMatchThreshold: number\n ignoreWhitespaceDifferences: boolean\n}\n\n/**\n * Options for the `HtmlDiff.analyze` static helper.\n *\n * `useProjections` controls structural-tag normalisation:\n * - `undefined` → use the same heuristic as `build()` (per-call decision)\n * - `true` → force projection on (skipped if either side has no content)\n * - `false` → force projection off (diff runs on raw word arrays)\n * Composers of multiple analyses (e.g. three-way diff) MUST pass the\n * same explicit boolean to all calls so shared inputs tokenise\n * identically across analyses.\n *\n * The remaining options mirror the per-instance fields on `HtmlDiff`\n * itself — they exist on the options bag because `analyze` constructs\n * the inner instance internally.\n */\nexport interface AnalyzeOptions {\n useProjections?: boolean\n blockExpressions?: readonly RegExp[]\n repeatingWordsAccuracy?: number\n orphanMatchThreshold?: number\n ignoreWhitespaceDifferences?: boolean\n}\n\nexport interface AnalyzeResult {\n /** Word array the `operations` index into (projected or raw). */\n readonly oldDiffWords: readonly string[]\n readonly newDiffWords: readonly string[]\n readonly operations: readonly Operation[]\n /** Original WordSplitter output, before any projection. */\n readonly oldOriginalWords: readonly string[]\n readonly newOriginalWords: readonly string[]\n /** Diff-index → original-word-index map; null when projections inactive. */\n readonly oldContentToOriginal: readonly number[] | null\n readonly newContentToOriginal: readonly number[] | null\n}\n\n/**\n * Options for `HtmlDiff.executeThreeWay`. Same shape as `AnalyzeOptions`\n * — the values flow unchanged into both internal `analyze` calls so V2's\n * tokenisation stays symmetric. Aliased so future divergence in either\n * direction lives in one place.\n *\n * `useProjections`: when undefined, `executeThreeWay` computes the\n * decision as the conjunction of both pair-wise\n * `evaluateProjectionApplicability` results.\n */\nexport type ThreeWayOptions = AnalyzeOptions\n\n/**\n * Opinionated options that align htmldiff's output with Microsoft Word's\n * track-changes rendering for legal-document rewrites.\n *\n * The library's bare default (`orphanMatchThreshold = 0`) keeps every\n * LCS match, however small — which fragments long sentence rewrites\n * into many tiny ins/del pairs around stray word matches (\"of\", \"the\",\n * \"shall\"). Word collapses those into a single coarse del+ins, which is\n * dramatically more readable for legal text.\n *\n * 0.25 was tuned empirically against a customer Word reference (US\n * Commercial One CP, May 2026):\n * - short edits (typo / one-word insert): output identical to\n * threshold=0 — inter-match distances are tiny so every match\n * trivially clears the bar;\n * - long rewrites (the \"Specified Indebtedness\" rewrite in the\n * reference): previously produced 6 dels + 5 ins fragmented around\n * stray matches; at 0.25 it condenses to 3 dels + 2 ins — close to\n * Word's 1+1 and a major readability win;\n * - higher values (0.3+) collapsed short edits containing inline\n * formatting changes into a single block — too aggressive.\n *\n * Consumers rendering legal documents should spread this into their\n * options:\n * `HtmlDiff.execute(old, new, { ...WORD_ALIGNED_OPTIONS })`\n * `HtmlDiff.executeThreeWay(g, c, m, { ...WORD_ALIGNED_OPTIONS })`\n *\n * Other consumers (machine-readable diff, exact-token alignment) can\n * keep the bare default.\n */\nexport const WORD_ALIGNED_OPTIONS: AnalyzeOptions = {\n orphanMatchThreshold: 0.25,\n}\n\nexport default class HtmlDiff {\n /**\n * This value defines balance between speed and memory utilization. The higher it is the faster it works and more memory consumes.\n * @private\n */\n private static MatchGranularityMaximum = 4\n\n private static DelTag = 'del'\n private static InsTag = 'ins'\n\n // ignore case\n private static SpecialCaseClosingTags = [\n '</strong>',\n '</em>',\n '</b>',\n '</i>',\n '</big>',\n '</small>',\n '</u>',\n '</sub>',\n '</sup>',\n '</strike>',\n '</s>',\n '</span>',\n ]\n\n private static SpecialCaseClosingTagsSet = new Set([\n '</strong>',\n '</em>',\n '</b>',\n '</i>',\n '</big>',\n '</small>',\n '</u>',\n '</sub>',\n '</sup>',\n '</strike>',\n '</s>',\n '</span>',\n ])\n\n private static SpecialCaseOpeningTagRegex =\n /<((strong)|(b)|(i)|(em)|(big)|(small)|(u)|(sub)|(sup)|(strike)|(s)|(span))[>\\s]+/i\n\n private static FormattingTags = new Set([\n 'strong',\n 'em',\n 'b',\n 'i',\n 'big',\n 'small',\n 'u',\n 'sub',\n 'sup',\n 'strike',\n 's',\n 'span',\n ])\n\n /**\n * Hard cap on nested `HtmlDiff.execute` calls (table preprocessing\n * recurses through `diffCell` for cell content). Each level allocates\n * fresh DP matrices and word arrays; without a guard a maliciously\n * nested table-in-cell-in-table-in-cell input could blow stack and\n * memory. Set high enough to comfortably handle real legal documents\n * (tables nested 2-3 deep at most), low enough to short-circuit\n * pathological input.\n */\n private static MaxTablePreprocessDepth = 8\n\n /**\n * Mirror cap for the three-way path. The 2-way `MaxTablePreprocessDepth`\n * guards the recursion inside `executeWithContext`; the 3-way path has\n * its own recursion (`executeThreeWay` → `preprocessTablesThreeWay` →\n * `cellDiff` → `executeThreeWay`) which needs its own guard. Once the\n * cap is reached, `executeThreeWay` skips table preprocessing and\n * falls back to the word-level merge — same bail-out semantics as the\n * 2-way path.\n */\n private static MaxThreeWayDepth = 8\n\n private content: string[] = []\n private newText: string\n private oldText: string\n // Written exactly once, by `executeWithContext` on the inner instance\n // for a recursive cell-diff. Top-level instances stay at 0. Treated as\n // effectively-readonly elsewhere — we dropped the modifier only so\n // `executeWithContext` can populate it without needing a private\n // constructor overload that would re-leak the parameter we just hid.\n private tablePreprocessDepth = 0\n\n /**\n * Tracks currently-open formatting-tag wraps. Each entry pairs the\n * opening tag (so a later closing tag can find its match) with the\n * styling info needed to RE-OPEN the wrap if an overlapping\n * formatting-tag close forces it to split. Without the styling info,\n * an overlap like `<strong>X</strong>` ↔ `<u>X</u>` produces an\n * unclosable wrap (the closing tag for the outer wrap arrives while\n * an inner wrap is still on the stack); see `insertTag`'s closing\n * handler for the split logic.\n */\n private specialTagDiffStack: Array<{\n tag: string\n styledTagNames: string\n cssClass: string\n metadata: WrapMetadata | undefined\n }> = []\n private newWords: string[] = []\n private oldWords: string[] = []\n /**\n * Content-only projections of oldWords/newWords (structural tags and adjacent whitespace removed).\n * When null, no structural normalization is applied (the word arrays are identical for diffing).\n */\n private oldContentWords: string[] | null = null\n private newContentWords: string[] | null = null\n /** Maps content-word index → original word index */\n private oldContentToOriginal: number[] | null = null\n private newContentToOriginal: number[] | null = null\n /**\n * Tracks the next unwritten word index in oldWords/newWords. Mutated only by\n * {@link sliceOriginalWordsForOp} (each op reads a slice and advances its cursor).\n * Advances monotonically. Used so:\n * - subsequent equal/delete ops know where in old to resume from\n * - subsequent insert ops know where in new to resume from\n * The two cursors are independent: equal/delete output from old and advance the old\n * cursor; insert outputs from new and advances the new cursor.\n */\n private lastOriginalOldOutputIndex = 0\n private lastOriginalNewOutputIndex = 0\n private matchGranularity = 0\n private blockExpressions: RegExp[] = []\n\n /**\n * Defines how to compare repeating words. Valid values are from 0 to 1.\n * This value allows to exclude some words from comparison that eventually\n * reduces the total time of the diff algorithm.\n * 0 means that all words are excluded so the diff will not find any matching words at all.\n * 1 (default value) means that all words participate in comparison so this is the most accurate case.\n * 0.5 means that any word that occurs more than 50% times may be excluded from comparison. This doesn't\n * mean that such words will definitely be excluded but only gives a permission to exclude them if necessary.\n */\n repeatingWordsAccuracy = 1.0\n\n /**\n * If true all whitespaces are considered as equal\n */\n ignoreWhitespaceDifferences = false\n\n /**\n * If some match is too small and located far from its neighbors then it is considered as orphan\n * and removed. For example:\n * <code>\n * aaaaa bb ccccccccc dddddd ee\n * 11111 bb 222222222 dddddd ee\n * </code>\n * will find two matches <code>bb</code> and <code>dddddd ee</code> but the first will be considered\n * as orphan and ignored, as result it will consider texts <code>aaaaa bb ccccccccc</code> and\n * <code>11111 bb 222222222</code> as single replacement:\n * <code>\n * &lt;del&gt;aaaaa bb ccccccccc&lt;/del&gt;&lt;ins&gt;11111 bb 222222222&lt;/ins&gt; dddddd ee\n * </code>\n * This property defines relative size of the match to be considered as orphan, from 0 to 1.\n * 1 means that all matches will be considered as orphans.\n * 0 (default) means that no match will be considered as orphan.\n * 0.2 means that if match length is less than 20% of distance between its neighbors it is considered as orphan.\n */\n orphanMatchThreshold = 0.0\n\n /**\n * Initializes a new instance of the class.\n * @param oldText The old text.\n * @param newText The new text.\n */\n constructor(oldText: string, newText: string) {\n this.oldText = oldText\n this.newText = newText\n }\n\n /**\n * Two-way diff entry point. Accepts the same `AnalyzeOptions` bag as\n * `executeThreeWay`, with two intentional exceptions documented\n * inline below. Consumers wanting Word-aligned output should spread\n * `WORD_ALIGNED_OPTIONS` into the third argument.\n *\n * Note: unlike `analyze`, `execute` runs `build()` which performs\n * full table preprocessing — `tablePreprocessDepth` stays at 0 so\n * the recursive cell diff can happen. Callers can't override that.\n */\n static execute(oldText: string, newText: string, options: AnalyzeOptions = {}): string {\n const inner = new HtmlDiff(oldText, newText)\n if (options.blockExpressions) {\n for (const expr of options.blockExpressions) inner.addBlockExpression(expr)\n }\n if (options.repeatingWordsAccuracy !== undefined) inner.repeatingWordsAccuracy = options.repeatingWordsAccuracy\n if (options.orphanMatchThreshold !== undefined) inner.orphanMatchThreshold = options.orphanMatchThreshold\n if (options.ignoreWhitespaceDifferences !== undefined) {\n inner.ignoreWhitespaceDifferences = options.ignoreWhitespaceDifferences\n }\n // `useProjections` is intentionally NOT plumbed here — the 2-way\n // path's build() runs its own heuristic. `analyze` honours it; if\n // you need to force it for a 2-way result, route through `analyze`\n // and consume the operations directly.\n return inner.build()\n }\n\n /**\n * Analyse a two-way diff and return its raw building blocks: the word\n * arrays the diff ran against, the operations produced, the original\n * (pre-projection) word arrays, and the mappings from diff-index back\n * to original-word index when structural projection is active.\n * Consumed by `executeThreeWay` so it can compose two diffs by walking\n * their Operation streams.\n *\n * The caller is expected to coordinate `useProjections` symmetrically\n * across composed analyses — if V1↔V2 projects but V2↔V3 doesn't,\n * V2's \"new\" array in the first analysis won't equal V2's \"old\" array\n * in the second. `evaluateProjectionApplicability` exposes the same\n * heuristic `build()` uses internally, so the orchestrator can compute\n * a single decision and pass it into every `analyze` call.\n *\n * Table preprocessing is skipped here. Placeholders mutate the input\n * in ways that don't compose across two independent analyses; the\n * 3-way orchestrator handles tables explicitly before calling analyze.\n */\n static analyze(oldText: string, newText: string, options: AnalyzeOptions = {}): AnalyzeResult {\n const inner = new HtmlDiff(oldText, newText)\n // Bypass table preprocessing — the caller handles tables.\n inner.tablePreprocessDepth = HtmlDiff.MaxTablePreprocessDepth\n if (options.blockExpressions) {\n for (const expr of options.blockExpressions) inner.addBlockExpression(expr)\n }\n if (options.repeatingWordsAccuracy !== undefined) inner.repeatingWordsAccuracy = options.repeatingWordsAccuracy\n if (options.orphanMatchThreshold !== undefined) inner.orphanMatchThreshold = options.orphanMatchThreshold\n if (options.ignoreWhitespaceDifferences !== undefined) {\n inner.ignoreWhitespaceDifferences = options.ignoreWhitespaceDifferences\n }\n inner.splitInputsToWords()\n if (options.useProjections === undefined) {\n // Mirror build()'s heuristic — same behaviour as a standalone 2-way diff.\n inner.buildContentProjections()\n } else if (options.useProjections) {\n // Caller forced projections on. Still skip if either side has no\n // structural content, since projecting an empty side produces an\n // empty diff space and the merge degrades.\n const oldProj = HtmlDiff.createContentProjection(inner.oldWords)\n const newProj = HtmlDiff.createContentProjection(inner.newWords)\n if (oldProj.contentWords.length > 0 && newProj.contentWords.length > 0) {\n inner.oldContentWords = oldProj.contentWords\n inner.oldContentToOriginal = oldProj.contentToOriginal\n inner.newContentWords = newProj.contentWords\n inner.newContentToOriginal = newProj.contentToOriginal\n }\n }\n // useProjections === false: leave projections unset, diff runs on raw words.\n const wordsForDiffOld = inner.oldContentWords ?? inner.oldWords\n const wordsForDiffNew = inner.newContentWords ?? inner.newWords\n inner.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(wordsForDiffOld.length, wordsForDiffNew.length)\n )\n return {\n oldDiffWords: wordsForDiffOld,\n newDiffWords: wordsForDiffNew,\n operations: inner.operations(),\n oldOriginalWords: inner.oldWords,\n newOriginalWords: inner.newWords,\n oldContentToOriginal: inner.oldContentToOriginal,\n newContentToOriginal: inner.newContentToOriginal,\n }\n }\n\n /**\n * Whether content-projection (structural-tag normalisation) would\n * apply to this pair of inputs under `build()`'s default heuristic.\n * Exposed so composers of multiple analyses can compute a symmetric\n * decision before calling `analyze` — see `analyze`'s docstring for\n * why symmetry matters.\n */\n static evaluateProjectionApplicability(oldText: string, newText: string): boolean {\n const oldWords = WordSplitter.convertHtmlToListOfWords(oldText, [])\n const newWords = WordSplitter.convertHtmlToListOfWords(newText, [])\n if (!HtmlDiff.hasStructuralDifferences(oldWords, newWords)) return false\n const oldProj = HtmlDiff.createContentProjection(oldWords)\n const newProj = HtmlDiff.createContentProjection(newWords)\n return HtmlDiff.shouldUseContentProjections(oldWords, newWords, oldProj, newProj)\n }\n\n /**\n * Three-way HTML diff against a shared genesis. Produces attributed\n * HTML that distinguishes CP's accumulated changes (genesis → cpLatest)\n * from Me's accumulated changes (genesis → meCurrent). Use this for\n * blackline UX where the negotiation has gone through multiple turns\n * and the reader wants to see \"who proposed what\" across the whole\n * history, not just the most recent round.\n *\n * When both parties happen to have made the same change (e.g. CP\n * proposed a wording change in turn N, Me adopted it in turn N+1),\n * the change reads as \"settled\" and is emitted unmarked — only\n * disagreements and pending proposals carry author attribution.\n *\n * @param genesis the shared common ancestor (per-user — the FE\n * picks between V1.0 and /preview/initialAnswers\n * based on `prefillReceiverAnswers`)\n * @param cpLatest the counterparty's current published version\n * @param meCurrent Me's current draft (the document on screen)\n */\n static executeThreeWay(genesis: string, cpLatest: string, meCurrent: string, options: ThreeWayOptions = {}): string {\n return HtmlDiff.executeThreeWayWithDepth(genesis, cpLatest, meCurrent, options, 0)\n }\n\n private static executeThreeWayWithDepth(\n genesis: string,\n cpLatest: string,\n meCurrent: string,\n options: ThreeWayOptions,\n depth: number\n ): string {\n // Table preprocessing first — replaces each genesis/cp/me table with a\n // shared-nonce placeholder, then the word-level merge runs over the\n // table-free inputs. Cells are diffed recursively via executeThreeWay\n // so the cell content is itself three-way attributed.\n //\n // Depth-cap the recursion so adversarially-nested input can't blow\n // stack/memory.\n const tablePreprocess =\n depth < HtmlDiff.MaxThreeWayDepth\n ? preprocessTablesThreeWay(genesis, cpLatest, meCurrent, (g, c, m) =>\n HtmlDiff.executeThreeWayWithDepth(g, c, m, options, depth + 1)\n )\n : null\n const inGenesis = tablePreprocess?.modifiedGenesis ?? genesis\n const inCp = tablePreprocess?.modifiedCp ?? cpLatest\n const inMe = tablePreprocess?.modifiedMe ?? meCurrent\n\n // Symmetric projection across both analyses. The genesis-spine\n // algorithm requires `genesis` to tokenise identically on each\n // pair-wise analysis (both have genesis as the OLD side), so the\n // useProjections decision must agree across both calls.\n const useProjections =\n options.useProjections ??\n (HtmlDiff.evaluateProjectionApplicability(inGenesis, inCp) &&\n HtmlDiff.evaluateProjectionApplicability(inGenesis, inMe))\n\n const analyzeOpts: AnalyzeOptions = {\n useProjections,\n blockExpressions: options.blockExpressions,\n repeatingWordsAccuracy: options.repeatingWordsAccuracy,\n orphanMatchThreshold: options.orphanMatchThreshold,\n ignoreWhitespaceDifferences: options.ignoreWhitespaceDifferences,\n }\n const dCp = HtmlDiff.analyze(inGenesis, inCp, analyzeOpts)\n const dMe = HtmlDiff.analyze(inGenesis, inMe, analyzeOpts)\n\n // Spine sanity check — both analyses must share an identical genesis\n // tokenisation. Symmetric useProjections guarantees this; if it ever\n // diverges, fail loudly rather than silently misattribute.\n if (dCp.oldDiffWords.length !== dMe.oldDiffWords.length) {\n throw new Error(\n 'HtmlDiff.executeThreeWay: genesis tokenisation diverged across pair-wise analyses ' +\n `(${dCp.oldDiffWords.length} vs ${dMe.oldDiffWords.length}). ` +\n 'This indicates the symmetric-projection coordination has a bug.'\n )\n }\n\n const segments = buildSegments(dCp, dMe)\n const merged = HtmlDiff.emitSegments(segments)\n return tablePreprocess ? restoreTablePlaceholders(merged, tablePreprocess.placeholderToDiff) : merged\n }\n\n /**\n * Drives a fresh `HtmlDiff` instance through `insertTag` for ins/del\n * segments and pushes equal segments straight to its `content`\n * buffer. Reusing the instance keeps the formatting-tag stack\n * (`specialTagDiffStack`) coherent across segments — a `<strong>`\n * opened in one segment and closed in another stays balanced.\n *\n * Edge case: an ins/del segment can open a formatting wrap whose\n * matching closer ends up in an equal segment (`<strong>` deleted\n * by CP but `</strong>` kept by both — buildSegments emits the open\n * as del-cp and the close as equal). Equal segments bypass\n * `insertTag` and push raw, so the stack entry for the open is\n * never popped. Rather than throw — which forces the caller's UI\n * into an error boundary — close every leftover wrap with `</ins>`\n * at the end of emission.\n *\n * Caveat: the `</ins>` close is honest for the mod-wrap that the\n * opener pushed (every formatting opener emits an inner `<ins…>`\n * postInject regardless of whether the outer segment is ins or\n * del). For del-segment formatting openers the outer `<del>` may\n * itself be left open by the same emission imbalance; this fixup\n * doesn't address that. Downstream browsers/DOMParser normalise\n * mildly-malformed HTML by closing dangling tags, so the rendered\n * output is usually acceptable — but the warning IS the signal\n * that the input had a real imbalance worth investigating.\n */\n private static emitSegments(segments: Segment[]): string {\n const emitter = new HtmlDiff('', '')\n for (const seg of segments) {\n if (seg.attr.kind === 'equal') {\n emitter.content.push(seg.words.join(''))\n continue\n }\n const { tag, baseClass, metadata } = segmentEmissionShape(seg.attr)\n // insertTag mutates its `words` array; pass a copy.\n emitter.insertTag(tag, baseClass, [...seg.words], metadata)\n }\n if (emitter.specialTagDiffStack.length > 0) {\n // Log once so we can spot bad inputs in dev tools, but don't\n // throw — the caller's only fallback was to crash the React\n // tree, which is worse than emitting slightly-imperfect HTML.\n // eslint-disable-next-line no-console\n console.warn(\n `HtmlDiff.executeThreeWay: emission left ${emitter.specialTagDiffStack.length} ` +\n 'unclosed formatting wrap(s) on the stack. Closing defensively. ' +\n 'This usually means a formatting tag opens in a del/ins segment ' +\n 'and its matching closer is in an equal segment.'\n )\n while (emitter.specialTagDiffStack.length > 0) {\n emitter.content.push('</ins>')\n emitter.specialTagDiffStack.pop()\n }\n }\n return emitter.content.join('')\n }\n\n /**\n * Internal entry point used by the table-cell recursion. Constructs an\n * inner `HtmlDiff`, applies the caller's settings, and bumps the\n * recursion depth — keeping the public constructor signature clean\n * while still threading the configuration that's required for cell-\n * level output to match the top-level call's behaviour.\n */\n private static executeWithContext(oldText: string, newText: string, ctx: RecursionContext): string {\n const inner = new HtmlDiff(oldText, newText)\n inner.tablePreprocessDepth = ctx.depth\n for (const expr of ctx.blockExpressions) inner.addBlockExpression(expr)\n inner.repeatingWordsAccuracy = ctx.repeatingWordsAccuracy\n inner.orphanMatchThreshold = ctx.orphanMatchThreshold\n inner.ignoreWhitespaceDifferences = ctx.ignoreWhitespaceDifferences\n return inner.build()\n }\n\n /**\n * Builds the HTML diff output\n * @return HTML diff markup\n */\n build(): string {\n // If there is no difference, don't bother checking for differences\n if (this.oldText === this.newText) {\n return this.newText\n }\n\n // Table preprocessing: when both sides have matching `<table>` structures,\n // diff cells positionally so cross-cell content shifts produce one\n // independent del/ins per cell rather than cell-misaligned output.\n // Recursion is guarded by MaxTablePreprocessDepth — check the cap\n // first so we don't construct a context that will never be used.\n let tablePreprocess: ReturnType<typeof preprocessTables> = null\n if (this.tablePreprocessDepth < HtmlDiff.MaxTablePreprocessDepth) {\n // Caller-configured settings (block expressions, accuracy\n // thresholds) flow to the recursive cell diff via `RecursionContext`\n // so cell-level output is consistent with the top-level\n // configuration. The context is built once here and reused for\n // every cell-diff callback invocation.\n const ctx: RecursionContext = {\n depth: this.tablePreprocessDepth + 1,\n blockExpressions: this.blockExpressions,\n repeatingWordsAccuracy: this.repeatingWordsAccuracy,\n orphanMatchThreshold: this.orphanMatchThreshold,\n ignoreWhitespaceDifferences: this.ignoreWhitespaceDifferences,\n }\n tablePreprocess = preprocessTables(this.oldText, this.newText, (oldCell, newCell) =>\n HtmlDiff.executeWithContext(oldCell, newCell, ctx)\n )\n }\n if (tablePreprocess) {\n this.oldText = tablePreprocess.modifiedOld\n this.newText = tablePreprocess.modifiedNew\n }\n\n this.splitInputsToWords()\n this.buildContentProjections()\n\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n this.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(wordsForDiffOld.length, wordsForDiffNew.length)\n )\n\n const operations = this.operations()\n for (const op of operations) {\n this.performOperation(op)\n }\n\n const result = this.content.join('')\n return tablePreprocess ? restoreTablePlaceholders(result, tablePreprocess.placeholderToDiff) : result\n }\n\n /**\n * Uses {@link expression} to group text together so that any change detected within the group is treated as a single block\n * @param expression\n */\n addBlockExpression(expression: RegExp) {\n this.blockExpressions.push(expression)\n }\n\n private splitInputsToWords() {\n this.oldWords = WordSplitter.convertHtmlToListOfWords(this.oldText, this.blockExpressions)\n\n // free memory, allow it for GC\n this.oldText = ''\n\n this.newWords = WordSplitter.convertHtmlToListOfWords(this.newText, this.blockExpressions)\n\n // free memory, allow it for GC\n this.newText = ''\n }\n\n /**\n * Builds \"content projections\" — word arrays with structural wrapper tags stripped — when\n * structural normalization is appropriate for these inputs. The diff algorithm operates on\n * the projections so wrapper-tag differences (e.g. `<p>` vs `<div>`) don't appear as content\n * changes; structural tags are then folded back in at output time.\n */\n private buildContentProjections() {\n if (!HtmlDiff.hasStructuralDifferences(this.oldWords, this.newWords)) return\n\n const oldProjection = HtmlDiff.createContentProjection(this.oldWords)\n const newProjection = HtmlDiff.createContentProjection(this.newWords)\n\n if (!HtmlDiff.shouldUseContentProjections(this.oldWords, this.newWords, oldProjection, newProjection)) {\n return\n }\n\n this.oldContentWords = oldProjection.contentWords\n this.oldContentToOriginal = oldProjection.contentToOriginal\n this.newContentWords = newProjection.contentWords\n this.newContentToOriginal = newProjection.contentToOriginal\n }\n\n /**\n * Decides whether structural normalization should be activated for this pair of inputs.\n * Each clause is a distinct correctness or fitness check — extend by adding a named\n * sub-predicate rather than chaining ad-hoc conditions.\n */\n private static shouldUseContentProjections(\n oldWords: string[],\n newWords: string[],\n oldProjection: { contentWords: string[]; contentToOriginal: number[] },\n newProjection: { contentWords: string[]; contentToOriginal: number[] }\n ): boolean {\n // One side has no content at all: that's a genuine addition/deletion, not a wrapper rename.\n // Normalization would mis-attribute the wrappers as part of the diff.\n if (oldProjection.contentWords.length === 0 || newProjection.contentWords.length === 0) return false\n\n // Asymmetric structural state: one side has no structural wrappers at all (e.g. plain text\n // vs. wrapped HTML). Normalization would force the equal output to use the unwrapped side's\n // (missing) structure and emit dangling closing tags from the wrapped side. The plain\n // word-level diff handles this correctly without normalization.\n const oldHasStructuralTags = oldProjection.contentWords.length < oldWords.length\n const newHasStructuralTags = newProjection.contentWords.length < newWords.length\n if (oldHasStructuralTags !== newHasStructuralTags) return false\n\n return true\n }\n\n /**\n * Tags that commonly serve as content wrappers and may change structurally\n * without affecting the actual content. Only these tags are stripped during\n * structural normalization.\n */\n private static WrapperTags = new Set(['div', 'p', 'section', 'article', 'main', 'header', 'footer', 'aside', 'nav'])\n\n private static isStructuralTag(word: string): boolean {\n if (!Utils.isTag(word)) return false\n const tagName = Utils.getTagName(word)\n return HtmlDiff.WrapperTags.has(tagName)\n }\n\n /** True when the word is a structural opening tag (e.g. `<p>`, `<div>`). */\n private static isOpeningStructuralTag(word: string): boolean {\n return HtmlDiff.isStructuralTag(word) && !word.startsWith('</')\n }\n\n /**\n * Returns true if words between structural tags are just whitespace (indentation).\n */\n private static isStructuralWhitespace(words: string[], index: number): boolean {\n if (!Utils.isWhiteSpace(words[index])) return false\n\n // Check if this whitespace is adjacent to a structural tag on either side\n const prevIsStructural = index === 0 || HtmlDiff.isStructuralTag(words[index - 1])\n const nextIsStructural = index === words.length - 1 || HtmlDiff.isStructuralTag(words[index + 1])\n return prevIsStructural || nextIsStructural\n }\n\n private static createContentProjection(words: string[]): {\n contentWords: string[]\n contentToOriginal: number[]\n } {\n const contentWords: string[] = []\n const contentToOriginal: number[] = []\n\n for (let i = 0; i < words.length; i++) {\n if (HtmlDiff.isStructuralTag(words[i])) continue\n if (HtmlDiff.isStructuralWhitespace(words, i)) continue\n contentWords.push(words[i])\n contentToOriginal.push(i)\n }\n\n return { contentWords, contentToOriginal }\n }\n\n private static hasStructuralDifferences(oldWords: string[], newWords: string[]): boolean {\n const oldStructural: string[] = []\n const newStructural: string[] = []\n\n // Compare only tag names (stripped of attributes) since structural normalization\n // is about wrapper tag name changes (e.g. <p> vs <div>), not attribute differences.\n // Attribute changes on the same tag name don't need projection-based normalization.\n for (const w of oldWords) {\n if (HtmlDiff.isStructuralTag(w)) {\n oldStructural.push(Utils.stripTagAttributes(w))\n }\n }\n for (const w of newWords) {\n if (HtmlDiff.isStructuralTag(w)) {\n newStructural.push(Utils.stripTagAttributes(w))\n }\n }\n\n if (oldStructural.length !== newStructural.length) return true\n for (let i = 0; i < oldStructural.length; i++) {\n if (oldStructural[i] !== newStructural[i]) return true\n }\n return false\n }\n\n private performOperation(operation: Operation) {\n switch (operation.action) {\n case Action.Equal:\n this.processEqualOperation(operation)\n break\n case Action.Delete:\n this.processDeleteOperation(operation, 'diffdel')\n break\n case Action.Insert:\n this.processInsertOperation(operation, 'diffins')\n break\n case Action.None:\n break\n case Action.Replace:\n this.processReplaceOperation(operation)\n break\n }\n }\n\n private processReplaceOperation(operation: Operation) {\n this.processDeleteOperation(operation, 'diffmod')\n this.processInsertOperation(operation, 'diffmod')\n }\n\n private processInsertOperation(operation: Operation, cssClass: string) {\n const words = this.usingContentProjections()\n ? this.sliceOriginalWordsForOp('new', operation.startInNew, operation.endInNew)\n : this.newWords.slice(operation.startInNew, operation.endInNew)\n this.insertTag(HtmlDiff.InsTag, cssClass, words)\n }\n\n private processDeleteOperation(operation: Operation, cssClass: string) {\n const words = this.usingContentProjections()\n ? this.sliceOriginalWordsForOp('old', operation.startInOld, operation.endInOld)\n : this.oldWords.slice(operation.startInOld, operation.endInOld)\n this.insertTag(HtmlDiff.DelTag, cssClass, words)\n }\n\n private processEqualOperation(operation: Operation) {\n if (this.usingContentProjections()) {\n // Output from old to preserve old's HTML structure for the matched content.\n const result = this.sliceOriginalWordsForOp('old', operation.startInOld, operation.endInOld)\n this.content.push(result.join(''))\n\n // Advance new-side tracking past the equivalent range in new so the next insert op\n // resumes from the correct position. We compute new's range with the same rule used\n // for old (rather than mirroring old's count) so the two sides are independently sound\n // when their structural tags don't perfectly parallel each other.\n this.sliceOriginalWordsForOp('new', operation.startInNew, operation.endInNew)\n } else {\n const result = this.newWords.slice(operation.startInNew, operation.endInNew)\n this.content.push(result.join(''))\n }\n }\n\n /** True when content projections are active for both sides — i.e. structural normalization is in effect. */\n private usingContentProjections(): boolean {\n return this.oldContentToOriginal !== null && this.newContentToOriginal !== null\n }\n\n /**\n * Returns the slice of original (old or new) words covering a content-index range,\n * including the structural tags that surround the content. Advances the side's cursor\n * past the slice so the next op resumes correctly.\n *\n * The slice extends:\n * - LEADING: from the side's cursor (or the first content word's original index,\n * whichever is smaller) so structural tags that precede the first content word\n * are picked up by this op rather than left orphaned.\n * - TRAILING (non-last range): from just after the last content word, including\n * closing structural tags that close *this* op's paragraphs, but stopping at\n * the first opening structural tag — that opening tag belongs to the next\n * op's paragraph and would otherwise be emitted twice.\n * - TRAILING (last range): all the way to the end of words, since there is no next\n * op to claim the trailing tags.\n */\n private sliceOriginalWordsForOp(side: 'old' | 'new', contentStart: number, contentEnd: number): string[] {\n const words = side === 'old' ? this.oldWords : this.newWords\n const contentToOriginal = side === 'old' ? this.oldContentToOriginal : this.newContentToOriginal\n\n if (!contentToOriginal) return words.slice(contentStart, contentEnd)\n if (contentStart >= contentEnd) return []\n\n const firstContentOrigIdx = contentToOriginal[contentStart]\n const lastContentOrigIdx = contentToOriginal[contentEnd - 1]\n const cursor = side === 'old' ? this.lastOriginalOldOutputIndex : this.lastOriginalNewOutputIndex\n const origStart = Math.min(cursor, firstContentOrigIdx)\n\n let origEnd: number\n if (contentEnd < contentToOriginal.length) {\n // Non-last range: walk trailing tags after the last content word, stopping at the\n // first opening structural tag so it can be emitted by the next op.\n const limit = contentToOriginal[contentEnd]\n origEnd = lastContentOrigIdx + 1\n while (origEnd < limit && !HtmlDiff.isOpeningStructuralTag(words[origEnd])) {\n origEnd++\n }\n } else {\n // Last range: include everything to the end.\n origEnd = words.length\n }\n\n if (side === 'old') {\n this.lastOriginalOldOutputIndex = origEnd\n } else {\n this.lastOriginalNewOutputIndex = origEnd\n }\n\n return words.slice(origStart, origEnd)\n }\n\n /**\n * This method encloses words within a specified tag (ins or del), and adds this into \"content\",\n * with a twist: if there are words contain tags, it actually creates multiple ins or del,\n * so that they don't include any ins or del. This handles cases like\n * old: '<p>a</p>'\n * new: '<p>ab</p>\n * <p>\n * c</b>'\n * diff result: '<p>a<ins>b</ins></p>\n * <p>\n * <ins>c</ins>\n * </p>\n * '\n * this still doesn't guarantee valid HTML (hint: think about diffing a text containing ins or\n * del tags), but handles correctly more cases than the earlier version.\n * P.S.: Spare a thought for people who write HTML browsers. They live in this ... every day.\n * @param tag\n * @param cssClass\n * @param words\n * @private\n */\n private insertTag(tag: string, cssClass: string, words: string[], metadata?: WrapMetadata) {\n while (true) {\n if (words.length === 0) {\n break\n }\n\n const allWordsUntilFirstTag = this.extractConsecutiveWords(words, x => !Utils.isTag(x))\n if (allWordsUntilFirstTag.length > 0) {\n const text = Utils.wrapText(allWordsUntilFirstTag.join(''), tag, cssClass, metadata)\n this.content.push(text)\n }\n\n const isInsertOpCompleted = words.length === 0\n if (isInsertOpCompleted) {\n break\n }\n\n // if there are still words left, they must start with a tag, but still can contain nonTag entries.\n // e.g. </span></big>bar\n // the remaining words need to be handled separately divided in a tagBlock, which definitely contains\n // at least one word and a potentially existing second block which starts with a nonTag but may\n // contain tags later on.\n const indexOfFirstNonTag = words.findIndex(x => !Utils.isTag(x))\n\n // if there are no nonTags, the whole block is a tagBlock and the index of the last tag is the last index of the block.\n // if there are nonTags, the index of the last tag is the index before the first nonTag.\n const indexLastTagInFirstTagBlock = indexOfFirstNonTag === -1 ? words.length - 1 : indexOfFirstNonTag - 1\n\n // Pre-injection sits BEFORE the extracted tag-block content (used\n // by closing tags so `</ins></strong>` reads left-to-right).\n // Post-injection sits AFTER (used by opening tags so the rendered\n // order is `<strong><ins ...>` and by the overlap-split case so\n // the re-opened `<ins>`s sit AFTER the actual closing tag).\n let preInject = ''\n let postInject = ''\n\n // handle opening tag\n if (HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {\n const tagNames = new Set<string>()\n for (const word of words) {\n if (Utils.isTag(word)) {\n tagNames.add(Utils.getTagName(word))\n }\n }\n const styledTagNames = Array.from(tagNames).join(' ')\n\n // Carry the caller's metadata into the formatting-tag wrapper so\n // a 3-way author tag survives a `<strong>`/`<em>` content edit.\n const styledCssClass = `mod ${styledTagNames}`\n this.specialTagDiffStack.push({ tag: words[0], styledTagNames, cssClass: styledCssClass, metadata })\n postInject = `<ins${Utils.composeTagAttributes(styledCssClass, metadata ?? {})}>`\n if (tag === HtmlDiff.DelTag) {\n words.shift()\n\n // following tags may be formatting tags as well, follow through\n while (words.length > 0 && HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {\n words.shift()\n }\n }\n }\n // handle closing tag\n else if (HtmlDiff.SpecialCaseClosingTagsSet.has(words[0].toLowerCase())) {\n // For delete operations: when the tag block contains a mix of formatting and\n // non-formatting closing tags (e.g. </strong></div>), compare against the first\n // closing tag (the formatting one) rather than the last tag in the block.\n // For purely formatting tag blocks (e.g. </i></strong>) or insert operations,\n // use the last tag as before to match against the outermost opening tag.\n let tagIndexToCompare = indexLastTagInFirstTagBlock\n if (tag === HtmlDiff.DelTag && indexOfFirstNonTag === -1) {\n const hasNonFormattingClosingTag = words\n .slice(0, indexLastTagInFirstTagBlock + 1)\n .some(w => !HtmlDiff.SpecialCaseClosingTagsSet.has(w.toLowerCase()))\n if (hasNonFormattingClosingTag) {\n tagIndexToCompare = 0\n }\n }\n\n // Search the stack for a matching opener (LIFO). When the match\n // is the top entry, this is the normal balanced case and we\n // emit a single `</ins>` before the closing tag. When the match\n // is below an unmatched opener — i.e. another formatting wrap\n // opened after it but hasn't been closed yet — the wraps\n // overlap in source order, which has no valid LIFO HTML\n // expression. Resolve by SPLITTING the wraps: close everything\n // above the match (their `<ins>`s and the match's `<ins>`), then\n // re-open the above wraps with fresh `<ins>` tags AFTER the\n // closing tag emits. The above wraps continue to apply until\n // their own closing tag arrives.\n const closingTagName = Utils.getTagName(words[tagIndexToCompare])\n let matchIdx = -1\n for (let i = this.specialTagDiffStack.length - 1; i >= 0; i--) {\n if (Utils.getTagName(this.specialTagDiffStack[i].tag) === closingTagName) {\n matchIdx = i\n break\n }\n }\n\n if (matchIdx >= 0) {\n const aboveEntries = this.specialTagDiffStack.splice(matchIdx + 1)\n this.specialTagDiffStack.pop() // pop the matched entry\n // One `</ins>` per above entry, then one for the match itself.\n preInject = '</ins>'.repeat(aboveEntries.length + 1)\n for (const entry of aboveEntries) {\n postInject += `<ins${Utils.composeTagAttributes(entry.cssClass, entry.metadata ?? {})}>`\n this.specialTagDiffStack.push(entry) // their wrap continues via the new <ins>\n }\n }\n // No match in stack — orphan closing tag, drop the `<ins>` work\n // and just let the tag itself flow through extractConsecutiveWords.\n\n if (tag === HtmlDiff.DelTag) {\n words.shift()\n // following tags may be formatting tags as well, follow through\n while (words.length > 0 && HtmlDiff.SpecialCaseClosingTagsSet.has(words[0].toLowerCase())) {\n words.shift()\n }\n }\n }\n\n if (words.length === 0 && preInject.length === 0 && postInject.length === 0) {\n break\n }\n\n // For delete operations, only extract non-formatting tags. Formatting tags (special case\n // opening/closing tags) need to be handled in the next loop iteration so they go through\n // the proper specialTagDiffStack logic. Otherwise, opening formatting tags get output as\n // plain tags and their corresponding closing tags are later discarded, producing invalid HTML.\n const isTagForExtraction =\n tag === HtmlDiff.DelTag\n ? (x: string) =>\n Utils.isTag(x) &&\n !HtmlDiff.SpecialCaseOpeningTagRegex.test(x) &&\n !HtmlDiff.SpecialCaseClosingTagsSet.has(x.toLowerCase())\n : Utils.isTag\n\n this.content.push(preInject + this.extractConsecutiveWords(words, isTagForExtraction).join('') + postInject)\n\n if (words.length === 0) continue\n\n // if there are still words left, they must start with a nonTag and need to be handled in the next iteration.\n this.insertTag(tag, cssClass, words, metadata)\n break\n }\n }\n\n private extractConsecutiveWords(words: string[], condition: (character: string) => boolean): string[] {\n let indexOfFirstTag: number | null = null\n for (let i = 0; i < words.length; i++) {\n const word = words[i]\n if (i === 0 && word === ' ') {\n words[i] = '&nbsp;'\n }\n if (!condition(word)) {\n indexOfFirstTag = i\n break\n }\n }\n\n if (indexOfFirstTag !== null) {\n const items = words.slice(0, indexOfFirstTag)\n if (indexOfFirstTag > 0) {\n words.splice(0, indexOfFirstTag)\n }\n return items\n }\n\n const items = words.slice(0)\n words.splice(0, words.length)\n return items\n }\n\n private operations(): Operation[] {\n let positionInOld = 0\n let positionInNew = 0\n const operations: Operation[] = []\n\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n const matches = this.matchingBlocks()\n matches.push(new Match(wordsForDiffOld.length, wordsForDiffNew.length, 0))\n\n //Remove orphans from matches.\n //If distance between left and right matches is 4 times longer than length of current match then it is considered as orphan\n const matchesWithoutOrphans = this.removeOrphans(matches)\n\n for (const match of matchesWithoutOrphans) {\n const matchStartsAtCurrentPositionInOld = positionInOld === match.startInOld\n const matchStartsAtCurrentPositionInNew = positionInNew === match.startInNew\n\n let action: Action\n\n if (!matchStartsAtCurrentPositionInOld && !matchStartsAtCurrentPositionInNew) {\n action = Action.Replace\n } else if (matchStartsAtCurrentPositionInOld && !matchStartsAtCurrentPositionInNew) {\n action = Action.Insert\n } else if (!matchStartsAtCurrentPositionInOld) {\n action = Action.Delete\n } // This occurs if the first few words are the same in both versions\n else {\n action = Action.None\n }\n\n if (action !== Action.None) {\n operations.push(new Operation(action, positionInOld, match.startInOld, positionInNew, match.startInNew))\n }\n\n if (match.size !== 0) {\n operations.push(new Operation(Action.Equal, match.startInOld, match.endInOld, match.startInNew, match.endInNew))\n }\n\n positionInOld = match.endInOld\n positionInNew = match.endInNew\n }\n\n return operations\n }\n\n private *removeOrphans(matches: Match[]) {\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n let prev: Match = new Match(0, 0, 0)\n let curr: Match | null = null\n\n for (const next of matches) {\n if (curr === null) {\n curr = next\n continue\n }\n\n if (\n (prev.endInOld === curr.startInOld && prev.endInNew === curr.startInNew) ||\n (curr.endInOld === next.startInOld && curr.endInNew === next.startInNew)\n ) {\n //if match has no diff on the left or on the right\n yield curr\n prev = curr\n curr = next\n continue\n }\n\n // Never orphan-reject a match whose tokens are ALL HTML tags.\n // Tag tokens are structural; rejecting `</strong>` / `</em>` as\n // an orphan match between two content deletions merges the tag\n // into the deletion, leaving the matching opener unclosed —\n // browsers then auto-close the opener at the END of the\n // deletion, producing visually-wrong output (e.g. the body of\n // a section deletion rendered as bold-italic because the\n // closing `</strong></em>` ended up after the body deletion\n // rather than after the heading). The orphan threshold is\n // designed for stray word matches between heavily-edited spans,\n // not for formatting boundaries.\n let allTags = true\n for (let i = curr.startInNew; i < curr.endInNew; i++) {\n if (!Utils.isTag(wordsForDiffNew[i])) {\n allTags = false\n break\n }\n }\n if (allTags) {\n yield curr\n prev = curr\n curr = next\n continue\n }\n\n let oldDistanceInChars = 0\n for (let i = prev.endInOld; i < next.startInOld; i++) {\n oldDistanceInChars += wordsForDiffOld[i].length\n }\n let newDistanceInChars = 0\n for (let i = prev.endInNew; i < next.startInNew; i++) {\n newDistanceInChars += wordsForDiffNew[i].length\n }\n let currMatchLengthInChars = 0\n for (let i = curr.startInNew; i < curr.endInNew; i++) {\n currMatchLengthInChars += wordsForDiffNew[i].length\n }\n\n if (currMatchLengthInChars > Math.max(oldDistanceInChars, newDistanceInChars) * this.orphanMatchThreshold) {\n yield curr\n }\n\n prev = curr\n curr = next\n }\n\n if (curr !== null) {\n yield curr //assume that the last match is always vital\n }\n }\n\n private matchingBlocks(): Match[] {\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n const matchingBlocks: Match[] = []\n this.findMatchingBlocks(0, wordsForDiffOld.length, 0, wordsForDiffNew.length, matchingBlocks)\n return matchingBlocks\n }\n\n private findMatchingBlocks(\n startInOld: number,\n endInOld: number,\n startInNew: number,\n endInNew: number,\n matchingBlocks: Match[]\n ) {\n const match = this.findMatch(startInOld, endInOld, startInNew, endInNew)\n\n if (match !== null) {\n if (startInOld < match.startInOld && startInNew < match.startInNew) {\n this.findMatchingBlocks(startInOld, match.startInOld, startInNew, match.startInNew, matchingBlocks)\n }\n\n matchingBlocks.push(match)\n\n if (match.endInOld < endInOld && match.endInNew < endInNew) {\n this.findMatchingBlocks(match.endInOld, endInOld, match.endInNew, endInNew, matchingBlocks)\n }\n }\n }\n\n private findMatch(startInOld: number, endInOld: number, startInNew: number, endInNew: number): Match | null {\n const wordsForDiffOld = this.oldContentWords ?? this.oldWords\n const wordsForDiffNew = this.newContentWords ?? this.newWords\n\n // For large texts it is more likely that there is a Match of size bigger than maximum granularity.\n // If not then go down and try to find it with smaller granularity.\n for (let i = this.matchGranularity; i > 0; i--) {\n const options = {\n blockSize: i,\n repeatingWordsAccuracy: this.repeatingWordsAccuracy,\n ignoreWhitespaceDifferences: this.ignoreWhitespaceDifferences,\n }\n const finder = new MatchFinder(\n wordsForDiffOld,\n wordsForDiffNew,\n startInOld,\n endInOld,\n startInNew,\n endInNew,\n options\n )\n const match = finder.findMatch()\n if (match !== null) return match\n }\n return null\n }\n}\n"],"mappings":"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"}