@createiq/htmldiff 1.1.0 → 1.2.0-beta.0
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.
- package/.claude/settings.local.json +15 -0
- package/README.md +40 -0
- package/dist/HtmlDiff.cjs +1255 -493
- package/dist/HtmlDiff.cjs.map +1 -1
- package/dist/HtmlDiff.d.cts +141 -7
- package/dist/HtmlDiff.d.mts +140 -7
- package/dist/HtmlDiff.mjs +1255 -493
- package/dist/HtmlDiff.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Alignment.ts +349 -0
- package/src/HtmlDiff.ts +323 -33
- package/src/HtmlScanner.ts +200 -0
- package/src/TableDiff.ts +67 -522
- package/src/ThreeWayDiff.ts +223 -0
- package/src/ThreeWayTable.ts +701 -0
- package/src/Utils.ts +34 -2
- package/test/HtmlDiff.analyze.spec.ts +152 -0
- package/test/HtmlDiff.tables.spec.ts +43 -19
- package/test/HtmlDiff.threeWay.spec.ts +175 -0
- package/test/HtmlDiff.threeWay.tables.spec.ts +407 -0
- package/test/TableDiff.bench.ts +39 -0
- package/test/Utils.spec.ts +48 -0
package/dist/HtmlDiff.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HtmlDiff.cjs","names":["Utils","Utils","Utils"],"sources":["../src/Match.ts","../src/Utils.ts","../src/MatchFinder.ts","../src/Operation.ts","../src/TableDiff.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| )+$/\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\nexport function wrapText(text: string, tagName: string, cssClass: string): string {\n return `<${tagName} class='${cssClass}'>${text}</${tagName}>`\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 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","import { 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\ninterface 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\ninterface RowRange {\n rowStart: number\n rowEnd: number\n cells: CellRange[]\n}\n\ninterface 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 combinatorial column-position search in\n// findBestColumnInsertPositions / findBestColumnDeletePositions. Worst\n// case is C(MAX_COLUMN_SEARCH_WIDTH, MAX_COLUMN_DELTA) ≈ 3.8M combos at\n// the caps below; wider or more-skewed rows fall through to cell-LCS.\nconst MAX_COLUMN_DELTA = 6\nconst MAX_COLUMN_SEARCH_WIDTH = 40\n\nfunction makePlaceholderPrefix(oldHtml: string, newHtml: 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 either 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 (!oldHtml.includes(prefix) && !newHtml.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\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\nfunction spliceString(s: string, start: number, end: number, replacement: string): string {\n return s.slice(0, start) + replacement + s.slice(end)\n}\n\nfunction 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\nfunction 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\n/**\n * Reorders the alignment so emission produces rows in the visually-\n * correct order. Each entry is assigned a fractional \"position\" in\n * new's 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 row order. The +0.5 places dels BEFORE any\n * insert at the same gap (insert at newIdx N1+1 has position N1+1\n * which is > N1+0.5), giving the natural \"delete first, insert\n * second\" reading order at a replaced position.\n *\n * This 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 row.\n * • Dels at the end (no preserved successor): positioned after the\n * last preserved row.\n *\n * Without this reordering, a run of unpaired deletes at low alignment\n * indices got emitted at cursor = first-new-row position — putting\n * all deletes before any preserved row in the output, regardless of\n * where they came from in old.\n */\nfunction 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 row 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 row 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\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\nfunction 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: combinatorial search is C(newCount, k); we cap to avoid\n // explosion on very wide tables. Worst case at the caps is C(40, 6) ≈\n // 3.8M combos; above that we fall through to 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, delta, diffCell)\n return diffMultiColumnDeleteRow(oldHtml, newHtml, oldRow, newRow, -delta, diffCell)\n }\n return diffStructurallyAlignedRow(oldHtml, newHtml, oldRow, newRow, diffCell)\n}\n\n/**\n * For a row where new has K more cells than old, find the K column\n * positions in new where cells were inserted by scanning all C(newCount,\n * K) combinations and picking the one that maximises positional content\n * similarity with the remaining cells. The inserted cells are emitted\n * with diff markers; the rest are aligned positionally with content\n * diff for matched pairs.\n */\nfunction diffMultiColumnAddRow(\n oldHtml: string,\n newHtml: string,\n oldRow: RowRange,\n newRow: RowRange,\n k: number,\n diffCell: DiffCellFn\n): string {\n const insertedPositions = findBestColumnInsertPositions(oldRow, newRow, k, 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 k: number,\n diffCell: DiffCellFn\n): string {\n const deletedPositions = findBestColumnDeletePositions(oldRow, newRow, k, 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(\n oldRow: RowRange,\n newRow: RowRange,\n k: number,\n oldHtml: string,\n newHtml: string\n): number[] {\n // Pre-compute cell texts once instead of letting textSimilarity\n // recompute them inside every combo iteration — C(N, K) combos times\n // ~N text extractions each is a lot of wasted string work.\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n let bestPositions: number[] = []\n let bestScore = -1\n for (const combo of combinationsOfRange(newRow.cells.length, k)) {\n const inserted = new Set(combo)\n let score = 0\n let oldIdx = 0\n for (let newIdx = 0; newIdx < newRow.cells.length; newIdx++) {\n if (inserted.has(newIdx)) continue\n score += textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n oldIdx++\n }\n if (score > bestScore) {\n bestScore = score\n bestPositions = combo\n }\n }\n return bestPositions\n}\n\nfunction findBestColumnDeletePositions(\n oldRow: RowRange,\n newRow: RowRange,\n k: number,\n oldHtml: string,\n newHtml: string\n): number[] {\n const oldTexts = oldRow.cells.map(c => cellText(oldHtml, c))\n const newTexts = newRow.cells.map(c => cellText(newHtml, c))\n let bestPositions: number[] = []\n let bestScore = -1\n for (const combo of combinationsOfRange(oldRow.cells.length, k)) {\n const deleted = new Set(combo)\n let score = 0\n let newIdx = 0\n for (let oldIdx = 0; oldIdx < oldRow.cells.length; oldIdx++) {\n if (deleted.has(oldIdx)) continue\n score += textSimilarity(oldTexts[oldIdx], newTexts[newIdx])\n newIdx++\n }\n if (score > bestScore) {\n bestScore = score\n bestPositions = combo\n }\n }\n return bestPositions\n}\n\n/**\n * Yields all sorted-ascending combinations of `k` distinct integers\n * from [0, n). Iterative implementation avoids recursion overhead and\n * keeps memory at O(k).\n */\nfunction* combinationsOfRange(n: number, k: number): IterableIterator<number[]> {\n if (k === 0 || k > n) return\n const indices = Array.from({ length: k }, (_, i) => i)\n while (true) {\n yield indices.slice()\n let i = k - 1\n while (i >= 0 && indices[i] === n - k + i) i--\n if (i < 0) return\n indices[i]++\n for (let j = i + 1; j < k; j++) indices[j] = indices[j - 1] + 1\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\ninterface Alignment {\n oldIdx: number | null\n newIdx: number | null\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\n/**\n * Identify pairings inside each unmatched-only run, then build the output\n * alignment by walking the original and substituting paired entries at\n * the *ins position* (not the del position). This keeps the result\n * monotonic in newIdx — critical because the cursor-based emission\n * downstream walks new's html in order. Emitting at the del position\n * would be fine when del<ins in the alignment array (the typical case),\n * but can violate monotonicity when there are mixed unpaired entries in\n * between (column-add + row-add together, content-edit + column-add,\n * etc.).\n *\n * Generic over what's being paired — works for both rows (by full row\n * content similarity) and cells (by per-cell content similarity).\n */\nfunction 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 * Combined similarity metric used for both row-level and cell-level\n * fuzzy pairing. Returns the MAX of 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 in a row).\n * Misses cases where the bulk of common content is in the middle\n * and the ends 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. a row whose only\n * edit is a column added at the start and another at the end,\n * where the ~50 chars in the middle that DO match would be\n * invisible to 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 */\nfunction 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\nfunction 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 * 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 */\nfunction 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 * 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 */\nfunction 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 */\nfunction findClassAttribute(openingTag: string): { valueStart: number; valueEnd: number; value: string } | 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\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 */\nfunction 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\nfunction 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\nfunction 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\ninterface OpeningTag {\n /** Index just past the closing `>` of the opening tag. */\n end: number\n}\n\nfunction parseOpeningTagAt(html: string, i: number): OpeningTag | null {\n // HTML comments, CDATA, processing instructions, and DOCTYPE need their\n // own terminators — a plain `>`-walker would cut a comment like\n // `<!-- a > b -->` at the first inner `>`, treating the rest as text\n // and corrupting downstream offsets. Word-exported HTML routinely\n // emits comments inside tables (conditional comments, OLE markers) so\n // these have to be handled, not just be theoretical.\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\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 */\nfunction findMatchingClosingTag(html: string, from: number, tagName: string, limit: number = html.length): 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","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 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 Utils from './Utils'\nimport WordSplitter from './WordSplitter'\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 private content: string[] = []\n private newText: string\n private oldText: string\n private readonly tablePreprocessDepth: number\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 * <del>aaaaa bb ccccccccc</del><ins>11111 bb 222222222</ins> 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 * @param tablePreprocessDepth Internal: nested-call depth for table\n * preprocessing. Callers should leave at default (0); the recursive\n * `diffCell` callback in TableDiff bumps it.\n */\n constructor(oldText: string, newText: string, tablePreprocessDepth = 0) {\n this.oldText = oldText\n this.newText = newText\n this.tablePreprocessDepth = tablePreprocessDepth\n }\n\n static execute(oldText: string, newText: string, tablePreprocessDepth = 0) {\n return new HtmlDiff(oldText, newText, tablePreprocessDepth).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 guarded by MaxTablePreprocessDepth to bound work on\n // deeply-nested table-in-cell-in-table inputs. Caller-configured\n // settings (block expressions, accuracy thresholds) are propagated to\n // the recursive cell diff so cell-level output is consistent with the\n // top-level configuration.\n const blockExpressions = this.blockExpressions\n const repeatingWordsAccuracy = this.repeatingWordsAccuracy\n const orphanMatchThreshold = this.orphanMatchThreshold\n const ignoreWhitespaceDifferences = this.ignoreWhitespaceDifferences\n const tablePreprocess =\n this.tablePreprocessDepth >= HtmlDiff.MaxTablePreprocessDepth\n ? null\n : preprocessTables(this.oldText, this.newText, (oldCell, newCell) => {\n const inner = new HtmlDiff(oldCell, newCell, this.tablePreprocessDepth + 1)\n for (const expr of blockExpressions) inner.addBlockExpression(expr)\n inner.repeatingWordsAccuracy = repeatingWordsAccuracy\n inner.orphanMatchThreshold = orphanMatchThreshold\n inner.ignoreWhitespaceDifferences = ignoreWhitespaceDifferences\n return inner.build()\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[]) {\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)\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 specialCaseTagInjection = `<ins class='mod ${styledTagNames}'>`\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)\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] = ' '\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| )+$/\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(V1, V2) (CP's changes) and diff(V2, V3) (Me's changes)\n * into a single attributed segment stream. The output is consumed by\n * `HtmlDiff.executeThreeWay` for emission.\n *\n * V2 is the structural spine. Both pair-wise analyses must tokenise V2\n * identically (`HtmlDiff.executeThreeWay` enforces this via the\n * symmetric-projection decision), so V2-diff indices are stable across\n * the two streams and we can fold them into a single per-V2-token\n * attribution view, interleaved with off-spine CP-deletions (V1-side)\n * and Me-insertions (V3-side).\n */\n\nexport type Author = 'cp' | 'me'\n\n/**\n * Attribution assigned to each output segment. `reject` is its own kind\n * (rather than a flavour of `del`) so exhaustive switching is safe — no\n * property-presence narrowing required at use sites.\n */\nexport type Attribution =\n | { kind: 'equal' }\n | { kind: 'ins'; author: Author }\n | { kind: 'del'; author: Author }\n // Me deleting tokens that CP inserted = rejecting CP's proposal.\n | { kind: 'reject'; by: 'me'; rejected: 'cp' }\n\nexport interface Segment {\n attr: Attribution\n /** Tokens to emit. For Equal segments these are original V2 words\n * (including structural tags); for ins/del they are diff-space tokens. */\n words: string[]\n}\n\nexport function buildSegments(d1: AnalyzeResult, d2: AnalyzeResult): Segment[] {\n const v2DiffLen = d1.newDiffWords.length\n const fromV1 = buildOriginMap(d1.operations, v2DiffLen)\n const toV3 = buildFateMap(d2.operations, v2DiffLen)\n const cpDeletionsAt = collectDeletionsAtBoundary(d1)\n const meInsertionsAt = collectInsertionsAtBoundary(d2)\n\n // Inverse map V2-diff-index → V2-original-index. Identity when no projection.\n const diffToOriginal: readonly number[] = d1.newContentToOriginal ?? Array.from({ length: v2DiffLen }, (_, i) => i)\n const v2OriginalLen = d1.newOriginalWords.length\n\n const segments: Segment[] = []\n let originalCursor = 0\n\n for (let i = 0; i < v2DiffLen; i++) {\n // CP-deletions from V1 land BEFORE the V2 token at this boundary —\n // they conceptually \"preceded\" V2[i] in V1's stream.\n const cpDel = cpDeletionsAt.get(i)\n if (cpDel?.length) appendSegment(segments, { kind: 'del', author: 'cp' }, cpDel)\n\n const attr = combine(fromV1[i], toV3[i])\n const origIdx = diffToOriginal[i]\n const slice = d1.newOriginalWords.slice(originalCursor, origIdx + 1)\n originalCursor = origIdx + 1\n\n // Me-insertions at this boundary go BEFORE V2[i] for pure\n // insertions, but AFTER V2[i] when V2[i] is itself a Me-deletion\n // (i.e. a Me Replace). This mirrors the 2-way del-then-ins\n // convention so a Replace reads as `<del>X</del><ins>Y</ins>`.\n const meIns = meInsertionsAt.get(i)\n const meInsAfterV2 = meIns?.length && isDeletion(attr)\n\n if (meIns?.length && !meInsAfterV2) {\n appendSegment(segments, { kind: 'ins', author: 'me' }, meIns)\n }\n appendSegment(segments, attr, slice)\n if (meInsAfterV2) {\n appendSegment(segments, { kind: 'ins', author: 'me' }, meIns)\n }\n }\n // Tail-end interleavings (CP-del / Me-ins at boundary v2DiffLen — i.e.\n // after every V2 token). Ordering doesn't matter since there's no\n // V2 token to anchor around.\n const tailCpDel = cpDeletionsAt.get(v2DiffLen)\n if (tailCpDel?.length) appendSegment(segments, { kind: 'del', author: 'cp' }, tailCpDel)\n const tailMeIns = meInsertionsAt.get(v2DiffLen)\n if (tailMeIns?.length) appendSegment(segments, { kind: 'ins', author: 'me' }, tailMeIns)\n\n // Trailing V2-original tokens (structural closing tags after the last\n // content word). Emit as equal — there's no following segment to claim\n // them, and attributing them to either author would be arbitrary.\n if (originalCursor < v2OriginalLen) {\n appendSegment(segments, { kind: 'equal' }, d1.newOriginalWords.slice(originalCursor))\n }\n\n return segments\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n\ntype V2Origin = 'preserved-from-v1' | 'inserted-by-cp' | 'replaced-into-by-cp'\ntype V2Fate = 'preserved-to-v3' | 'deleted-by-me' | 'replaced-out-by-me'\n\nfunction buildOriginMap(ops: readonly Operation[], v2Len: number): V2Origin[] {\n const out: V2Origin[] = new Array(v2Len).fill('preserved-from-v1')\n for (const op of ops) {\n const origin =\n op.action === Action.Insert ? 'inserted-by-cp' : op.action === Action.Replace ? 'replaced-into-by-cp' : null\n if (origin === null) continue\n for (let i = op.startInNew; i < op.endInNew; i++) {\n if (i >= 0 && i < v2Len) out[i] = origin\n }\n }\n return out\n}\n\nfunction buildFateMap(ops: readonly Operation[], v2Len: number): V2Fate[] {\n const out: V2Fate[] = new Array(v2Len).fill('preserved-to-v3')\n for (const op of ops) {\n const fate =\n op.action === Action.Delete ? 'deleted-by-me' : op.action === Action.Replace ? 'replaced-out-by-me' : null\n if (fate === null) continue\n for (let i = op.startInOld; i < op.endInOld; i++) {\n if (i >= 0 && i < v2Len) out[i] = fate\n }\n }\n return out\n}\n\nfunction isDeletion(attr: Attribution): boolean {\n return attr.kind === 'del' || attr.kind === 'reject'\n}\n\nfunction combine(origin: V2Origin, fate: V2Fate): Attribution {\n const cpInserted = origin === 'inserted-by-cp' || origin === 'replaced-into-by-cp'\n const meDeleted = fate === 'deleted-by-me' || fate === 'replaced-out-by-me'\n if (!cpInserted && !meDeleted) return { kind: 'equal' }\n if (cpInserted && !meDeleted) return { kind: 'ins', author: 'cp' }\n if (!cpInserted && meDeleted) return { kind: 'del', author: 'me' }\n return { kind: 'reject', by: 'me', rejected: 'cp' }\n}\n\n/**\n * Map V2-diff-boundary → CP-deleted V1 tokens at that boundary. Includes\n * both pure Delete ops and the V1-side of Replace ops (semantically a\n * Delete+Insert; the Insert half is picked up by the V2-token walk).\n */\nfunction collectDeletionsAtBoundary(d: AnalyzeResult): Map<number, string[]> {\n const out = new Map<number, string[]>()\n for (const op of d.operations) {\n if (op.action !== Action.Delete && op.action !== Action.Replace) continue\n const words = d.oldDiffWords.slice(op.startInOld, op.endInOld)\n if (words.length === 0) continue\n const existing = out.get(op.startInNew) ?? []\n existing.push(...words)\n out.set(op.startInNew, existing)\n }\n return out\n}\n\nfunction collectInsertionsAtBoundary(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 existing = out.get(op.startInOld) ?? []\n existing.push(...words)\n out.set(op.startInOld, existing)\n }\n return out\n}\n\nfunction appendSegment(segments: Segment[], attr: Attribution, words: 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 if (a.kind === 'reject' && b.kind === 'reject') return true\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, rejects?: Author): WrapMetadata {\n const dataAttrs: Record<string, string> = { author }\n if (rejects !== undefined) dataAttrs.rejects = rejects\n const extraClasses = rejects !== undefined ? `${author} rejects-${rejects}` : author\n return { extraClasses, dataAttrs }\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 */\nexport function segmentEmissionShape(attr: Exclude<Attribution, { kind: 'equal' }>): {\n tag: 'ins' | 'del'\n baseClass: 'diffins' | 'diffdel'\n metadata: WrapMetadata\n} {\n switch (attr.kind) {\n case 'ins':\n return { tag: 'ins', baseClass: 'diffins', metadata: authorAttribution(attr.author) }\n case 'del':\n return { tag: 'del', baseClass: 'diffdel', metadata: authorAttribution(attr.author) }\n case 'reject':\n return { tag: 'del', baseClass: 'diffdel', metadata: authorAttribution(attr.by, attr.rejected) }\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. Same shape as the existing two-way\n * `preprocessTables` but takes V1/V2/V3 and a cell-level three-way diff\n * callback. All three inputs share a single placeholder nonce so V2's\n * tokenisation is identical when the word-level 3-way merger sees it\n * from both pair-wise analyses.\n *\n * This commit handles only the same-dimensions positional case across\n * all three table triples. The structural-change case (rows/cells\n * differ between any pair) throws; the next commit replaces that with\n * a row-level V2-spine merge that mirrors the word-level approach.\n * Multi-table count divergence (CP added or Me removed a whole table)\n * is handled in commit 6 (D3).\n */\n\nexport interface ThreeWayPreprocessResult {\n modifiedV1: string\n modifiedV2: string\n modifiedV3: string\n placeholderToDiff: Map<string, string>\n}\n\nexport type ThreeWayDiffCellFn = (v1Cell: string, v2Cell: string, v3Cell: string) => string\n\nexport function preprocessTablesThreeWay(\n v1: string,\n v2: string,\n v3: string,\n cellDiff: ThreeWayDiffCellFn\n): ThreeWayPreprocessResult | null {\n const t1s = findTopLevelTables(v1)\n const t2s = findTopLevelTables(v2)\n const t3s = findTopLevelTables(v3)\n\n // No tables in any input — caller can skip preprocessing entirely.\n if (t1s.length === 0 && t2s.length === 0 && t3s.length === 0) return null\n\n // Size cap: bail to word-level diff for pathologically large tables.\n for (const t of t1s) if (exceedsSizeLimit(t)) return null\n for (const t of t2s) if (exceedsSizeLimit(t)) return null\n for (const t of t3s) if (exceedsSizeLimit(t)) return null\n\n const placeholderPrefix = makePlaceholderPrefix(v1, v2, v3)\n\n // Fast path: counts match AND each positional triple looks similar\n // enough that 1:1 positional pairing is sound. The similarity gate\n // catches the swap case — V1=[A,B], V2=[B,A] has matching counts but\n // positionally pairing would mis-attribute. Without the gate, a swap\n // would silently land in the per-cell diff machinery comparing\n // unrelated tables.\n if (positionallyAligned(v1, v2, v3, t1s, t2s, t3s)) {\n return preprocessAlignedByPosition(v1, v2, v3, t1s, t2s, t3s, cellDiff, placeholderPrefix)\n }\n\n // Multi-table mismatch (D3). CP added/removed/moved a table, Me added/\n // removed/moved a table, etc. Use content-LCS to pair tables across\n // each adjacent pair, then assign placeholders so the word-level 3-way\n // merger naturally attributes unpaired tables — the placeholder token\n // appears only in the inputs where the table exists, and the merger\n // sees that as an insertion/deletion.\n return preprocessMisalignedByContent(v1, v2, v3, t1s, t2s, t3s, cellDiff, placeholderPrefix)\n}\n\nfunction preprocessAlignedByPosition(\n v1: string,\n v2: string,\n v3: string,\n t1s: TableRange[],\n t2s: TableRange[],\n t3s: TableRange[],\n cellDiff: ThreeWayDiffCellFn,\n placeholderPrefix: string\n): ThreeWayPreprocessResult {\n const pairs: Array<{\n t1: TableRange\n t2: TableRange\n t3: TableRange\n diffed: string\n }> = []\n for (let i = 0; i < t1s.length; i++) {\n pairs.push({\n t1: t1s[i],\n t2: t2s[i],\n t3: t3s[i],\n diffed: diffTableThreeWay(v1, v2, v3, t1s[i], t2s[i], t3s[i], cellDiff),\n })\n }\n let modifiedV1 = v1\n let modifiedV2 = v2\n let modifiedV3 = v3\n const placeholderToDiff = new Map<string, string>()\n // Splice end → start so earlier offsets stay valid.\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 modifiedV1 = spliceString(modifiedV1, pairs[i].t1.tableStart, pairs[i].t1.tableEnd, placeholder)\n modifiedV2 = spliceString(modifiedV2, pairs[i].t2.tableStart, pairs[i].t2.tableEnd, placeholder)\n modifiedV3 = spliceString(modifiedV3, pairs[i].t3.tableStart, pairs[i].t3.tableEnd, placeholder)\n }\n return { modifiedV1, modifiedV2, modifiedV3, placeholderToDiff }\n}\n\n/**\n * Multi-table mismatch handler. Tables are paired across V1↔V2 and\n * V2↔V3 via content-LCS, then substituted as placeholders such that\n * each placeholder appears in exactly the inputs where its underlying\n * table exists. The word-level merger sees:\n * - paired-everywhere placeholders → equal in both diffs → unwrapped\n * - V2-only (CP-inserted + Me-rejected) → inserted by CP, deleted by\n * Me → reject wrapper around the table\n * - V2+V3 (CP-inserted, Me-kept) → ins-cp wrapper\n * - V1+V2 (Me-deleted) → del-me wrapper\n * - V1-only (CP-deleted before V2) → del-cp wrapper\n * - V3-only (Me-inserted) → ins-me wrapper\n *\n * Each placeholder's content is the diffed table for paired triples,\n * or the raw table HTML for unpaired tables (the word-level wrapper\n * provides the attribution).\n */\nfunction preprocessMisalignedByContent(\n v1: string,\n v2: string,\n v3: string,\n t1s: TableRange[],\n t2s: TableRange[],\n t3s: TableRange[],\n cellDiff: ThreeWayDiffCellFn,\n placeholderPrefix: string\n): ThreeWayPreprocessResult {\n const k1 = t1s.map(t => tableKey(v1, t))\n const k2 = t2s.map(t => tableKey(v2, t))\n const k3 = t3s.map(t => tableKey(v3, t))\n\n const align12 = lcsAlign(k1, k2)\n const align23 = lcsAlign(k2, k3)\n\n // Maps from table-index → counterpart in the other input (or -1).\n const v1ToV2 = new Array<number>(t1s.length).fill(-1)\n const v2ToV1 = new Array<number>(t2s.length).fill(-1)\n for (const a of align12) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n v1ToV2[a.oldIdx] = a.newIdx\n v2ToV1[a.newIdx] = a.oldIdx\n }\n }\n const v2ToV3 = new Array<number>(t2s.length).fill(-1)\n const v3ToV2 = new Array<number>(t3s.length).fill(-1)\n for (const a of align23) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n v2ToV3[a.oldIdx] = a.newIdx\n v3ToV2[a.newIdx] = a.oldIdx\n }\n }\n\n // Allocate placeholders. Each logical-table-position (paired triple,\n // paired pair, or singleton) gets one shared placeholder used in\n // every input that contains it.\n let nextId = 0\n const placeholderToDiff = new Map<string, string>()\n const placeholders = {\n v1: new Array<string | null>(t1s.length).fill(null),\n v2: new Array<string | null>(t2s.length).fill(null),\n v3: new Array<string | null>(t3s.length).fill(null),\n }\n\n const allocate = (): string => `${placeholderPrefix}${nextId++}${PLACEHOLDER_SUFFIX}`\n\n // 1. Triples paired through V2 (preserved in both V1↔V2 AND V2↔V3) — full 3-way diff.\n for (let v2Idx = 0; v2Idx < t2s.length; v2Idx++) {\n const v1Idx = v2ToV1[v2Idx]\n const v3Idx = v2ToV3[v2Idx]\n if (v1Idx === -1 || v3Idx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, diffTableThreeWay(v1, v2, v3, t1s[v1Idx], t2s[v2Idx], t3s[v3Idx], cellDiff))\n placeholders.v1[v1Idx] = placeholder\n placeholders.v2[v2Idx] = placeholder\n placeholders.v3[v3Idx] = placeholder\n }\n\n // For unpaired placeholders the word-level merger can't wrap a tag\n // token (insertTag emits tags verbatim), so we bake the author\n // attribution directly into the placeholder content. The merger then\n // only has to position the placeholder via word-level alignment;\n // the attribution wrapping is already in the substituted HTML.\n const wrapWhole = (tag: 'ins' | 'del', author: Author, tableHtml: string, rejects?: Author): string =>\n Utils.wrapText(tableHtml, tag, `diff${tag}`, authorAttribution(author, rejects))\n\n // 2. V2 tables paired only with V3 (CP-inserted into V2, Me-kept).\n for (let v2Idx = 0; v2Idx < t2s.length; v2Idx++) {\n if (placeholders.v2[v2Idx] !== null) continue\n const v3Idx = v2ToV3[v2Idx]\n if (v3Idx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, wrapWhole('ins', 'cp', v2.slice(t2s[v2Idx].tableStart, t2s[v2Idx].tableEnd)))\n placeholders.v2[v2Idx] = placeholder\n placeholders.v3[v3Idx] = placeholder\n }\n\n // 3. V2 tables paired only with V1 (preserved from V1, Me-deleted in V3).\n for (let v2Idx = 0; v2Idx < t2s.length; v2Idx++) {\n if (placeholders.v2[v2Idx] !== null) continue\n const v1Idx = v2ToV1[v2Idx]\n if (v1Idx === -1) continue\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, wrapWhole('del', 'me', v2.slice(t2s[v2Idx].tableStart, t2s[v2Idx].tableEnd)))\n placeholders.v1[v1Idx] = placeholder\n placeholders.v2[v2Idx] = placeholder\n }\n\n // 4. V2 tables paired with neither (CP-inserted AND Me-deleted = reject).\n for (let v2Idx = 0; v2Idx < t2s.length; v2Idx++) {\n if (placeholders.v2[v2Idx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(\n placeholder,\n wrapWhole('del', 'me', v2.slice(t2s[v2Idx].tableStart, t2s[v2Idx].tableEnd), 'cp')\n )\n placeholders.v2[v2Idx] = placeholder\n }\n\n // 5. V1 tables unpaired with V2 (CP-deleted before V2).\n for (let v1Idx = 0; v1Idx < t1s.length; v1Idx++) {\n if (placeholders.v1[v1Idx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, wrapWhole('del', 'cp', v1.slice(t1s[v1Idx].tableStart, t1s[v1Idx].tableEnd)))\n placeholders.v1[v1Idx] = placeholder\n }\n\n // 6. V3 tables unpaired with V2 (Me-inserted into V3).\n for (let v3Idx = 0; v3Idx < t3s.length; v3Idx++) {\n if (placeholders.v3[v3Idx] !== null) continue\n const placeholder = allocate()\n placeholderToDiff.set(placeholder, wrapWhole('ins', 'me', v3.slice(t3s[v3Idx].tableStart, t3s[v3Idx].tableEnd)))\n placeholders.v3[v3Idx] = placeholder\n }\n\n // Splice placeholders into each input. End → start per input.\n let modifiedV1 = v1\n for (let i = t1s.length - 1; i >= 0; i--) {\n const p = placeholders.v1[i]\n if (p === null) continue\n modifiedV1 = spliceString(modifiedV1, t1s[i].tableStart, t1s[i].tableEnd, p)\n }\n let modifiedV2 = v2\n for (let i = t2s.length - 1; i >= 0; i--) {\n const p = placeholders.v2[i]\n if (p === null) continue\n modifiedV2 = spliceString(modifiedV2, t2s[i].tableStart, t2s[i].tableEnd, p)\n }\n let modifiedV3 = v3\n for (let i = t3s.length - 1; i >= 0; i--) {\n const p = placeholders.v3[i]\n if (p === null) continue\n modifiedV3 = spliceString(modifiedV3, t3s[i].tableStart, t3s[i].tableEnd, p)\n }\n\n return { modifiedV1, modifiedV2, modifiedV3, placeholderToDiff }\n}\n\n/**\n * Threshold at which positional pairing is considered sound. Below this\n * similarity, two positionally-aligned tables are probably different\n * tables (e.g. CP swapped them around) and content-LCS pairing should\n * be used instead. 0.5 is a deliberately loose bar — paired-but-content-\n * edited tables (the common case) sit well above it; genuinely different\n * tables sit well below.\n */\nconst POSITIONAL_PAIR_SIMILARITY_THRESHOLD = 0.5\n\n/**\n * Returns true when V1/V2/V3 tables can be 1:1 paired by position. The\n * three lists must have equal length AND each positional triple must\n * have content similar enough that positional pairing reflects the\n * authors' likely intent. The slow content-LCS path handles cases that\n * fail this gate (table reordering, additions, deletions).\n */\nfunction positionallyAligned(\n v1: string,\n v2: string,\n v3: string,\n t1s: TableRange[],\n t2s: TableRange[],\n t3s: TableRange[]\n): boolean {\n if (t1s.length !== t2s.length || t2s.length !== t3s.length) return false\n for (let i = 0; i < t1s.length; i++) {\n const k1 = tableKey(v1, t1s[i])\n const k2 = tableKey(v2, t2s[i])\n const k3 = tableKey(v3, t3s[i])\n if (textSimilarity(k1, k2) < POSITIONAL_PAIR_SIMILARITY_THRESHOLD) return false\n if (textSimilarity(k2, k3) < POSITIONAL_PAIR_SIMILARITY_THRESHOLD) return false\n }\n return true\n}\n\nfunction tableKey(html: string, table: TableRange): string {\n // Whitespace-normalised full table HTML — tables with byte-identical\n // content (modulo whitespace) pair; any structural or content\n // difference falls through to unpaired (table-level ins/del).\n return html.slice(table.tableStart, table.tableEnd).replace(/\\s+/g, ' ').trim()\n}\n\nfunction diffTableThreeWay(\n v1: string,\n v2: string,\n v3: string,\n t1: TableRange,\n t2: TableRange,\n t3: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n if (sameDimensions(t1, t2) && sameDimensions(t2, t3)) {\n return diffTablePositional(v1, v2, v3, t1, t2, t3, cellDiff)\n }\n return diffTableStructural(v1, v2, v3, t1, t2, t3, cellDiff)\n}\n\nfunction diffTablePositional(\n v1: string,\n v2: string,\n v3: string,\n t1: TableRange,\n t2: TableRange,\n t3: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n // Walk V2 verbatim — its scaffolding (`<table>`, `<tr>`, attributes,\n // inter-cell whitespace) is the spine. Substitute each cell content\n // range with the 3-way merge.\n const out: string[] = []\n let cursor = t2.tableStart\n for (let r = 0; r < t2.rows.length; r++) {\n const r1 = t1.rows[r]\n const r2 = t2.rows[r]\n const r3 = t3.rows[r]\n for (let c = 0; c < r2.cells.length; c++) {\n const c1 = r1.cells[c]\n const c2 = r2.cells[c]\n const c3 = r3.cells[c]\n out.push(v2.slice(cursor, c2.contentStart))\n out.push(\n cellDiff(\n v1.slice(c1.contentStart, c1.contentEnd),\n v2.slice(c2.contentStart, c2.contentEnd),\n v3.slice(c3.contentStart, c3.contentEnd)\n )\n )\n cursor = c2.contentEnd\n }\n }\n out.push(v2.slice(cursor, t2.tableEnd))\n return out.join('')\n}\n\n/**\n * Structural-change three-way table diff: rows or cells differ in count\n * across V1/V2/V3. Strategy:\n * 1. Run row-LCS for each pair (V1↔V2, V2↔V3) over rowKeys\n * 2. Build per-V2-row origin (from align1) and fate (from align2)\n * 3. Walk V2's row order, interleaving:\n * - CP-deleted V1 rows (in align1 but not preserved into V2)\n * - Me-inserted V3 rows (in align2 but not from V2)\n * 4. For each V2 row, combine origin+fate to decide:\n * - equal: recurse cellDiff if cell counts match, else fall back\n * - ins-cp: emit V2 row as fully-CP-inserted\n * - del-me: emit V2 row as fully-Me-deleted\n * - reject: emit V2 row as Me-rejects-CP\n *\n * Tie-break to Me on LCS disagreement (D2): each LCS is authoritative\n * for its own pair-wise view; we don't attempt to reconcile cases where\n * align1's idea of V2's V1 origin contradicts what align2 implies via\n * V3 history. In practice these cases manifest as the row being\n * attributed independently per pair, which is the conservative correct\n * thing to do.\n */\nfunction diffTableStructural(\n v1: string,\n v2: string,\n v3: string,\n t1: TableRange,\n t2: TableRange,\n t3: TableRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n const v1Keys = t1.rows.map(r => rowKey(v1, r))\n const v2Keys = t2.rows.map(r => rowKey(v2, r))\n const v3Keys = t3.rows.map(r => rowKey(v3, r))\n\n const align1 = lcsAlign(v1Keys, v2Keys)\n const align2 = lcsAlign(v2Keys, v3Keys)\n\n // Per-V2-row attribution lookups.\n // Origin: 'preserved' (with V1 row index) or 'cp-inserted'.\n // Fate: 'preserved' (with V3 row index) or 'me-deleted'.\n const v2Origin = new Array<{ kind: 'preserved'; v1Idx: number } | { kind: 'cp-inserted' }>(t2.rows.length)\n for (let i = 0; i < v2Origin.length; i++) v2Origin[i] = { kind: 'cp-inserted' }\n for (const a of align1) {\n if (a.newIdx !== null && a.oldIdx !== null) {\n v2Origin[a.newIdx] = { kind: 'preserved', v1Idx: a.oldIdx }\n }\n }\n\n const v2Fate = new Array<{ kind: 'preserved'; v3Idx: number } | { kind: 'me-deleted' }>(t2.rows.length)\n for (let i = 0; i < v2Fate.length; i++) v2Fate[i] = { kind: 'me-deleted' }\n for (const a of align2) {\n if (a.oldIdx !== null && a.newIdx !== null) {\n v2Fate[a.oldIdx] = { kind: 'preserved', v3Idx: a.newIdx }\n }\n }\n\n // Off-spine surfaces.\n // CP-deleted V1 rows: in align1 with newIdx == null. They land at the\n // V2 boundary that follows them. The boundary index is the next\n // preserved V2 row, or v2.rows.length if no following preserved row.\n const cpDelRowsAt = collectCpDelRowsAtBoundary(align1, t2.rows.length)\n // Me-inserted V3 rows: in align2 with oldIdx == null. They land at the\n // V2 boundary they sit before — i.e. the next preserved V2 row.\n const meInsRowsAt = collectMeInsRowsAtBoundary(align2, t2.rows.length)\n\n // Emit. We reconstruct the table from scratch since rows may be added\n // or deleted from V2's order; preserve the V2 header (everything up\n // to the first <tr>) and the V2 footer (after the last </tr>).\n const out: string[] = []\n out.push(tableHeaderSlice(v2, t2))\n\n const emitBoundary = (i: number) => {\n const cpDel = cpDelRowsAt.get(i)\n if (cpDel) {\n for (const v1RowIdx of cpDel) {\n out.push(emitFullRowAttributed(v1, t1.rows[v1RowIdx], 'del', 'cp'))\n }\n }\n const meIns = meInsRowsAt.get(i)\n if (meIns) {\n for (const v3RowIdx of meIns) {\n out.push(emitFullRowAttributed(v3, t3.rows[v3RowIdx], 'ins', 'me'))\n }\n }\n }\n\n for (let r = 0; r < t2.rows.length; r++) {\n emitBoundary(r)\n const v2Row = t2.rows[r]\n const origin = v2Origin[r]\n const fate = v2Fate[r]\n out.push(emitV2Row(v1, v2, v3, v2Row, t1, t3, origin, fate, cellDiff))\n }\n emitBoundary(t2.rows.length)\n out.push(tableFooterSlice(v2, t2))\n return out.join('')\n}\n\nfunction emitV2Row(\n v1: string,\n v2: string,\n v3: string,\n v2Row: RowRange,\n t1: TableRange,\n t3: TableRange,\n origin: { kind: 'preserved'; v1Idx: number } | { kind: 'cp-inserted' },\n fate: { kind: 'preserved'; v3Idx: number } | { kind: 'me-deleted' },\n cellDiff: ThreeWayDiffCellFn\n): string {\n if (origin.kind === 'cp-inserted' && fate.kind === 'me-deleted') {\n // CP added the row, Me removed it: reject. Show as Me-deletion of\n // CP's insertion via the rejects markup.\n return emitFullRowAttributed(v2, v2Row, 'del', 'me', 'cp')\n }\n if (origin.kind === 'cp-inserted') {\n // CP added the row, Me kept it. Attribute as CP-inserted but emit\n // V2's content (which equals V3's content since Me kept it).\n return emitFullRowAttributed(v2, v2Row, 'ins', 'cp')\n }\n if (fate.kind === 'me-deleted') {\n // Me removed an original V1 row. Emit as Me-deletion of V2's content.\n return emitFullRowAttributed(v2, v2Row, 'del', 'me')\n }\n // Preserved on both sides — recurse into cells. The discriminated-union\n // narrowing makes the indices safe to access directly.\n const v1Row = t1.rows[origin.v1Idx]\n const v3Row = t3.rows[fate.v3Idx]\n if (v1Row.cells.length === v2Row.cells.length && v2Row.cells.length === v3Row.cells.length) {\n // Same cell counts → positional cell diff via cellDiff.\n return diffRowPositional(v1, v2, v3, v1Row, v2Row, v3Row, cellDiff)\n }\n // Cell-count mismatch within a preserved row. Cell-level structural\n // change is deferred; fall back to Me-attribution Replace (V2 row\n // removed, V3 row inserted). This is lossy for CP's contribution\n // within the row but functional. Real-world legal docs rarely change\n // column count mid-row; this is a known limitation.\n const out: string[] = []\n out.push(emitFullRowAttributed(v2, v2Row, 'del', 'me'))\n out.push(emitFullRowAttributed(v3, v3Row, 'ins', 'me'))\n return out.join('')\n}\n\nfunction diffRowPositional(\n v1: string,\n v2: string,\n v3: string,\n v1Row: RowRange,\n v2Row: RowRange,\n v3Row: RowRange,\n cellDiff: ThreeWayDiffCellFn\n): string {\n // Walk V2's row verbatim, substituting each cell content with the\n // 3-way merge. Mirrors `diffTablePositional` at the row scale.\n const out: string[] = []\n let cursor = v2Row.rowStart\n for (let c = 0; c < v2Row.cells.length; c++) {\n const c1 = v1Row.cells[c]\n const c2 = v2Row.cells[c]\n const c3 = v3Row.cells[c]\n out.push(v2.slice(cursor, c2.contentStart))\n out.push(\n cellDiff(\n v1.slice(c1.contentStart, c1.contentEnd),\n v2.slice(c2.contentStart, c2.contentEnd),\n v3.slice(c3.contentStart, c3.contentEnd)\n )\n )\n cursor = c2.contentEnd\n }\n out.push(v2.slice(cursor, v2Row.rowEnd))\n return out.join('')\n}\n\nfunction collectCpDelRowsAtBoundary(align: ReturnType<typeof lcsAlign>, v2RowCount: number): Map<number, number[]> {\n // For each unpaired V1 row (oldIdx set, newIdx null), determine its\n // V2 boundary index: the position just before the next preserved V2\n // row, or v2RowCount if there's no following preserved row.\n const out = new Map<number, number[]>()\n let nextV2Boundary = v2RowCount\n // Walk the alignment in reverse so we can compute nextV2Boundary\n // running backwards, then assign each unpaired V1 row to the boundary\n // currently in scope.\n const pending: number[] = []\n for (let i = align.length - 1; i >= 0; i--) {\n const a = align[i]\n if (a.newIdx !== null) {\n // Flush pending unpaired V1 rows to this V2 boundary.\n if (pending.length > 0) {\n const existing = out.get(nextV2Boundary) ?? []\n // pending was filled backwards — reverse so document order is preserved.\n existing.unshift(...pending.toReversed())\n out.set(nextV2Boundary, existing)\n pending.length = 0\n }\n nextV2Boundary = a.newIdx\n } else if (a.oldIdx !== null) {\n // Unpaired V1 row — CP deleted it.\n pending.push(a.oldIdx)\n }\n }\n if (pending.length > 0) {\n const existing = out.get(nextV2Boundary) ?? []\n existing.unshift(...pending.reverse())\n out.set(nextV2Boundary, existing)\n }\n return out\n}\n\nfunction collectMeInsRowsAtBoundary(align: ReturnType<typeof lcsAlign>, v2RowCount: number): Map<number, number[]> {\n // For each unpaired V3 row (newIdx set, oldIdx null), determine its\n // V2 boundary: the position of the next preserved V2 row, or\n // v2RowCount if at the tail. Mirror of CP-del logic.\n const out = new Map<number, number[]>()\n let nextV2Boundary = v2RowCount\n const pending: number[] = []\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(nextV2Boundary) ?? []\n existing.unshift(...pending.toReversed())\n out.set(nextV2Boundary, existing)\n pending.length = 0\n }\n nextV2Boundary = 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(nextV2Boundary) ?? []\n existing.unshift(...pending.reverse())\n out.set(nextV2Boundary, existing)\n }\n return out\n}\n\nfunction tableHeaderSlice(html: string, table: TableRange): string {\n // Slice from <table> to start of first <tr>. If table is empty, take\n // everything up to </table>.\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 // Slice from end of last <tr> to </table>.\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 that's fully attributed to one author, in an ins or del\n * role. `rejectsAuthor` is set when the row is a Me-deletion of a\n * CP-inserted row. Wraps `<tr>` in `class='diffins cp'` etc. and each\n * `<td>` content in the corresponding `<ins>`/`<del>` wrapper with the\n * author classes/attrs.\n */\nfunction emitFullRowAttributed(\n html: string,\n row: RowRange,\n kind: 'ins' | 'del',\n author: Author,\n rejectsAuthor?: Author\n): string {\n const trOpening = parseOpeningTagAt(html, row.rowStart)\n if (!trOpening) return html.slice(html.length, html.length)\n const trWithAttrs = injectAuthorAttribution(html.slice(row.rowStart, trOpening.end), kind, author, rejectsAuthor)\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, rejectsAuthor))\n cursor = cell.cellEnd\n }\n out.push(html.slice(cursor, row.rowEnd))\n return out.join('')\n}\n\nfunction emitFullCellAttributed(\n html: string,\n cell: CellRange,\n kind: 'ins' | 'del',\n author: Author,\n rejectsAuthor?: Author\n): 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, rejectsAuthor)\n // Wrap the content in an ins/del with the author classes — same\n // shape as the word-level emission. Empty cells get the class on the\n // <td> but no inner wrapper.\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, rejectsAuthor))\n const closing = html.slice(cell.contentEnd, cell.cellEnd)\n return tdWithAttrs + innerWrapped + closing\n}\n\n/**\n * Inject author classes + data-attrs into an existing opening tag (e.g.\n * an `<tr>` or `<td>` already in the source HTML). Uses the same\n * attribution shape as `authorAttribution` + `Utils.wrapText` so the\n * inject-into-existing and wrap-around-text paths agree.\n */\nfunction injectAuthorAttribution(\n openingTag: string,\n kind: 'ins' | 'del',\n author: Author,\n rejectsAuthor?: Author\n): string {\n const meta = authorAttribution(author, rejectsAuthor)\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 // Insert the data-* attributes just before the closing '>' of the\n // opening tag. `<tr>` and `<td>` are never self-closing in real HTML,\n // but handle `/>` defensively for symmetry with other HTML emitters.\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 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 * <del>aaaaa bb ccccccccc</del><ins>11111 bb 222222222</ins> 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 static executeThreeWay(v1: string, v2: string, v3: string, options: ThreeWayOptions = {}): string {\n return HtmlDiff.executeThreeWayWithDepth(v1, v2, v3, options, 0)\n }\n\n private static executeThreeWayWithDepth(\n v1: string,\n v2: string,\n v3: string,\n options: ThreeWayOptions,\n depth: number\n ): string {\n // Table preprocessing first — replaces each V1/V2/V3 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. Restoration\n // happens at the end.\n //\n // Depth-cap the recursion. Each level recurses cellDiff → executeThreeWay,\n // which would otherwise run unbounded on adversarially-nested input.\n // Beyond the cap, skip table preprocessing entirely and let the\n // word-level merge handle the raw HTML — same bail-out semantics as\n // the 2-way `MaxTablePreprocessDepth` cap.\n const tablePreprocess =\n depth < HtmlDiff.MaxThreeWayDepth\n ? preprocessTablesThreeWay(v1, v2, v3, (c1, c2, c3) =>\n HtmlDiff.executeThreeWayWithDepth(c1, c2, c3, options, depth + 1)\n )\n : null\n const inV1 = tablePreprocess?.modifiedV1 ?? v1\n const inV2 = tablePreprocess?.modifiedV2 ?? v2\n const inV3 = tablePreprocess?.modifiedV3 ?? v3\n\n const useProjections =\n options.useProjections ??\n (HtmlDiff.evaluateProjectionApplicability(inV1, inV2) && HtmlDiff.evaluateProjectionApplicability(inV2, inV3))\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 d1 = HtmlDiff.analyze(inV1, inV2, analyzeOpts)\n const d2 = HtmlDiff.analyze(inV2, inV3, analyzeOpts)\n\n // Spine sanity check. Symmetric `useProjections` should guarantee\n // alignment, but if a bug ever lets these diverge we want to fail\n // loudly rather than silently produce a misattributed output.\n if (d1.newDiffWords.length !== d2.oldDiffWords.length) {\n throw new Error(\n 'HtmlDiff.executeThreeWay: V2 tokenisation diverged across pair-wise analyses ' +\n `(${d1.newDiffWords.length} vs ${d2.oldDiffWords.length}). ` +\n 'This indicates the symmetric-projection coordination has a bug.'\n )\n }\n\n const segments = buildSegments(d1, d2)\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] = ' '\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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"}
|