@createiq/htmldiff 1.0.0 → 1.0.1

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/dist/HtmlDiff.cjs CHANGED
@@ -663,8 +663,9 @@ var HtmlDiff = class _HtmlDiff {
663
663
  let specialCaseTagInjection = "";
664
664
  let specialCaseTagInjectionIsBefore = false;
665
665
  if (_HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {
666
+ const styledTagNames = words.filter((word) => Utils_default.isTag(word)).map((style) => Utils_default.getTagName(style)).join(" ");
666
667
  this.specialTagDiffStack.push(words[0]);
667
- specialCaseTagInjection = "<ins class='mod'>";
668
+ specialCaseTagInjection = `<ins class='mod ${styledTagNames}'>`;
668
669
  if (tag === _HtmlDiff.DelTag) {
669
670
  words.shift();
670
671
  while (words.length > 0 && _HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/HtmlDiff.ts","../src/Action.ts","../src/Match.ts","../src/Utils.ts","../src/MatchFinder.ts","../src/Operation.ts","../src/Mode.ts","../src/WordSplitter.ts"],"sourcesContent":["import Action from './Action'\nimport Match from './Match'\nimport MatchFinder from './MatchFinder'\nimport Operation from './Operation'\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 SpecialCaseOpeningTagRegex =\n /<((strong)|(b)|(i)|(em)|(big)|(small)|(u)|(sub)|(sup)|(strike)|(s)|(span))[>\\s]+/i\n\n private content: string[] = []\n private newText: string\n private oldText: string\n\n private specialTagDiffStack: string[] = []\n private newWords: string[] = []\n private oldWords: string[] = []\n private matchGranularity = 0\n private blockExpressions: RegExp[] = []\n\n /**\n * Defines how to compare repeating words. Valid values are from 0 to 1.\n * This value allows to exclude some words from comparison that eventually\n * reduces the total time of the diff algorithm.\n * 0 means that all words are excluded so the diff will not find any matching words at all.\n * 1 (default value) means that all words participate in comparison so this is the most accurate case.\n * 0.5 means that any word that occurs more than 50% times may be excluded from comparison. This doesn't\n * mean that such words will definitely be excluded but only gives a permission to exclude them if necessary.\n */\n repeatingWordsAccuracy = 1.0\n\n /**\n * If true all whitespaces are considered as equal\n */\n ignoreWhitespaceDifferences = false\n\n /**\n * If some match is too small and located far from its neighbors then it is considered as orphan\n * and removed. For example:\n * <code>\n * aaaaa bb ccccccccc dddddd ee\n * 11111 bb 222222222 dddddd ee\n * </code>\n * will find two matches <code>bb</code> and <code>dddddd ee</code> but the first will be considered\n * as orphan and ignored, as result it will consider texts <code>aaaaa bb ccccccccc</code> and\n * <code>11111 bb 222222222</code> as single replacement:\n * <code>\n * &lt;del&gt;aaaaa bb ccccccccc&lt;/del&gt;&lt;ins&gt;11111 bb 222222222&lt;/ins&gt; dddddd ee\n * </code>\n * This property defines relative size of the match to be considered as orphan, from 0 to 1.\n * 1 means that all matches will be considered as orphans.\n * 0 (default) means that no match will be considered as orphan.\n * 0.2 means that if match length is less than 20% of distance between its neighbors it is considered as orphan.\n */\n orphanMatchThreshold = 0.0\n\n /**\n * Initializes a new instance of the class.\n * @param oldText The old text.\n * @param newText The new text.\n */\n constructor(oldText: string, newText: string) {\n this.oldText = oldText\n this.newText = newText\n }\n\n static execute(oldText: string, newText: string) {\n return new HtmlDiff(oldText, newText).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 this.splitInputsToWords()\n\n this.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(this.oldWords.length, this.newWords.length)\n )\n\n const operations = this.operations()\n for (const op of operations) {\n this.performOperation(op)\n }\n\n return this.content.join('')\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 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 text = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.insertTag(HtmlDiff.InsTag, cssClass, text)\n }\n\n private processDeleteOperation(operation: Operation, cssClass: string) {\n const text = this.oldWords.filter((_, pos) => pos >= operation.startInOld && pos < operation.endInOld)\n this.insertTag(HtmlDiff.DelTag, cssClass, text)\n }\n\n private processEqualOperation(operation: Operation) {\n const result = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.content.push(result.join(''))\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 this.specialTagDiffStack.push(words[0])\n specialCaseTagInjection = \"<ins class='mod'>\"\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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n const openingTag = this.specialTagDiffStack.length === 0 ? null : this.specialTagDiffStack.pop()\n const openingAndClosingTagsMatch =\n !!openingTag && Utils.getTagName(openingTag) === Utils.getTagName(words[indexLastTagInFirstTagBlock])\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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n words.shift()\n }\n }\n }\n\n if (words.length === 0 && specialCaseTagInjection.length === 0) {\n break\n }\n\n if (specialCaseTagInjectionIsBefore) {\n this.content.push(specialCaseTagInjection + this.extractConsecutiveWords(words, Utils.isTag).join(''))\n } else {\n this.content.push(this.extractConsecutiveWords(words, Utils.isTag).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] = '&nbsp;'\n }\n if (!condition(word)) {\n indexOfFirstTag = i\n break\n }\n }\n\n if (indexOfFirstTag !== null) {\n const items = words.filter((s, pos) => pos >= 0 && pos < indexOfFirstTag)\n if (indexOfFirstTag > 0) {\n words.splice(0, indexOfFirstTag)\n }\n return items\n }\n\n const items = words.filter((s, pos) => pos >= 0 && pos <= words.length)\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 matches = this.matchingBlocks()\n matches.push(new Match(this.oldWords.length, this.newWords.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 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 const oldDistanceInChars = this.oldWords\n .slice(prev.endInOld, next.startInOld)\n .reduce((acc, word) => acc + word.length, 0)\n const newDistanceInChars = this.newWords\n .slice(prev.endInNew, next.startInNew)\n .reduce((acc, word) => acc + word.length, 0)\n const currMatchLengthInChars = this.newWords\n .slice(curr.startInNew, curr.endInNew)\n .reduce((acc, word) => acc + word.length, 0)\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 matchingBlocks: Match[] = []\n this.findMatchingBlocks(0, this.oldWords.length, 0, this.newWords.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 // 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(this.oldWords, this.newWords, startInOld, endInOld, startInNew, endInNew, options)\n const match = finder.findMatch()\n if (match !== null) return match\n }\n return null\n }\n}\n","enum Action {\n Equal = 0,\n Delete = 1,\n Insert = 2,\n None = 3,\n Replace = 4,\n}\n\nexport default Action\n","export default class Match {\n private _startInOld: number\n private _startInNew: number\n private _size: number\n\n constructor(startInOld: number, startInNew: number, size: number) {\n this._startInOld = startInOld\n this._startInNew = startInNew\n this._size = size\n }\n\n get startInOld() {\n return this._startInOld\n }\n\n get startInNew() {\n return this._startInNew\n }\n\n get size() {\n return this._size\n }\n\n get endInOld() {\n return this._startInOld + this._size\n }\n\n get endInNew() {\n return this._startInNew + this._size\n }\n}\n","const openingTagRegex = /^\\s*<[^>]+>\\s*$/\nconst closingTagTexRegex = /^\\s*<\\/[^>]+>\\s*$/\nconst tagWordRegex = /<[^\\s>]+/\nconst whitespaceRegex = /^(\\s|&nbsp;)+$/\nconst wordRegex = /[\\w#@]+/\nconst tagRegex = /<\\/?(?<name>[^\\s\\/>]+)[^>]*>/\n\nconst SpecialCaseWordTags: readonly string[] = ['<img']\n\nexport function isTag(item: string): boolean {\n if (SpecialCaseWordTags.some(re => item?.startsWith(re))) {\n return false\n }\n\n return isOpeningTag(item) || isClosingTag(item)\n}\n\nfunction isOpeningTag(item: string): boolean {\n return openingTagRegex.test(item)\n}\n\nfunction isClosingTag(item: string): boolean {\n return closingTagTexRegex.test(item)\n}\n\nexport function stripTagAttributes(word: string): string {\n const match = tagWordRegex.exec(word)\n if (match) {\n return `${match[0]}${word.endsWith('/>') ? '/>' : '>'}`\n }\n\n return word\n}\n\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 this.wordIndices = {\n ...this.wordIndices,\n [key]: [...(this.wordIndices[key] ?? []), i],\n }\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 if (Object.keys(this.wordIndices).length === 0) {\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 (!Object.keys(this.wordIndices).includes(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","enum Mode {\n Character = 0,\n Tag = 1,\n Whitespace = 2,\n Entity = 3,\n}\n\nexport default Mode\n","import Mode from './Mode'\nimport Utils from './Utils'\n\nexport default class WordSplitter {\n private text: string\n private isBlockCheckRequired: boolean\n private blockLocations: BlockFinderResult\n private mode: Mode\n private isGrouping = false\n private globbingUntil: number\n private currentWord: string[]\n private words: string[]\n private static NotGlobbing = -1\n\n private get currentWordHasChars() {\n return this.currentWord.length > 0\n }\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockLocations = new BlockFinder(text, blockExpressions).findBlocks()\n this.isBlockCheckRequired = this.blockLocations.hasBlocks\n this.mode = Mode.Character\n this.globbingUntil = WordSplitter.NotGlobbing\n this.currentWord = []\n this.words = []\n }\n\n process(): string[] {\n for (let index = 0; index < this.text.length; index++) {\n const character = this.text.charAt(index)\n this.processCharacter(index, character)\n }\n\n this.appendCurrentWordToWords()\n return this.words\n }\n\n private processCharacter(index: number, character: string) {\n if (this.isGlobbing(index, character)) {\n return\n }\n\n switch (this.mode) {\n case Mode.Character:\n this.processTextCharacter(character)\n break\n case Mode.Tag:\n this.processHtmlTagContinuation(character)\n break\n case Mode.Whitespace:\n this.processWhiteSpaceContinuation(character)\n break\n case Mode.Entity:\n this.processEntityContinuation(character)\n break\n }\n }\n\n private processEntityContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (character.trim().length === 0) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (Utils.isEndOfEntity(character)) {\n let switchToNextMode = true\n if (this.currentWordHasChars) {\n this.currentWord.push(character)\n this.words.push(this.currentWord.join(''))\n\n //join &nbsp; entity with last whitespace\n if (\n this.words.length > 2 &&\n Utils.isWhiteSpace(this.words[this.words.length - 2]) &&\n Utils.isWhiteSpace(this.words[this.words.length - 1])\n ) {\n const w1 = this.words[this.words.length - 2]\n const w2 = this.words[this.words.length - 1]\n this.words.splice(this.words.length - 2, 2)\n this.currentWord = `${w1}${w2}`.split('')\n this.mode = Mode.Whitespace\n switchToNextMode = false\n }\n }\n\n if (switchToNextMode) {\n this.currentWord = []\n this.mode = Mode.Character\n }\n } else if (Utils.isWord(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processWhiteSpaceContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processHtmlTagContinuation(character: string) {\n if (Utils.isEndOfTag(character)) {\n this.currentWord.push(character)\n this.appendCurrentWordToWords()\n this.mode = Utils.isWhiteSpace(character) ? Mode.Whitespace : Mode.Character\n } else {\n this.currentWord.push(character)\n }\n }\n\n private processTextCharacter(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push('<')\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (\n Utils.isWord(character) &&\n (this.currentWord.length === 0 || Utils.isWord(this.currentWord[this.currentWord.length - 1]))\n ) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n }\n }\n\n private appendCurrentWordToWords() {\n if (this.currentWordHasChars) {\n this.words.push(this.currentWord.join(''))\n this.currentWord = []\n }\n }\n\n private isGlobbing(index: number, character: string): boolean {\n if (!this.isBlockCheckRequired) {\n return false\n }\n const isCurrentBlockTerminating = index === this.globbingUntil\n if (isCurrentBlockTerminating) {\n this.globbingUntil = WordSplitter.NotGlobbing\n this.isGrouping = false\n this.appendCurrentWordToWords()\n }\n\n const until = this.blockLocations.isInBlock(index)\n if (until) {\n this.isGrouping = true\n this.globbingUntil = until\n }\n if (this.isGrouping) {\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n return this.isGrouping\n }\n\n static convertHtmlToListOfWords(text: string, blockExpressions: RegExp[]): string[] {\n return new WordSplitter(text, blockExpressions).process()\n }\n}\n\nclass BlockFinderResult {\n private blocks: Map<number, number> = new Map()\n\n addBlock(from: number, to: number) {\n if (this.blocks.has(from)) {\n throw new ArgumentError('One or more block expressions result in a text sequence that overlaps.')\n }\n\n this.blocks.set(from, to)\n }\n\n isInBlock(location: number): number | null {\n return this.blocks.get(location) ?? null\n }\n\n get hasBlocks() {\n return this.blocks.size > 0\n }\n}\n\nclass ArgumentError extends Error {}\n\nclass BlockFinder {\n private text: string\n private blockExpressions: RegExp[]\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockExpressions = blockExpressions\n }\n\n findBlocks(): BlockFinderResult {\n const result = new BlockFinderResult()\n for (const expression of this.blockExpressions) {\n this.processBlockMatcher(expression, result)\n }\n return result\n }\n\n private processBlockMatcher(exp: RegExp, result: BlockFinderResult) {\n let match: RegExpExecArray | null\n // biome-ignore lint/suspicious/noAssignInExpressions: Couldn't think of a nicer way to do this\n while ((match = exp.exec(this.text)) !== null) {\n this.tryAddBlock(exp, match, result)\n }\n }\n\n private tryAddBlock(exp: RegExp, match: RegExpExecArray, result: BlockFinderResult) {\n try {\n const from = match.index\n const to = match.index + match[0].length\n result.addBlock(from, to)\n } catch (e) {\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"],"mappings":"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","names":["Action","Mode","items"]}
1
+ {"version":3,"sources":["../src/HtmlDiff.ts","../src/Action.ts","../src/Match.ts","../src/Utils.ts","../src/MatchFinder.ts","../src/Operation.ts","../src/Mode.ts","../src/WordSplitter.ts"],"sourcesContent":["import Action from './Action'\nimport Match from './Match'\nimport MatchFinder from './MatchFinder'\nimport Operation from './Operation'\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 SpecialCaseOpeningTagRegex =\n /<((strong)|(b)|(i)|(em)|(big)|(small)|(u)|(sub)|(sup)|(strike)|(s)|(span))[>\\s]+/i\n\n private content: string[] = []\n private newText: string\n private oldText: string\n\n private specialTagDiffStack: string[] = []\n private newWords: string[] = []\n private oldWords: string[] = []\n private matchGranularity = 0\n private blockExpressions: RegExp[] = []\n\n /**\n * Defines how to compare repeating words. Valid values are from 0 to 1.\n * This value allows to exclude some words from comparison that eventually\n * reduces the total time of the diff algorithm.\n * 0 means that all words are excluded so the diff will not find any matching words at all.\n * 1 (default value) means that all words participate in comparison so this is the most accurate case.\n * 0.5 means that any word that occurs more than 50% times may be excluded from comparison. This doesn't\n * mean that such words will definitely be excluded but only gives a permission to exclude them if necessary.\n */\n repeatingWordsAccuracy = 1.0\n\n /**\n * If true all whitespaces are considered as equal\n */\n ignoreWhitespaceDifferences = false\n\n /**\n * If some match is too small and located far from its neighbors then it is considered as orphan\n * and removed. For example:\n * <code>\n * aaaaa bb ccccccccc dddddd ee\n * 11111 bb 222222222 dddddd ee\n * </code>\n * will find two matches <code>bb</code> and <code>dddddd ee</code> but the first will be considered\n * as orphan and ignored, as result it will consider texts <code>aaaaa bb ccccccccc</code> and\n * <code>11111 bb 222222222</code> as single replacement:\n * <code>\n * &lt;del&gt;aaaaa bb ccccccccc&lt;/del&gt;&lt;ins&gt;11111 bb 222222222&lt;/ins&gt; dddddd ee\n * </code>\n * This property defines relative size of the match to be considered as orphan, from 0 to 1.\n * 1 means that all matches will be considered as orphans.\n * 0 (default) means that no match will be considered as orphan.\n * 0.2 means that if match length is less than 20% of distance between its neighbors it is considered as orphan.\n */\n orphanMatchThreshold = 0.0\n\n /**\n * Initializes a new instance of the class.\n * @param oldText The old text.\n * @param newText The new text.\n */\n constructor(oldText: string, newText: string) {\n this.oldText = oldText\n this.newText = newText\n }\n\n static execute(oldText: string, newText: string) {\n return new HtmlDiff(oldText, newText).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 this.splitInputsToWords()\n\n this.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(this.oldWords.length, this.newWords.length)\n )\n\n const operations = this.operations()\n for (const op of operations) {\n this.performOperation(op)\n }\n\n return this.content.join('')\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 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 text = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.insertTag(HtmlDiff.InsTag, cssClass, text)\n }\n\n private processDeleteOperation(operation: Operation, cssClass: string) {\n const text = this.oldWords.filter((_, pos) => pos >= operation.startInOld && pos < operation.endInOld)\n this.insertTag(HtmlDiff.DelTag, cssClass, text)\n }\n\n private processEqualOperation(operation: Operation) {\n const result = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.content.push(result.join(''))\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 styledTagNames = words\n .filter(word => Utils.isTag(word))\n .map(style => Utils.getTagName(style))\n .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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n const openingTag = this.specialTagDiffStack.length === 0 ? null : this.specialTagDiffStack.pop()\n const openingAndClosingTagsMatch =\n !!openingTag && Utils.getTagName(openingTag) === Utils.getTagName(words[indexLastTagInFirstTagBlock])\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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n words.shift()\n }\n }\n }\n\n if (words.length === 0 && specialCaseTagInjection.length === 0) {\n break\n }\n\n if (specialCaseTagInjectionIsBefore) {\n this.content.push(specialCaseTagInjection + this.extractConsecutiveWords(words, Utils.isTag).join(''))\n } else {\n this.content.push(this.extractConsecutiveWords(words, Utils.isTag).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] = '&nbsp;'\n }\n if (!condition(word)) {\n indexOfFirstTag = i\n break\n }\n }\n\n if (indexOfFirstTag !== null) {\n const items = words.filter((s, pos) => pos >= 0 && pos < indexOfFirstTag)\n if (indexOfFirstTag > 0) {\n words.splice(0, indexOfFirstTag)\n }\n return items\n }\n\n const items = words.filter((s, pos) => pos >= 0 && pos <= words.length)\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 matches = this.matchingBlocks()\n matches.push(new Match(this.oldWords.length, this.newWords.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 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 const oldDistanceInChars = this.oldWords\n .slice(prev.endInOld, next.startInOld)\n .reduce((acc, word) => acc + word.length, 0)\n const newDistanceInChars = this.newWords\n .slice(prev.endInNew, next.startInNew)\n .reduce((acc, word) => acc + word.length, 0)\n const currMatchLengthInChars = this.newWords\n .slice(curr.startInNew, curr.endInNew)\n .reduce((acc, word) => acc + word.length, 0)\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 matchingBlocks: Match[] = []\n this.findMatchingBlocks(0, this.oldWords.length, 0, this.newWords.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 // 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(this.oldWords, this.newWords, startInOld, endInOld, startInNew, endInNew, options)\n const match = finder.findMatch()\n if (match !== null) return match\n }\n return null\n }\n}\n","enum Action {\n Equal = 0,\n Delete = 1,\n Insert = 2,\n None = 3,\n Replace = 4,\n}\n\nexport default Action\n","export default class Match {\n private _startInOld: number\n private _startInNew: number\n private _size: number\n\n constructor(startInOld: number, startInNew: number, size: number) {\n this._startInOld = startInOld\n this._startInNew = startInNew\n this._size = size\n }\n\n get startInOld() {\n return this._startInOld\n }\n\n get startInNew() {\n return this._startInNew\n }\n\n get size() {\n return this._size\n }\n\n get endInOld() {\n return this._startInOld + this._size\n }\n\n get endInNew() {\n return this._startInNew + this._size\n }\n}\n","const openingTagRegex = /^\\s*<[^>]+>\\s*$/\nconst closingTagTexRegex = /^\\s*<\\/[^>]+>\\s*$/\nconst tagWordRegex = /<[^\\s>]+/\nconst whitespaceRegex = /^(\\s|&nbsp;)+$/\nconst wordRegex = /[\\w#@]+/\nconst tagRegex = /<\\/?(?<name>[^\\s\\/>]+)[^>]*>/\n\nconst SpecialCaseWordTags: readonly string[] = ['<img']\n\nexport function isTag(item: string): boolean {\n if (SpecialCaseWordTags.some(re => item?.startsWith(re))) {\n return false\n }\n\n return isOpeningTag(item) || isClosingTag(item)\n}\n\nfunction isOpeningTag(item: string): boolean {\n return openingTagRegex.test(item)\n}\n\nfunction isClosingTag(item: string): boolean {\n return closingTagTexRegex.test(item)\n}\n\nexport function stripTagAttributes(word: string): string {\n const match = tagWordRegex.exec(word)\n if (match) {\n return `${match[0]}${word.endsWith('/>') ? '/>' : '>'}`\n }\n\n return word\n}\n\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 this.wordIndices = {\n ...this.wordIndices,\n [key]: [...(this.wordIndices[key] ?? []), i],\n }\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 if (Object.keys(this.wordIndices).length === 0) {\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 (!Object.keys(this.wordIndices).includes(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","enum Mode {\n Character = 0,\n Tag = 1,\n Whitespace = 2,\n Entity = 3,\n}\n\nexport default Mode\n","import Mode from './Mode'\nimport Utils from './Utils'\n\nexport default class WordSplitter {\n private text: string\n private isBlockCheckRequired: boolean\n private blockLocations: BlockFinderResult\n private mode: Mode\n private isGrouping = false\n private globbingUntil: number\n private currentWord: string[]\n private words: string[]\n private static NotGlobbing = -1\n\n private get currentWordHasChars() {\n return this.currentWord.length > 0\n }\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockLocations = new BlockFinder(text, blockExpressions).findBlocks()\n this.isBlockCheckRequired = this.blockLocations.hasBlocks\n this.mode = Mode.Character\n this.globbingUntil = WordSplitter.NotGlobbing\n this.currentWord = []\n this.words = []\n }\n\n process(): string[] {\n for (let index = 0; index < this.text.length; index++) {\n const character = this.text.charAt(index)\n this.processCharacter(index, character)\n }\n\n this.appendCurrentWordToWords()\n return this.words\n }\n\n private processCharacter(index: number, character: string) {\n if (this.isGlobbing(index, character)) {\n return\n }\n\n switch (this.mode) {\n case Mode.Character:\n this.processTextCharacter(character)\n break\n case Mode.Tag:\n this.processHtmlTagContinuation(character)\n break\n case Mode.Whitespace:\n this.processWhiteSpaceContinuation(character)\n break\n case Mode.Entity:\n this.processEntityContinuation(character)\n break\n }\n }\n\n private processEntityContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (character.trim().length === 0) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (Utils.isEndOfEntity(character)) {\n let switchToNextMode = true\n if (this.currentWordHasChars) {\n this.currentWord.push(character)\n this.words.push(this.currentWord.join(''))\n\n //join &nbsp; entity with last whitespace\n if (\n this.words.length > 2 &&\n Utils.isWhiteSpace(this.words[this.words.length - 2]) &&\n Utils.isWhiteSpace(this.words[this.words.length - 1])\n ) {\n const w1 = this.words[this.words.length - 2]\n const w2 = this.words[this.words.length - 1]\n this.words.splice(this.words.length - 2, 2)\n this.currentWord = `${w1}${w2}`.split('')\n this.mode = Mode.Whitespace\n switchToNextMode = false\n }\n }\n\n if (switchToNextMode) {\n this.currentWord = []\n this.mode = Mode.Character\n }\n } else if (Utils.isWord(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processWhiteSpaceContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processHtmlTagContinuation(character: string) {\n if (Utils.isEndOfTag(character)) {\n this.currentWord.push(character)\n this.appendCurrentWordToWords()\n this.mode = Utils.isWhiteSpace(character) ? Mode.Whitespace : Mode.Character\n } else {\n this.currentWord.push(character)\n }\n }\n\n private processTextCharacter(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push('<')\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (\n Utils.isWord(character) &&\n (this.currentWord.length === 0 || Utils.isWord(this.currentWord[this.currentWord.length - 1]))\n ) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n }\n }\n\n private appendCurrentWordToWords() {\n if (this.currentWordHasChars) {\n this.words.push(this.currentWord.join(''))\n this.currentWord = []\n }\n }\n\n private isGlobbing(index: number, character: string): boolean {\n if (!this.isBlockCheckRequired) {\n return false\n }\n const isCurrentBlockTerminating = index === this.globbingUntil\n if (isCurrentBlockTerminating) {\n this.globbingUntil = WordSplitter.NotGlobbing\n this.isGrouping = false\n this.appendCurrentWordToWords()\n }\n\n const until = this.blockLocations.isInBlock(index)\n if (until) {\n this.isGrouping = true\n this.globbingUntil = until\n }\n if (this.isGrouping) {\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n return this.isGrouping\n }\n\n static convertHtmlToListOfWords(text: string, blockExpressions: RegExp[]): string[] {\n return new WordSplitter(text, blockExpressions).process()\n }\n}\n\nclass BlockFinderResult {\n private blocks: Map<number, number> = new Map()\n\n addBlock(from: number, to: number) {\n if (this.blocks.has(from)) {\n throw new ArgumentError('One or more block expressions result in a text sequence that overlaps.')\n }\n\n this.blocks.set(from, to)\n }\n\n isInBlock(location: number): number | null {\n return this.blocks.get(location) ?? null\n }\n\n get hasBlocks() {\n return this.blocks.size > 0\n }\n}\n\nclass ArgumentError extends Error {}\n\nclass BlockFinder {\n private text: string\n private blockExpressions: RegExp[]\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockExpressions = blockExpressions\n }\n\n findBlocks(): BlockFinderResult {\n const result = new BlockFinderResult()\n for (const expression of this.blockExpressions) {\n this.processBlockMatcher(expression, result)\n }\n return result\n }\n\n private processBlockMatcher(exp: RegExp, result: BlockFinderResult) {\n let match: RegExpExecArray | null\n // biome-ignore lint/suspicious/noAssignInExpressions: Couldn't think of a nicer way to do this\n while ((match = exp.exec(this.text)) !== null) {\n this.tryAddBlock(exp, match, result)\n }\n }\n\n private tryAddBlock(exp: RegExp, match: RegExpExecArray, result: BlockFinderResult) {\n try {\n const from = match.index\n const to = match.index + match[0].length\n result.addBlock(from, to)\n } catch (e) {\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"],"mappings":"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","names":["Action","Mode","items"]}
package/dist/HtmlDiff.js CHANGED
@@ -637,8 +637,9 @@ var HtmlDiff = class _HtmlDiff {
637
637
  let specialCaseTagInjection = "";
638
638
  let specialCaseTagInjectionIsBefore = false;
639
639
  if (_HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {
640
+ const styledTagNames = words.filter((word) => Utils_default.isTag(word)).map((style) => Utils_default.getTagName(style)).join(" ");
640
641
  this.specialTagDiffStack.push(words[0]);
641
- specialCaseTagInjection = "<ins class='mod'>";
642
+ specialCaseTagInjection = `<ins class='mod ${styledTagNames}'>`;
642
643
  if (tag === _HtmlDiff.DelTag) {
643
644
  words.shift();
644
645
  while (words.length > 0 && _HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Action.ts","../src/Match.ts","../src/Utils.ts","../src/MatchFinder.ts","../src/Operation.ts","../src/Mode.ts","../src/WordSplitter.ts","../src/HtmlDiff.ts"],"sourcesContent":["enum Action {\n Equal = 0,\n Delete = 1,\n Insert = 2,\n None = 3,\n Replace = 4,\n}\n\nexport default Action\n","export default class Match {\n private _startInOld: number\n private _startInNew: number\n private _size: number\n\n constructor(startInOld: number, startInNew: number, size: number) {\n this._startInOld = startInOld\n this._startInNew = startInNew\n this._size = size\n }\n\n get startInOld() {\n return this._startInOld\n }\n\n get startInNew() {\n return this._startInNew\n }\n\n get size() {\n return this._size\n }\n\n get endInOld() {\n return this._startInOld + this._size\n }\n\n get endInNew() {\n return this._startInNew + this._size\n }\n}\n","const openingTagRegex = /^\\s*<[^>]+>\\s*$/\nconst closingTagTexRegex = /^\\s*<\\/[^>]+>\\s*$/\nconst tagWordRegex = /<[^\\s>]+/\nconst whitespaceRegex = /^(\\s|&nbsp;)+$/\nconst wordRegex = /[\\w#@]+/\nconst tagRegex = /<\\/?(?<name>[^\\s\\/>]+)[^>]*>/\n\nconst SpecialCaseWordTags: readonly string[] = ['<img']\n\nexport function isTag(item: string): boolean {\n if (SpecialCaseWordTags.some(re => item?.startsWith(re))) {\n return false\n }\n\n return isOpeningTag(item) || isClosingTag(item)\n}\n\nfunction isOpeningTag(item: string): boolean {\n return openingTagRegex.test(item)\n}\n\nfunction isClosingTag(item: string): boolean {\n return closingTagTexRegex.test(item)\n}\n\nexport function stripTagAttributes(word: string): string {\n const match = tagWordRegex.exec(word)\n if (match) {\n return `${match[0]}${word.endsWith('/>') ? '/>' : '>'}`\n }\n\n return word\n}\n\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 this.wordIndices = {\n ...this.wordIndices,\n [key]: [...(this.wordIndices[key] ?? []), i],\n }\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 if (Object.keys(this.wordIndices).length === 0) {\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 (!Object.keys(this.wordIndices).includes(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","enum Mode {\n Character = 0,\n Tag = 1,\n Whitespace = 2,\n Entity = 3,\n}\n\nexport default Mode\n","import Mode from './Mode'\nimport Utils from './Utils'\n\nexport default class WordSplitter {\n private text: string\n private isBlockCheckRequired: boolean\n private blockLocations: BlockFinderResult\n private mode: Mode\n private isGrouping = false\n private globbingUntil: number\n private currentWord: string[]\n private words: string[]\n private static NotGlobbing = -1\n\n private get currentWordHasChars() {\n return this.currentWord.length > 0\n }\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockLocations = new BlockFinder(text, blockExpressions).findBlocks()\n this.isBlockCheckRequired = this.blockLocations.hasBlocks\n this.mode = Mode.Character\n this.globbingUntil = WordSplitter.NotGlobbing\n this.currentWord = []\n this.words = []\n }\n\n process(): string[] {\n for (let index = 0; index < this.text.length; index++) {\n const character = this.text.charAt(index)\n this.processCharacter(index, character)\n }\n\n this.appendCurrentWordToWords()\n return this.words\n }\n\n private processCharacter(index: number, character: string) {\n if (this.isGlobbing(index, character)) {\n return\n }\n\n switch (this.mode) {\n case Mode.Character:\n this.processTextCharacter(character)\n break\n case Mode.Tag:\n this.processHtmlTagContinuation(character)\n break\n case Mode.Whitespace:\n this.processWhiteSpaceContinuation(character)\n break\n case Mode.Entity:\n this.processEntityContinuation(character)\n break\n }\n }\n\n private processEntityContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (character.trim().length === 0) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (Utils.isEndOfEntity(character)) {\n let switchToNextMode = true\n if (this.currentWordHasChars) {\n this.currentWord.push(character)\n this.words.push(this.currentWord.join(''))\n\n //join &nbsp; entity with last whitespace\n if (\n this.words.length > 2 &&\n Utils.isWhiteSpace(this.words[this.words.length - 2]) &&\n Utils.isWhiteSpace(this.words[this.words.length - 1])\n ) {\n const w1 = this.words[this.words.length - 2]\n const w2 = this.words[this.words.length - 1]\n this.words.splice(this.words.length - 2, 2)\n this.currentWord = `${w1}${w2}`.split('')\n this.mode = Mode.Whitespace\n switchToNextMode = false\n }\n }\n\n if (switchToNextMode) {\n this.currentWord = []\n this.mode = Mode.Character\n }\n } else if (Utils.isWord(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processWhiteSpaceContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processHtmlTagContinuation(character: string) {\n if (Utils.isEndOfTag(character)) {\n this.currentWord.push(character)\n this.appendCurrentWordToWords()\n this.mode = Utils.isWhiteSpace(character) ? Mode.Whitespace : Mode.Character\n } else {\n this.currentWord.push(character)\n }\n }\n\n private processTextCharacter(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push('<')\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (\n Utils.isWord(character) &&\n (this.currentWord.length === 0 || Utils.isWord(this.currentWord[this.currentWord.length - 1]))\n ) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n }\n }\n\n private appendCurrentWordToWords() {\n if (this.currentWordHasChars) {\n this.words.push(this.currentWord.join(''))\n this.currentWord = []\n }\n }\n\n private isGlobbing(index: number, character: string): boolean {\n if (!this.isBlockCheckRequired) {\n return false\n }\n const isCurrentBlockTerminating = index === this.globbingUntil\n if (isCurrentBlockTerminating) {\n this.globbingUntil = WordSplitter.NotGlobbing\n this.isGrouping = false\n this.appendCurrentWordToWords()\n }\n\n const until = this.blockLocations.isInBlock(index)\n if (until) {\n this.isGrouping = true\n this.globbingUntil = until\n }\n if (this.isGrouping) {\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n return this.isGrouping\n }\n\n static convertHtmlToListOfWords(text: string, blockExpressions: RegExp[]): string[] {\n return new WordSplitter(text, blockExpressions).process()\n }\n}\n\nclass BlockFinderResult {\n private blocks: Map<number, number> = new Map()\n\n addBlock(from: number, to: number) {\n if (this.blocks.has(from)) {\n throw new ArgumentError('One or more block expressions result in a text sequence that overlaps.')\n }\n\n this.blocks.set(from, to)\n }\n\n isInBlock(location: number): number | null {\n return this.blocks.get(location) ?? null\n }\n\n get hasBlocks() {\n return this.blocks.size > 0\n }\n}\n\nclass ArgumentError extends Error {}\n\nclass BlockFinder {\n private text: string\n private blockExpressions: RegExp[]\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockExpressions = blockExpressions\n }\n\n findBlocks(): BlockFinderResult {\n const result = new BlockFinderResult()\n for (const expression of this.blockExpressions) {\n this.processBlockMatcher(expression, result)\n }\n return result\n }\n\n private processBlockMatcher(exp: RegExp, result: BlockFinderResult) {\n let match: RegExpExecArray | null\n // biome-ignore lint/suspicious/noAssignInExpressions: Couldn't think of a nicer way to do this\n while ((match = exp.exec(this.text)) !== null) {\n this.tryAddBlock(exp, match, result)\n }\n }\n\n private tryAddBlock(exp: RegExp, match: RegExpExecArray, result: BlockFinderResult) {\n try {\n const from = match.index\n const to = match.index + match[0].length\n result.addBlock(from, to)\n } catch (e) {\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 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 SpecialCaseOpeningTagRegex =\n /<((strong)|(b)|(i)|(em)|(big)|(small)|(u)|(sub)|(sup)|(strike)|(s)|(span))[>\\s]+/i\n\n private content: string[] = []\n private newText: string\n private oldText: string\n\n private specialTagDiffStack: string[] = []\n private newWords: string[] = []\n private oldWords: string[] = []\n private matchGranularity = 0\n private blockExpressions: RegExp[] = []\n\n /**\n * Defines how to compare repeating words. Valid values are from 0 to 1.\n * This value allows to exclude some words from comparison that eventually\n * reduces the total time of the diff algorithm.\n * 0 means that all words are excluded so the diff will not find any matching words at all.\n * 1 (default value) means that all words participate in comparison so this is the most accurate case.\n * 0.5 means that any word that occurs more than 50% times may be excluded from comparison. This doesn't\n * mean that such words will definitely be excluded but only gives a permission to exclude them if necessary.\n */\n repeatingWordsAccuracy = 1.0\n\n /**\n * If true all whitespaces are considered as equal\n */\n ignoreWhitespaceDifferences = false\n\n /**\n * If some match is too small and located far from its neighbors then it is considered as orphan\n * and removed. For example:\n * <code>\n * aaaaa bb ccccccccc dddddd ee\n * 11111 bb 222222222 dddddd ee\n * </code>\n * will find two matches <code>bb</code> and <code>dddddd ee</code> but the first will be considered\n * as orphan and ignored, as result it will consider texts <code>aaaaa bb ccccccccc</code> and\n * <code>11111 bb 222222222</code> as single replacement:\n * <code>\n * &lt;del&gt;aaaaa bb ccccccccc&lt;/del&gt;&lt;ins&gt;11111 bb 222222222&lt;/ins&gt; dddddd ee\n * </code>\n * This property defines relative size of the match to be considered as orphan, from 0 to 1.\n * 1 means that all matches will be considered as orphans.\n * 0 (default) means that no match will be considered as orphan.\n * 0.2 means that if match length is less than 20% of distance between its neighbors it is considered as orphan.\n */\n orphanMatchThreshold = 0.0\n\n /**\n * Initializes a new instance of the class.\n * @param oldText The old text.\n * @param newText The new text.\n */\n constructor(oldText: string, newText: string) {\n this.oldText = oldText\n this.newText = newText\n }\n\n static execute(oldText: string, newText: string) {\n return new HtmlDiff(oldText, newText).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 this.splitInputsToWords()\n\n this.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(this.oldWords.length, this.newWords.length)\n )\n\n const operations = this.operations()\n for (const op of operations) {\n this.performOperation(op)\n }\n\n return this.content.join('')\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 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 text = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.insertTag(HtmlDiff.InsTag, cssClass, text)\n }\n\n private processDeleteOperation(operation: Operation, cssClass: string) {\n const text = this.oldWords.filter((_, pos) => pos >= operation.startInOld && pos < operation.endInOld)\n this.insertTag(HtmlDiff.DelTag, cssClass, text)\n }\n\n private processEqualOperation(operation: Operation) {\n const result = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.content.push(result.join(''))\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 this.specialTagDiffStack.push(words[0])\n specialCaseTagInjection = \"<ins class='mod'>\"\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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n const openingTag = this.specialTagDiffStack.length === 0 ? null : this.specialTagDiffStack.pop()\n const openingAndClosingTagsMatch =\n !!openingTag && Utils.getTagName(openingTag) === Utils.getTagName(words[indexLastTagInFirstTagBlock])\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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n words.shift()\n }\n }\n }\n\n if (words.length === 0 && specialCaseTagInjection.length === 0) {\n break\n }\n\n if (specialCaseTagInjectionIsBefore) {\n this.content.push(specialCaseTagInjection + this.extractConsecutiveWords(words, Utils.isTag).join(''))\n } else {\n this.content.push(this.extractConsecutiveWords(words, Utils.isTag).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] = '&nbsp;'\n }\n if (!condition(word)) {\n indexOfFirstTag = i\n break\n }\n }\n\n if (indexOfFirstTag !== null) {\n const items = words.filter((s, pos) => pos >= 0 && pos < indexOfFirstTag)\n if (indexOfFirstTag > 0) {\n words.splice(0, indexOfFirstTag)\n }\n return items\n }\n\n const items = words.filter((s, pos) => pos >= 0 && pos <= words.length)\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 matches = this.matchingBlocks()\n matches.push(new Match(this.oldWords.length, this.newWords.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 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 const oldDistanceInChars = this.oldWords\n .slice(prev.endInOld, next.startInOld)\n .reduce((acc, word) => acc + word.length, 0)\n const newDistanceInChars = this.newWords\n .slice(prev.endInNew, next.startInNew)\n .reduce((acc, word) => acc + word.length, 0)\n const currMatchLengthInChars = this.newWords\n .slice(curr.startInNew, curr.endInNew)\n .reduce((acc, word) => acc + word.length, 0)\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 matchingBlocks: Match[] = []\n this.findMatchingBlocks(0, this.oldWords.length, 0, this.newWords.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 // 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(this.oldWords, this.newWords, startInOld, endInOld, startInNew, endInNew, options)\n const match = finder.findMatch()\n if (match !== null) return match\n }\n return null\n }\n}\n"],"mappings":"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","names":["Action","Mode","items"]}
1
+ {"version":3,"sources":["../src/Action.ts","../src/Match.ts","../src/Utils.ts","../src/MatchFinder.ts","../src/Operation.ts","../src/Mode.ts","../src/WordSplitter.ts","../src/HtmlDiff.ts"],"sourcesContent":["enum Action {\n Equal = 0,\n Delete = 1,\n Insert = 2,\n None = 3,\n Replace = 4,\n}\n\nexport default Action\n","export default class Match {\n private _startInOld: number\n private _startInNew: number\n private _size: number\n\n constructor(startInOld: number, startInNew: number, size: number) {\n this._startInOld = startInOld\n this._startInNew = startInNew\n this._size = size\n }\n\n get startInOld() {\n return this._startInOld\n }\n\n get startInNew() {\n return this._startInNew\n }\n\n get size() {\n return this._size\n }\n\n get endInOld() {\n return this._startInOld + this._size\n }\n\n get endInNew() {\n return this._startInNew + this._size\n }\n}\n","const openingTagRegex = /^\\s*<[^>]+>\\s*$/\nconst closingTagTexRegex = /^\\s*<\\/[^>]+>\\s*$/\nconst tagWordRegex = /<[^\\s>]+/\nconst whitespaceRegex = /^(\\s|&nbsp;)+$/\nconst wordRegex = /[\\w#@]+/\nconst tagRegex = /<\\/?(?<name>[^\\s\\/>]+)[^>]*>/\n\nconst SpecialCaseWordTags: readonly string[] = ['<img']\n\nexport function isTag(item: string): boolean {\n if (SpecialCaseWordTags.some(re => item?.startsWith(re))) {\n return false\n }\n\n return isOpeningTag(item) || isClosingTag(item)\n}\n\nfunction isOpeningTag(item: string): boolean {\n return openingTagRegex.test(item)\n}\n\nfunction isClosingTag(item: string): boolean {\n return closingTagTexRegex.test(item)\n}\n\nexport function stripTagAttributes(word: string): string {\n const match = tagWordRegex.exec(word)\n if (match) {\n return `${match[0]}${word.endsWith('/>') ? '/>' : '>'}`\n }\n\n return word\n}\n\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 this.wordIndices = {\n ...this.wordIndices,\n [key]: [...(this.wordIndices[key] ?? []), i],\n }\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 if (Object.keys(this.wordIndices).length === 0) {\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 (!Object.keys(this.wordIndices).includes(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","enum Mode {\n Character = 0,\n Tag = 1,\n Whitespace = 2,\n Entity = 3,\n}\n\nexport default Mode\n","import Mode from './Mode'\nimport Utils from './Utils'\n\nexport default class WordSplitter {\n private text: string\n private isBlockCheckRequired: boolean\n private blockLocations: BlockFinderResult\n private mode: Mode\n private isGrouping = false\n private globbingUntil: number\n private currentWord: string[]\n private words: string[]\n private static NotGlobbing = -1\n\n private get currentWordHasChars() {\n return this.currentWord.length > 0\n }\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockLocations = new BlockFinder(text, blockExpressions).findBlocks()\n this.isBlockCheckRequired = this.blockLocations.hasBlocks\n this.mode = Mode.Character\n this.globbingUntil = WordSplitter.NotGlobbing\n this.currentWord = []\n this.words = []\n }\n\n process(): string[] {\n for (let index = 0; index < this.text.length; index++) {\n const character = this.text.charAt(index)\n this.processCharacter(index, character)\n }\n\n this.appendCurrentWordToWords()\n return this.words\n }\n\n private processCharacter(index: number, character: string) {\n if (this.isGlobbing(index, character)) {\n return\n }\n\n switch (this.mode) {\n case Mode.Character:\n this.processTextCharacter(character)\n break\n case Mode.Tag:\n this.processHtmlTagContinuation(character)\n break\n case Mode.Whitespace:\n this.processWhiteSpaceContinuation(character)\n break\n case Mode.Entity:\n this.processEntityContinuation(character)\n break\n }\n }\n\n private processEntityContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (character.trim().length === 0) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (Utils.isEndOfEntity(character)) {\n let switchToNextMode = true\n if (this.currentWordHasChars) {\n this.currentWord.push(character)\n this.words.push(this.currentWord.join(''))\n\n //join &nbsp; entity with last whitespace\n if (\n this.words.length > 2 &&\n Utils.isWhiteSpace(this.words[this.words.length - 2]) &&\n Utils.isWhiteSpace(this.words[this.words.length - 1])\n ) {\n const w1 = this.words[this.words.length - 2]\n const w2 = this.words[this.words.length - 1]\n this.words.splice(this.words.length - 2, 2)\n this.currentWord = `${w1}${w2}`.split('')\n this.mode = Mode.Whitespace\n switchToNextMode = false\n }\n }\n\n if (switchToNextMode) {\n this.currentWord = []\n this.mode = Mode.Character\n }\n } else if (Utils.isWord(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processWhiteSpaceContinuation(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n }\n\n private processHtmlTagContinuation(character: string) {\n if (Utils.isEndOfTag(character)) {\n this.currentWord.push(character)\n this.appendCurrentWordToWords()\n this.mode = Utils.isWhiteSpace(character) ? Mode.Whitespace : Mode.Character\n } else {\n this.currentWord.push(character)\n }\n }\n\n private processTextCharacter(character: string) {\n if (Utils.isStartOfTag(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push('<')\n this.mode = Mode.Tag\n } else if (Utils.isStartOfEntity(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Entity\n } else if (Utils.isWhiteSpace(character)) {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n this.mode = Mode.Whitespace\n } else if (\n Utils.isWord(character) &&\n (this.currentWord.length === 0 || Utils.isWord(this.currentWord[this.currentWord.length - 1]))\n ) {\n this.currentWord.push(character)\n } else {\n this.appendCurrentWordToWords()\n this.currentWord.push(character)\n }\n }\n\n private appendCurrentWordToWords() {\n if (this.currentWordHasChars) {\n this.words.push(this.currentWord.join(''))\n this.currentWord = []\n }\n }\n\n private isGlobbing(index: number, character: string): boolean {\n if (!this.isBlockCheckRequired) {\n return false\n }\n const isCurrentBlockTerminating = index === this.globbingUntil\n if (isCurrentBlockTerminating) {\n this.globbingUntil = WordSplitter.NotGlobbing\n this.isGrouping = false\n this.appendCurrentWordToWords()\n }\n\n const until = this.blockLocations.isInBlock(index)\n if (until) {\n this.isGrouping = true\n this.globbingUntil = until\n }\n if (this.isGrouping) {\n this.currentWord.push(character)\n this.mode = Mode.Character\n }\n return this.isGrouping\n }\n\n static convertHtmlToListOfWords(text: string, blockExpressions: RegExp[]): string[] {\n return new WordSplitter(text, blockExpressions).process()\n }\n}\n\nclass BlockFinderResult {\n private blocks: Map<number, number> = new Map()\n\n addBlock(from: number, to: number) {\n if (this.blocks.has(from)) {\n throw new ArgumentError('One or more block expressions result in a text sequence that overlaps.')\n }\n\n this.blocks.set(from, to)\n }\n\n isInBlock(location: number): number | null {\n return this.blocks.get(location) ?? null\n }\n\n get hasBlocks() {\n return this.blocks.size > 0\n }\n}\n\nclass ArgumentError extends Error {}\n\nclass BlockFinder {\n private text: string\n private blockExpressions: RegExp[]\n\n constructor(text: string, blockExpressions: RegExp[]) {\n this.text = text\n this.blockExpressions = blockExpressions\n }\n\n findBlocks(): BlockFinderResult {\n const result = new BlockFinderResult()\n for (const expression of this.blockExpressions) {\n this.processBlockMatcher(expression, result)\n }\n return result\n }\n\n private processBlockMatcher(exp: RegExp, result: BlockFinderResult) {\n let match: RegExpExecArray | null\n // biome-ignore lint/suspicious/noAssignInExpressions: Couldn't think of a nicer way to do this\n while ((match = exp.exec(this.text)) !== null) {\n this.tryAddBlock(exp, match, result)\n }\n }\n\n private tryAddBlock(exp: RegExp, match: RegExpExecArray, result: BlockFinderResult) {\n try {\n const from = match.index\n const to = match.index + match[0].length\n result.addBlock(from, to)\n } catch (e) {\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 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 SpecialCaseOpeningTagRegex =\n /<((strong)|(b)|(i)|(em)|(big)|(small)|(u)|(sub)|(sup)|(strike)|(s)|(span))[>\\s]+/i\n\n private content: string[] = []\n private newText: string\n private oldText: string\n\n private specialTagDiffStack: string[] = []\n private newWords: string[] = []\n private oldWords: string[] = []\n private matchGranularity = 0\n private blockExpressions: RegExp[] = []\n\n /**\n * Defines how to compare repeating words. Valid values are from 0 to 1.\n * This value allows to exclude some words from comparison that eventually\n * reduces the total time of the diff algorithm.\n * 0 means that all words are excluded so the diff will not find any matching words at all.\n * 1 (default value) means that all words participate in comparison so this is the most accurate case.\n * 0.5 means that any word that occurs more than 50% times may be excluded from comparison. This doesn't\n * mean that such words will definitely be excluded but only gives a permission to exclude them if necessary.\n */\n repeatingWordsAccuracy = 1.0\n\n /**\n * If true all whitespaces are considered as equal\n */\n ignoreWhitespaceDifferences = false\n\n /**\n * If some match is too small and located far from its neighbors then it is considered as orphan\n * and removed. For example:\n * <code>\n * aaaaa bb ccccccccc dddddd ee\n * 11111 bb 222222222 dddddd ee\n * </code>\n * will find two matches <code>bb</code> and <code>dddddd ee</code> but the first will be considered\n * as orphan and ignored, as result it will consider texts <code>aaaaa bb ccccccccc</code> and\n * <code>11111 bb 222222222</code> as single replacement:\n * <code>\n * &lt;del&gt;aaaaa bb ccccccccc&lt;/del&gt;&lt;ins&gt;11111 bb 222222222&lt;/ins&gt; dddddd ee\n * </code>\n * This property defines relative size of the match to be considered as orphan, from 0 to 1.\n * 1 means that all matches will be considered as orphans.\n * 0 (default) means that no match will be considered as orphan.\n * 0.2 means that if match length is less than 20% of distance between its neighbors it is considered as orphan.\n */\n orphanMatchThreshold = 0.0\n\n /**\n * Initializes a new instance of the class.\n * @param oldText The old text.\n * @param newText The new text.\n */\n constructor(oldText: string, newText: string) {\n this.oldText = oldText\n this.newText = newText\n }\n\n static execute(oldText: string, newText: string) {\n return new HtmlDiff(oldText, newText).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 this.splitInputsToWords()\n\n this.matchGranularity = Math.min(\n HtmlDiff.MatchGranularityMaximum,\n Math.min(this.oldWords.length, this.newWords.length)\n )\n\n const operations = this.operations()\n for (const op of operations) {\n this.performOperation(op)\n }\n\n return this.content.join('')\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 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 text = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.insertTag(HtmlDiff.InsTag, cssClass, text)\n }\n\n private processDeleteOperation(operation: Operation, cssClass: string) {\n const text = this.oldWords.filter((_, pos) => pos >= operation.startInOld && pos < operation.endInOld)\n this.insertTag(HtmlDiff.DelTag, cssClass, text)\n }\n\n private processEqualOperation(operation: Operation) {\n const result = this.newWords.filter((_, pos) => pos >= operation.startInNew && pos < operation.endInNew)\n this.content.push(result.join(''))\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 styledTagNames = words\n .filter(word => Utils.isTag(word))\n .map(style => Utils.getTagName(style))\n .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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n const openingTag = this.specialTagDiffStack.length === 0 ? null : this.specialTagDiffStack.pop()\n const openingAndClosingTagsMatch =\n !!openingTag && Utils.getTagName(openingTag) === Utils.getTagName(words[indexLastTagInFirstTagBlock])\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.SpecialCaseClosingTags.includes(words[0].toLowerCase())) {\n words.shift()\n }\n }\n }\n\n if (words.length === 0 && specialCaseTagInjection.length === 0) {\n break\n }\n\n if (specialCaseTagInjectionIsBefore) {\n this.content.push(specialCaseTagInjection + this.extractConsecutiveWords(words, Utils.isTag).join(''))\n } else {\n this.content.push(this.extractConsecutiveWords(words, Utils.isTag).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] = '&nbsp;'\n }\n if (!condition(word)) {\n indexOfFirstTag = i\n break\n }\n }\n\n if (indexOfFirstTag !== null) {\n const items = words.filter((s, pos) => pos >= 0 && pos < indexOfFirstTag)\n if (indexOfFirstTag > 0) {\n words.splice(0, indexOfFirstTag)\n }\n return items\n }\n\n const items = words.filter((s, pos) => pos >= 0 && pos <= words.length)\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 matches = this.matchingBlocks()\n matches.push(new Match(this.oldWords.length, this.newWords.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 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 const oldDistanceInChars = this.oldWords\n .slice(prev.endInOld, next.startInOld)\n .reduce((acc, word) => acc + word.length, 0)\n const newDistanceInChars = this.newWords\n .slice(prev.endInNew, next.startInNew)\n .reduce((acc, word) => acc + word.length, 0)\n const currMatchLengthInChars = this.newWords\n .slice(curr.startInNew, curr.endInNew)\n .reduce((acc, word) => acc + word.length, 0)\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 matchingBlocks: Match[] = []\n this.findMatchingBlocks(0, this.oldWords.length, 0, this.newWords.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 // 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(this.oldWords, this.newWords, startInOld, endInOld, startInNew, endInNew, options)\n const match = finder.findMatch()\n if (match !== null) return match\n }\n return null\n }\n}\n"],"mappings":"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","names":["Action","Mode","items"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@createiq/htmldiff",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "TypeScript port of htmldiff.net",
5
5
  "type": "module",
6
6
  "author": "Mathew Mannion <mathew.mannion@linklaters.com>",
package/src/HtmlDiff.ts CHANGED
@@ -232,8 +232,13 @@ export default class HtmlDiff {
232
232
 
233
233
  // handle opening tag
234
234
  if (HtmlDiff.SpecialCaseOpeningTagRegex.test(words[0])) {
235
+ const styledTagNames = words
236
+ .filter(word => Utils.isTag(word))
237
+ .map(style => Utils.getTagName(style))
238
+ .join(' ')
239
+
235
240
  this.specialTagDiffStack.push(words[0])
236
- specialCaseTagInjection = "<ins class='mod'>"
241
+ specialCaseTagInjection = `<ins class='mod ${styledTagNames}'>`
237
242
  if (tag === HtmlDiff.DelTag) {
238
243
  words.shift()
239
244
 
@@ -12,7 +12,7 @@ describe('HtmlDiff', () => {
12
12
  ],
13
13
  ['a c', 'a b c', "a <ins class='diffins'>b </ins>c"],
14
14
  ['a b c', 'a c', "a <del class='diffdel'>b </del>c"],
15
- ['a b c', 'a <strong>b</strong> c', "a <strong><ins class='mod'>b</ins></strong> c"],
15
+ ['a b c', 'a <strong>b</strong> c', "a <strong><ins class='mod strong'>b</ins></strong> c"],
16
16
  ['a b c', 'a d c', "a <del class='diffmod'>b</del><ins class='diffmod'>d</ins> c"],
17
17
  ["<a title='xx'>test</a>", "<a title='yy'>test</a>", "<a title='yy'>test</a>"],
18
18
  ["<img src='logo.jpg'/>", '', "<del class='diffdel'><img src='logo.jpg'/></del>"],
@@ -30,7 +30,7 @@ describe('HtmlDiff', () => {
30
30
  [
31
31
  'This is a longer piece of text to ensure the new blocksize algorithm works',
32
32
  'This is a longer piece of text to <strong>ensure</strong> the new blocksize algorithm works decently',
33
- "This is a longer piece of text to <strong><ins class='mod'>ensure</ins></strong> the new blocksize algorithm works<ins class='diffins'>&nbsp;decently</ins>",
33
+ "This is a longer piece of text to <strong><ins class='mod strong'>ensure</ins></strong> the new blocksize algorithm works<ins class='diffins'>&nbsp;decently</ins>",
34
34
  ],
35
35
  [
36
36
  'By virtue of an agreement between xxx and the <b>yyy schools</b>, ...',
@@ -40,12 +40,12 @@ describe('HtmlDiff', () => {
40
40
  [
41
41
  'Some plain text',
42
42
  'Some <strong><i>plain</i></strong> text',
43
- "Some <strong><i><ins class='mod'>plain</ins></i></strong> text",
43
+ "Some <strong><i><ins class='mod strong i'>plain</ins></i></strong> text",
44
44
  ],
45
45
  [
46
46
  'Some <strong><i>formatted</i></strong> text',
47
47
  'Some formatted text',
48
- "Some <ins class='mod'>formatted</ins> text",
48
+ "Some <ins class='mod strong i'>formatted</ins> text",
49
49
  ],
50
50
  [
51
51
  '<table><tr><td>col1</td><td>col2</td></tr><tr><td>Data 1</td><td>Data 2</td></tr></table>',
@@ -55,7 +55,7 @@ describe('HtmlDiff', () => {
55
55
  [
56
56
  'text',
57
57
  '<span style="text-decoration: line-through;">text</span>',
58
- '<span style="text-decoration: line-through;"><ins class=\'mod\'>text</ins></span>',
58
+ '<span style="text-decoration: line-through;"><ins class=\'mod span\'>text</ins></span>',
59
59
  ],
60
60
 
61
61
  // TODO: Don't speak Chinese, this needs to be validated
@@ -145,35 +145,35 @@ describe('HtmlDiff', () => {
145
145
  [
146
146
  '<div class="dumb">Thiis a text without any sup-tags and other special things</div>',
147
147
  '<div class="dumb">Thiis a text <sup>1</sup>without any sup-tags and other special things</div>',
148
- "<div class=\"dumb\">Thiis a text <sup><ins class='mod'><ins class='diffins'>1</ins></ins></sup>without any sup-tags and other special things</div>",
148
+ "<div class=\"dumb\">Thiis a text <sup><ins class='mod sup sup'><ins class='diffins'>1</ins></ins></sup>without any sup-tags and other special things</div>",
149
149
  ],
150
150
 
151
151
  // Inserting a new word at the end of the reformatted text
152
152
  [
153
153
  '<span>text remains</span>',
154
154
  '<span><strong>text remains Test</strong></span>',
155
- "<span><strong><ins class='mod'>text remains<ins class='diffins'>&nbsp;Test</ins></ins></strong></span>",
155
+ "<span><strong><ins class='mod strong'>text remains<ins class='diffins'>&nbsp;Test</ins></ins></strong></span>",
156
156
  ],
157
157
 
158
158
  // Inserting a new word at the end of a reformatted text and another word at the end outside the reformatted text
159
159
  [
160
160
  '<span>text remains</span>',
161
161
  '<span><strong>text remains Test</strong> Test</span>',
162
- "<span><strong><ins class='mod'>text remains<ins class='diffins'>&nbsp;Test</ins></ins></strong><ins class='diffins'>&nbsp;Test</ins></span>",
162
+ "<span><strong><ins class='mod strong'>text remains<ins class='diffins'>&nbsp;Test</ins></ins></strong><ins class='diffins'>&nbsp;Test</ins></span>",
163
163
  ],
164
164
 
165
165
  // Twice reformatted text with an offset at the end
166
166
  [
167
167
  '<span>text remains</span>',
168
168
  '<span><strong><big>text </big>remains</strong></span>',
169
- "<span><strong><big><ins class='mod'>text </big>remains</ins></strong></span>",
169
+ "<span><strong><big><ins class='mod strong big'>text </big>remains</ins></strong></span>",
170
170
  ],
171
171
 
172
172
  // Inserting a new word at the beginning of a reformatted text.
173
173
  [
174
174
  '<span>text remains</span>',
175
175
  '<span><strong>Test text remains</strong></span>',
176
- "<span><strong><ins class='mod'><ins class='diffins'>Test </ins>text remains</ins></strong></span>",
176
+ "<span><strong><ins class='mod strong'><ins class='diffins'>Test </ins>text remains</ins></strong></span>",
177
177
  ],
178
178
  ] as const)('should handle missing closing tag in diff (%s, %s) -> %s', ([oldText, newText, expected]) => {
179
179
  expect(HtmlDiff.execute(oldText, newText)).toEqual(expected)
@@ -183,12 +183,12 @@ describe('HtmlDiff', () => {
183
183
  [
184
184
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
185
185
  '<p>The additional term only applies where <strong><em>5 years</em></strong> have elapsed since the execution of this language.</p>',
186
- `<p>The additional term only applies where <strong><em><ins class='mod'>5 years</ins></em></strong> have elapsed since the execution of this language.</p>`,
186
+ `<p>The additional term only applies where <strong><em><ins class='mod strong em'>5 years</ins></em></strong> have elapsed since the execution of this language.</p>`,
187
187
  ],
188
188
  [
189
189
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
190
190
  '<p>The additional term only applies where <em><strong>5 years</strong></em> have elapsed since the execution of this language.</p>',
191
- `<p>The additional term only applies where <em><strong><ins class='mod'>5 years</ins></strong></em> have elapsed since the execution of this language.</p>`,
191
+ `<p>The additional term only applies where <em><strong><ins class='mod em strong'>5 years</ins></strong></em> have elapsed since the execution of this language.</p>`,
192
192
  ],
193
193
  [
194
194
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
@@ -198,12 +198,12 @@ describe('HtmlDiff', () => {
198
198
  [
199
199
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
200
200
  '<p>The additional term only applies where [<strong><em>5 years</em></strong>] have elapsed since the execution of this language.</p>',
201
- `<p>The additional term only applies where <ins class='diffins'>[</ins><strong><em><ins class='mod'>5 years</ins></em></strong><ins class='diffins'>]</ins> have elapsed since the execution of this language.</p>`,
201
+ `<p>The additional term only applies where <ins class='diffins'>[</ins><strong><em><ins class='mod strong em'>5 years</ins></em></strong><ins class='diffins'>]</ins> have elapsed since the execution of this language.</p>`,
202
202
  ],
203
203
  [
204
204
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
205
205
  '<p>The additional term only applies where [<em><strong>5 years</strong></em>] have elapsed since the execution of this language.</p>',
206
- `<p>The additional term only applies where <ins class='diffins'>[</ins><em><strong><ins class='mod'>5 years</ins></strong></em><ins class='diffins'>]</ins> have elapsed since the execution of this language.</p>`,
206
+ `<p>The additional term only applies where <ins class='diffins'>[</ins><em><strong><ins class='mod em strong'>5 years</ins></strong></em><ins class='diffins'>]</ins> have elapsed since the execution of this language.</p>`,
207
207
  ],
208
208
  ] as const)('should work when the comparison adds styling', ([oldText, newText, expected]) => {
209
209
  expect(HtmlDiff.execute(oldText, newText)).toEqual(expected)
@@ -213,12 +213,12 @@ describe('HtmlDiff', () => {
213
213
  [
214
214
  '<p>The additional term only applies where <strong><em>5 years</em></strong> have elapsed since the execution of this language.</p>',
215
215
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
216
- `<p>The additional term only applies where <ins class='mod'>5 years</ins> have elapsed since the execution of this language.</p>`,
216
+ `<p>The additional term only applies where <ins class='mod strong em'>5 years</ins> have elapsed since the execution of this language.</p>`,
217
217
  ],
218
218
  [
219
219
  '<p>The additional term only applies where <em><strong>5 years</strong></em> have elapsed since the execution of this language.</p>',
220
220
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
221
- `<p>The additional term only applies where <ins class='mod'>5 years</ins> have elapsed since the execution of this language.</p>`,
221
+ `<p>The additional term only applies where <ins class='mod em strong'>5 years</ins> have elapsed since the execution of this language.</p>`,
222
222
  ],
223
223
  [
224
224
  '<p>The additional term only applies where [5 years] have elapsed since the execution of this language.</p>',
@@ -228,22 +228,22 @@ describe('HtmlDiff', () => {
228
228
  [
229
229
  '<p>The additional term only applies where [<strong><em>5 years</em></strong>] have elapsed since the execution of this language.</p>',
230
230
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
231
- `<p>The additional term only applies where <del class='diffdel'>[</del><ins class='mod'>5 years</ins><del class='diffdel'>]</del> have elapsed since the execution of this language.</p>`,
231
+ `<p>The additional term only applies where <del class='diffdel'>[</del><ins class='mod strong em'>5 years</ins><del class='diffdel'>]</del> have elapsed since the execution of this language.</p>`,
232
232
  ],
233
233
  [
234
234
  '<p>The additional term only applies where [<em><strong>5 years</strong></em>] have elapsed since the execution of this language.</p>',
235
235
  '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
236
- `<p>The additional term only applies where <del class='diffdel'>[</del><ins class='mod'>5 years</ins><del class='diffdel'>]</del> have elapsed since the execution of this language.</p>`,
236
+ `<p>The additional term only applies where <del class='diffdel'>[</del><ins class='mod em strong'>5 years</ins><del class='diffdel'>]</del> have elapsed since the execution of this language.</p>`,
237
237
  ],
238
238
  [
239
239
  '<p>The additional term only applies where [<strong><em>5 years</em></strong>] have elapsed since the execution of this language.</p>',
240
240
  '<p>The additional term only applies where [<strong>5 years</strong>] have elapsed since the execution of this language.</p>',
241
- `<p>The additional term only applies where [<strong><ins class='mod'>5 years</ins></strong>] have elapsed since the execution of this language.</p>`,
241
+ `<p>The additional term only applies where [<strong><ins class='mod em'>5 years</ins></strong>] have elapsed since the execution of this language.</p>`,
242
242
  ],
243
243
  [
244
244
  '<p>The additional term only applies where <strong><em>5 years</em></strong> have elapsed since the execution of this language.</p>',
245
245
  '<p>The additional term only applies where [5 years] have elapsed since the execution of this language.</p>',
246
- `<p>The additional term only applies where <ins class='mod'><ins class='diffmod'>[</ins>5 years</ins><ins class='diffmod'>]</ins> have elapsed since the execution of this language.</p>`,
246
+ `<p>The additional term only applies where <ins class='mod strong em'><ins class='diffmod'>[</ins>5 years</ins><ins class='diffmod'>]</ins> have elapsed since the execution of this language.</p>`,
247
247
  ],
248
248
  ] as const)('should work when the comparison removes styling', ([oldText, newText, expected]) => {
249
249
  expect(HtmlDiff.execute(oldText, newText)).toEqual(expected)