{"version":3,"sources":["jsdelivr-header.js","/npm/node-htmldiff@0.9.4/js/htmldiff.js"],"names":[],"mappings":"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","file":"/npm/node-htmldiff@0.9.4/js/htmldiff.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.37.0.\n * Original file: /npm/node-htmldiff@0.9.4/js/htmldiff.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","/**\n * htmldiff.js is a library that compares HTML content. It creates a diff between two\n * HTML documents by combining the two documents and wrapping the differences with\n * <ins> and <del> tags. Here is a high-level overview of how the diff works.\n *\n * 1. Tokenize the before and after HTML with htmlToTokens.\n * 2. Generate a list of operations that convert the before list of tokens to the after\n *    list of tokens with calculateOperations, which does the following:\n *      a. Find all the matching blocks of tokens between the before and after lists of\n *         tokens with findMatchingBlocks. This is done by finding the single longest\n *         matching block with findMatch, then iteratively finding the next longest\n *         matching blocks that precede and follow the longest matching block.\n *      b. Determine insertions, deletions, and replacements from the matching blocks.\n *         This is done in calculateOperations.\n * 3. Render the list of operations by wrapping tokens with <ins> and <del> tags where\n *    appropriate with renderOperations.\n *\n * Example usage:\n *\n *   var htmldiff = require('htmldiff.js');\n *\n *   htmldiff('<p>this is some text</p>', '<p>this is some more text</p>')\n *   == '<p>this is some <ins>more </ins>text</p>'\n *\n *   htmldiff('<p>this is some text</p>', '<p>this is some more text</p>', 'diff-class')\n *   == '<p>this is some <ins class=\"diff-class\">more </ins>text</p>'\n */\n(function(){\n    'use strict';\n\n    function isEndOfTag(char){\n        return char === '>';\n    }\n\n    function isStartOfTag(char){\n        return char === '<';\n    }\n\n    function isWhitespace(char){\n        return /^\\s+$/.test(char);\n    }\n\n    /**\n     * Determines if the given token is a tag.\n     *\n     * @param {string} token The token in question.\n     *\n     * @return {boolean|string} False if the token is not a tag, or the tag name otherwise.\n     */\n    function isTag(token){\n        var match = token.match(/^\\s*<([^!>][^>]*)>\\s*$/);\n        return !!match && match[1].trim().split(' ')[0];\n    }\n\n    function isntTag(token){\n        return !isTag(token);\n    }\n\n    function isStartofHTMLComment(word){\n        return /^<!--/.test(word);\n    }\n\n    function isEndOfHTMLComment(word){\n        return /--\\>$/.test(word);\n    }\n\n    /**\n     * Regular expression to check atomic tags.\n     * @see function diff.\n     */\n    var atomicTagsRegExp;\n    // Added head and style (for style tags inside the body)\n    var defaultAtomicTagsRegExp = new RegExp('^<(iframe|object|math|svg|script|video|head|style|a)');\n    \n    /**\n     * Checks if the current word is the beginning of an atomic tag. An atomic tag is one whose\n     * child nodes should not be compared - the entire tag should be treated as one token. This\n     * is useful for tags where it does not make sense to insert <ins> and <del> tags.\n     *\n     * @param {string} word The characters of the current token read so far.\n     *\n     * @return {string|null} The name of the atomic tag if the word will be an atomic tag,\n     *    null otherwise\n     */\n    function isStartOfAtomicTag(word){\n        var result = atomicTagsRegExp.exec(word);\n        return result && result[1];\n    }\n\n    /**\n     * Checks if the current word is the end of an atomic tag (i.e. it has all the characters,\n     * except for the end bracket of the closing tag, such as '<iframe></iframe').\n     *\n     * @param {string} word The characters of the current token read so far.\n     * @param {string} tag The ending tag to look for.\n     *\n     * @return {boolean} True if the word is now a complete token (including the end tag),\n     *    false otherwise.\n     */\n    function isEndOfAtomicTag(word, tag){\n        return word.substring(word.length - tag.length - 2) === ('</' + tag);\n    }\n\n    /**\n     * Checks if a tag is a void tag.\n     *\n     * @param {string} token The token to check.\n     *\n     * @return {boolean} True if the token is a void tag, false otherwise.\n     */\n    function isVoidTag(token){\n        return /^\\s*<[^>]+\\/>\\s*$/.test(token);\n    }\n\n    /**\n     * Checks if a token can be wrapped inside a tag.\n     *\n     * @param {string} token The token to check.\n     *\n     * @return {boolean} True if the token can be wrapped inside a tag, false otherwise.\n     */\n    function isWrappable(token){\n        var is_img = /^<img[\\s>]/.test(token);\n        return is_img|| isntTag(token) || isStartOfAtomicTag(token) || isVoidTag(token);\n    }\n\n    /**\n     * Creates a token that holds a string and key representation. The key is used for diffing\n     * comparisons and the string is used to recompose the document after the diff is complete.\n     *\n     * @param {string} currentWord The section of the document to create a token for.\n     *\n     * @return {Object} A token object with a string and key property.\n     */\n    function createToken(currentWord){\n        return {\n            string: currentWord,\n            key: getKeyForToken(currentWord)\n        };\n    }\n\n    /**\n     * A Match stores the information of a matching block. A matching block is a list of\n     * consecutive tokens that appear in both the before and after lists of tokens.\n     *\n     * @param {number} startInBefore The index of the first token in the list of before tokens.\n     * @param {number} startInAfter The index of the first token in the list of after tokens.\n     * @param {number} length The number of consecutive matching tokens in this block.\n     * @param {Segment} segment The segment where the match was found.\n     */\n    function Match(startInBefore, startInAfter, length, segment){\n        this.segment = segment;\n        this.length = length;\n\n        this.startInBefore = startInBefore + segment.beforeIndex;\n        this.startInAfter = startInAfter + segment.afterIndex;\n        this.endInBefore = this.startInBefore + this.length - 1;\n        this.endInAfter = this.startInAfter + this.length - 1;\n\n        this.segmentStartInBefore = startInBefore;\n        this.segmentStartInAfter = startInAfter;\n        this.segmentEndInBefore = (this.segmentStartInBefore + this.length) - 1;\n        this.segmentEndInAfter = (this.segmentStartInAfter + this.length) - 1;\n    }\n\n    /**\n     * Tokenizes a string of HTML.\n     *\n     * @param {string} html The string to tokenize.\n     *\n     * @return {Array.<string>} The list of tokens.\n     */\n    function htmlToTokens(html){\n        var mode = 'char';\n        var currentWord = '';\n        var currentAtomicTag = '';\n        var words = [];\n        for (var i = 0; i < html.length; i++){\n            var char = html[i];\n            switch (mode){\n                case 'tag':\n                    var atomicTag = isStartOfAtomicTag(currentWord);\n                    if (atomicTag){\n                        mode = 'atomic_tag';\n                        currentAtomicTag = atomicTag;\n                        currentWord += char;\n                    } else if (isStartofHTMLComment(currentWord)){\n                        mode = 'html_comment';\n                        currentWord += char;\n                    } else if (isEndOfTag(char)){\n                        currentWord += '>';\n                        words.push(createToken(currentWord));\n                        currentWord = '';\n                        if (isWhitespace(char)){\n                            mode = 'whitespace';\n                        } else {\n                            mode = 'char';\n                        }\n                    } else {\n                        currentWord += char;\n                    }\n                    break;\n                case 'atomic_tag':\n                    if (isEndOfTag(char) && isEndOfAtomicTag(currentWord, currentAtomicTag)){\n                        currentWord += '>';\n                        words.push(createToken(currentWord));\n                        currentWord = '';\n                        currentAtomicTag = '';\n                        mode = 'char';\n                    } else {\n                        currentWord += char;\n                    }\n                    break;\n                case 'html_comment':\n                    currentWord += char;\n                    if (isEndOfHTMLComment(currentWord)){\n                        currentWord = '';\n                        mode = 'char';\n                    }\n                    break;\n                case 'char':\n                    if (isStartOfTag(char)){\n                        if (currentWord){\n                            words.push(createToken(currentWord));\n                        }\n                        currentWord = '<';\n                        mode = 'tag';\n                    } else if (/\\s/.test(char)){\n                        if (currentWord){\n                            words.push(createToken(currentWord));\n                        }\n                        currentWord = char;\n                        mode = 'whitespace';\n                    } else if (/[\\w\\d\\#@]/.test(char)){\n                        currentWord += char;\n                    } else if (/&/.test(char)){\n                        if (currentWord){\n                            words.push(createToken(currentWord));\n                        }\n                        currentWord = char;\n                    } else {\n                        currentWord += char;\n                        words.push(createToken(currentWord));\n                        currentWord = '';\n                    }\n                    break;\n                case 'whitespace':\n                    if (isStartOfTag(char)){\n                        if (currentWord){\n                            words.push(createToken(currentWord));\n                        }\n                        currentWord = '<';\n                        mode = 'tag';\n                    } else if (isWhitespace(char)){\n                        currentWord += char;\n                    } else {\n                        if (currentWord){\n                            words.push(createToken(currentWord));\n                        }\n                        currentWord = char;\n                        mode = 'char';\n                    }\n                    break;\n                default:\n                    throw new Error('Unknown mode ' + mode);\n            }\n        }\n        if (currentWord){\n            words.push(createToken(currentWord));\n        }\n        return words;\n    }\n\n    /**\n     * Creates a key that should be used to match tokens. This is useful, for example, if we want\n     * to consider two open tag tokens as equal, even if they don't have the same attributes. We\n     * use a key instead of overwriting the token because we may want to render the original string\n     * without losing the attributes.\n     *\n     * @param {string} token The token to create the key for.\n     *\n     * @return {string} The identifying key that should be used to match before and after tokens.\n     */\n    function getKeyForToken(token){\n        // If the token is an image element, grab it's src attribute to include in the key.\n        var img = /^<img.*src=['\"]([^\"']*)['\"].*>$/.exec(token);\n        if (img) {\n            return '<img src=\"' + img[1] + '\">';\n        }\n        \n        // If the token is an a element, grab it's data attribute to include in the key.\n        var a = /^<a.*href=['\"]([^\"']*)['\"]/.exec(token);\n        if (a) {\n            return '<a href=\"' + a[1] + '\"></a>';\n        }\n\n        // If the token is an object element, grab it's data attribute to include in the key.\n        var object = /^<object.*data=['\"]([^\"']*)['\"]/.exec(token);\n        if (object) {\n            return '<object src=\"' + object[1] + '\"></object>';\n        }\n\n        // If it's a video, math or svg element, the entire token should be compared except the\n        // data-uuid.\n        if(/^<(svg|math|video)[\\s>]/.test(token)) {\n            var uuid = token.indexOf('data-uuid=\"');\n            if (uuid !== -1) {\n                var start = token.slice(0, uuid);\n                var end = token.slice(uuid + 44);\n                return start + end;\n            } else {\n                return token;\n            } \n        }\n\n        // If the token is an iframe element, grab it's src attribute to include in it's key.\n        var iframe = /^<iframe.*src=['\"]([^\"']*)['\"].*>/.exec(token);\n        if (iframe) {\n            return '<iframe src=\"' + iframe[1] + '\"></iframe>';\n        }\n\n        // If the token is any other element, just grab the tag name.\n        var tagName = /<([^\\s>]+)[\\s>]/.exec(token);\n        if (tagName){\n            return '<' + (tagName[1].toLowerCase()) + '>';\n        }\n\n        // Otherwise, the token is text, collapse the whitespace.\n        if (token) {\n            return token.replace(/(\\s+|&nbsp;|&#160;)/g, ' ');\n        }\n        return token;\n    }\n\n    /**\n     * Creates a map from token key to an array of indices of locations of the matching token in\n     * the list of all tokens.\n     *\n     * @param {Array.<string>} tokens The list of tokens to be mapped.\n     *\n     * @return {Object} A mapping that can be used to search for tokens.\n     */\n    function createMap(tokens){\n        return tokens.reduce(function(map, token, index){\n            if (map[token.key]){\n                map[token.key].push(index);\n            } else {\n                map[token.key] = [index];\n            }\n            return map;\n        }, Object.create(null));\n    }\n\n    /**\n     * Compares two match objects to determine if the second match object comes before or after the\n     * first match object. Returns -1 if the m2 should come before m1. Returns 1 if m1 should come\n     * before m2. If the two matches criss-cross each other, a null is returned.\n     *\n     * @param {Match} m1 The first match object to compare.\n     * @param {Match} m2 The second match object to compare.\n     *\n     * @return {number} Returns -1 if the m2 should come before m1. Returns 1 if m1 should come\n     *    before m2. If the two matches criss-cross each other, 0 is returned.\n     */\n    function compareMatches(m1, m2){\n        if (m2.endInBefore < m1.startInBefore && m2.endInAfter < m1.startInAfter){\n            return -1;\n        } else if (m2.startInBefore > m1.endInBefore && m2.startInAfter > m1.endInAfter){\n            return 1;\n        } else {\n            return 0;\n        }\n    }\n\n    /**\n     * A constructor for a binary search tree used to keep match objects in the proper order as\n     * they're found.\n     *\n     * @constructor\n     */\n    function MatchBinarySearchTree(){\n        this._root = null;\n    }\n\n    MatchBinarySearchTree.prototype = {\n        /**\n         * Adds matches to the binary search tree.\n         *\n         * @param {Match} value The match to add to the binary search tree.\n         */\n        add: function (value){\n            // Create the node to hold the match value.\n            var node = {\n                value: value,\n                left: null,\n                right: null\n            };\n\n            var current = this._root;\n            if(current){\n                while (true){\n                    // Determine if the match value should go to the left or right of the current\n                    // node.\n                    var position = compareMatches(current.value, value);\n                    if (position === -1){\n                        // The position of the match is to the left of this node.\n                        if (current.left){\n                            current = current.left;\n                        } else {\n                            current.left = node;\n                            break;\n                        }\n                    } else if (position === 1){\n                        // The position of the match is to the right of this node.\n                        if (current.right){\n                            current = current.right;\n                        } else {\n                            current.right = node;\n                            break;\n                        }\n                    } else {\n                        // If 0 was returned from compareMatches, that means the node cannot\n                        // be inserted because it overlaps an existing node.\n                        break;\n                    }\n                }\n            } else {\n                // If no nodes exist in the tree, make this the root node.\n                this._root = node;\n            }\n        },\n\n        /**\n         * Converts the binary search tree into an array using an in-order traversal.\n         *\n         * @return {Array.<Match>} An array containing the matches in the binary search tree.\n         */\n        toArray: function(){\n            function inOrder(node, nodes){\n                if (node){\n                    inOrder(node.left, nodes);\n                    nodes.push(node.value);\n                    inOrder(node.right, nodes);\n                }\n                return nodes;\n            }\n\n            return inOrder(this._root, []);\n        }\n    };\n\n\n    /**\n     * Finds and returns the best match between the before and after arrays contained in the segment\n     * provided.\n     *\n     * @param {Segment} segment The segment in which to look for a match.\n     *\n     * @return {Match} The best match.\n     */\n    function findBestMatch(segment){\n        var beforeTokens = segment.beforeTokens;\n        var afterMap = segment.afterMap;\n        var lastSpace = null;\n        var bestMatch = null;\n\n        // Iterate through the entirety of the beforeTokens to find the best match.\n        for (var beforeIndex = 0; beforeIndex < beforeTokens.length; beforeIndex++){\n            var lookBehind = false;\n\n            // If the current best match is longer than the remaining tokens, we can bail because we\n            // won't find a better match.\n            var remainingTokens = beforeTokens.length - beforeIndex;\n            if (bestMatch && remainingTokens < bestMatch.length){\n                break;\n            }\n\n            // If the current token is whitespace, make a note of it and move on. Trying to start a\n            // set of matches with whitespace is not efficient because it's too prevelant in most\n            // documents. Instead, if the next token yields a match, we'll see if the whitespace can\n            // be included in that match.\n            var beforeToken = beforeTokens[beforeIndex];\n            if (beforeToken.key === ' '){\n                lastSpace = beforeIndex;\n                continue;\n            }\n\n            // Check to see if we just skipped a space, if so, we'll ask getFullMatch to look behind\n            // by one token to see if it can include the whitespace.\n            if (lastSpace === beforeIndex - 1){\n                lookBehind = true;\n            }\n\n            // If the current token is not found in the afterTokens, it won't match and we can move\n            // on.\n            var afterTokenLocations = afterMap[beforeToken.key];\n            if(!afterTokenLocations){\n                continue;\n            }\n\n            // For each instance of the current token in afterTokens, let's see how big of a match\n            // we can build.\n            afterTokenLocations.forEach(function(afterIndex){\n                // getFullMatch will see how far the current token match will go in both\n                // beforeTokens and afterTokens.\n                var bestMatchLength = bestMatch ? bestMatch.length : 0;\n                var match = getFullMatch(\n                        segment, beforeIndex, afterIndex, bestMatchLength, lookBehind);\n\n                // If we got a new best match, we'll save it aside.\n                if (match && match.length > bestMatchLength){\n                    bestMatch = match;\n                }\n            });\n        }\n\n        return bestMatch;\n    }\n\n    /**\n     * Takes the start of a match, and expands it in the beforeTokens and afterTokens of the\n     * current segment as far as it can go.\n     *\n     * @param {Segment} segment The segment object to search within when expanding the match.\n     * @param {number} beforeStart The offset within beforeTokens to start looking.\n     * @param {number} afterStart The offset within afterTokens to start looking.\n     * @param {number} minLength The minimum length match that must be found.\n     * @param {boolean} lookBehind If true, attempt to match a whitespace token just before the\n     *    beforeStart and afterStart tokens.\n     *\n     * @return {Match} The full match.\n     */\n    function getFullMatch(segment, beforeStart, afterStart, minLength, lookBehind){\n        var beforeTokens = segment.beforeTokens;\n        var afterTokens = segment.afterTokens;\n\n        // If we already have a match that goes to the end of the document, no need to keep looking.\n        var minBeforeIndex = beforeStart + minLength;\n        var minAfterIndex = afterStart + minLength;\n        if(minBeforeIndex >= beforeTokens.length || minAfterIndex >= afterTokens.length){\n            return;\n        }\n\n        // If a minLength was provided, we can do a quick check to see if the tokens after that\n        // length match. If not, we won't be beating the previous best match, and we can bail out\n        // early.\n        if (minLength){\n            var nextBeforeWord = beforeTokens[minBeforeIndex].key;\n            var nextAfterWord = afterTokens[minAfterIndex].key;\n            if (nextBeforeWord !== nextAfterWord){\n                return;\n            }\n        }\n\n        // Extend the current match as far foward as it can go, without overflowing beforeTokens or\n        // afterTokens.\n        var searching = true;\n        var currentLength = 1;\n        var beforeIndex = beforeStart + currentLength;\n        var afterIndex = afterStart + currentLength;\n\n        while (searching && beforeIndex < beforeTokens.length && afterIndex < afterTokens.length){\n            var beforeWord = beforeTokens[beforeIndex].key;\n            var afterWord = afterTokens[afterIndex].key;\n            if (beforeWord === afterWord){\n                currentLength++;\n                beforeIndex = beforeStart + currentLength;\n                afterIndex = afterStart + currentLength;\n            } else {\n                searching = false;\n            }\n        }\n\n        // If we've been asked to look behind, it's because both beforeTokens and afterTokens may\n        // have a whitespace token just behind the current match that was previously ignored. If so,\n        // we'll expand the current match to include it.\n        if (lookBehind && beforeStart > 0 && afterStart > 0){\n            var prevBeforeKey = beforeTokens[beforeStart - 1].key;\n            var prevAfterKey = afterTokens[afterStart - 1].key;\n            if (prevBeforeKey === ' ' && prevAfterKey === ' '){\n                beforeStart--;\n                afterStart--;\n                currentLength++;\n            }\n        }\n\n        return new Match(beforeStart, afterStart, currentLength, segment);\n    }\n\n    /**\n     * Creates segment objects from the original document that can be used to restrict the area that\n     * findBestMatch and it's helper functions search to increase performance.\n     *\n     * @param {Array.<Token>} beforeTokens Tokens from the before document.\n     * @param {Array.<Token>} afterTokens Tokens from the after document.\n     * @param {number} beforeIndex The index within the before document where this segment begins.\n     * @param {number} afterIndex The index within the after document where this segment behinds.\n     *\n     * @return {Segment} The segment object.\n     */\n    function createSegment(beforeTokens, afterTokens, beforeIndex, afterIndex){\n        return {\n            beforeTokens: beforeTokens,\n            afterTokens: afterTokens,\n            beforeMap: createMap(beforeTokens),\n            afterMap: createMap(afterTokens),\n            beforeIndex: beforeIndex,\n            afterIndex: afterIndex\n        };\n    }\n\n    /**\n     * Finds all the matching blocks within the given segment in the before and after lists of\n     * tokens.\n     *\n     * @param {Segment} The segment that should be searched for matching blocks.\n     *\n     * @return {Array.<Match>} The list of matching blocks in this range.\n     */\n    function findMatchingBlocks(segment){\n        // Create a binary search tree to hold the matches we find in order.\n        var matches = new MatchBinarySearchTree();\n        var match;\n        var segments = [segment];\n\n        // Each time the best match is found in a segment, zero, one or two new segments may be\n        // created from the parts of the original segment not included in the match. We will\n        // continue to iterate until all segments have been processed.\n        while(segments.length){\n            segment = segments.pop();\n            match = findBestMatch(segment);\n\n            if (match && match.length){\n                // If there's an unmatched area at the start of the segment, create a new segment\n                // from that area and throw it into the segments array to get processed.\n                if (match.segmentStartInBefore > 0 && match.segmentStartInAfter > 0){\n                    var leftBeforeTokens = segment.beforeTokens.slice(\n                            0, match.segmentStartInBefore);\n                    var leftAfterTokens = segment.afterTokens.slice(0, match.segmentStartInAfter);\n\n                    segments.push(createSegment(leftBeforeTokens, leftAfterTokens,\n                            segment.beforeIndex, segment.afterIndex));\n                }\n\n                // If there's an unmatched area at the end of the segment, create a new segment from that\n                // area and throw it into the segments array to get processed.\n                var rightBeforeTokens = segment.beforeTokens.slice(match.segmentEndInBefore + 1);\n                var rightAfterTokens = segment.afterTokens.slice(match.segmentEndInAfter + 1);\n                var rightBeforeIndex = segment.beforeIndex + match.segmentEndInBefore + 1;\n                var rightAfterIndex = segment.afterIndex + match.segmentEndInAfter + 1;\n\n                if (rightBeforeTokens.length && rightAfterTokens.length){\n                    segments.push(createSegment(rightBeforeTokens, rightAfterTokens,\n                            rightBeforeIndex, rightAfterIndex));\n                }\n\n                matches.add(match);\n            }\n        }\n\n        return matches.toArray();\n    }\n\n    /**\n     * Gets a list of operations required to transform the before list of tokens into the\n     * after list of tokens. An operation describes whether a particular list of consecutive\n     * tokens are equal, replaced, inserted, or deleted.\n     *\n     * @param {Array.<string>} beforeTokens The before list of tokens.\n     * @param {Array.<string>} afterTokens The after list of tokens.\n     *\n     * @return {Array.<Object>} The list of operations to transform the before list of\n     *      tokens into the after list of tokens, where each operation has the following\n     *      keys:\n     *      - {string} action One of {'replace', 'insert', 'delete', 'equal'}.\n     *      - {number} startInBefore The beginning of the range in the list of before tokens.\n     *      - {number} endInBefore The end of the range in the list of before tokens.\n     *      - {number} startInAfter The beginning of the range in the list of after tokens.\n     *      - {number} endInAfter The end of the range in the list of after tokens.\n     */\n    function calculateOperations(beforeTokens, afterTokens){\n        if (!beforeTokens) throw new Error('Missing beforeTokens');\n        if (!afterTokens) throw new Error('Missing afterTokens');\n\n        var positionInBefore = 0;\n        var positionInAfter = 0;\n        var operations = [];\n        var segment = createSegment(beforeTokens, afterTokens, 0, 0);\n        var matches = findMatchingBlocks(segment);\n        matches.push(new Match(beforeTokens.length, afterTokens.length, 0, segment));\n\n        for (var index = 0; index < matches.length; index++){\n            var match = matches[index];\n            var actionUpToMatchPositions = 'none';\n            if (positionInBefore === match.startInBefore){\n                if (positionInAfter !== match.startInAfter){\n                    actionUpToMatchPositions = 'insert';\n                }\n            } else {\n                actionUpToMatchPositions = 'delete';\n                if (positionInAfter !== match.startInAfter){\n                    actionUpToMatchPositions = 'replace';\n                }\n            }\n            if (actionUpToMatchPositions !== 'none'){\n                operations.push({\n                    action: actionUpToMatchPositions,\n                    startInBefore: positionInBefore,\n                    endInBefore: (actionUpToMatchPositions !== 'insert' ?\n                            match.startInBefore - 1 : null),\n                    startInAfter: positionInAfter,\n                    endInAfter: (actionUpToMatchPositions !== 'delete' ?\n                            match.startInAfter - 1 : null)\n                });\n            }\n            if (match.length !== 0){\n                operations.push({\n                    action: 'equal',\n                    startInBefore: match.startInBefore,\n                    endInBefore: match.endInBefore,\n                    startInAfter: match.startInAfter,\n                    endInAfter: match.endInAfter\n                });\n            }\n            positionInBefore = match.endInBefore + 1;\n            positionInAfter = match.endInAfter + 1;\n        }\n\n        var postProcessed = [];\n        var lastOp = {action: 'none'};\n\n        function isSingleWhitespace(op){\n            if (op.action !== 'equal'){\n                return false;\n            }\n            if (op.endInBefore - op.startInBefore !== 0){\n                return false;\n            }\n            return /^\\s$/.test(beforeTokens.slice(op.startInBefore, op.endInBefore + 1));\n        }\n\n        for (var i = 0; i < operations.length; i++){\n            var op = operations[i];\n\n            if ((isSingleWhitespace(op) && lastOp.action === 'replace') ||\n                    (op.action === 'replace' && lastOp.action === 'replace')){\n                lastOp.endInBefore = op.endInBefore;\n                lastOp.endInAfter = op.endInAfter;\n            } else {\n                postProcessed.push(op);\n                lastOp = op;\n            }\n        }\n        return postProcessed;\n    }\n\n    /**\n     * A TokenWrapper provides a utility for grouping segments of tokens based on whether they're\n     * wrappable or not. A tag is considered wrappable if it is closed within the given set of\n     * tokens. For example, given the following tokens:\n     *\n     *      ['</b>', 'this', ' ', 'is', ' ', 'a', ' ', '<b>', 'test', '</b>', '!']\n     *\n     * The first '</b>' is not considered wrappable since the tag is not fully contained within the\n     * array of tokens. The '<b>', 'test', and '</b>' would be a part of the same wrappable segment\n     * since the entire bold tag is within the set of tokens.\n     *\n     * TokenWrapper has a method 'combine' which allows walking over the segments to wrap them in\n     * tags.\n     */\n    function TokenWrapper(tokens){\n        this.tokens = tokens;\n        this.notes = tokens.reduce(function(data, token, index){\n            data.notes.push({\n                isWrappable: isWrappable(token),\n                insertedTag: false\n            });\n\n            var tag = !isVoidTag(token) && isTag(token);\n            var lastEntry = data.tagStack[data.tagStack.length - 1];\n            if (tag){\n                if (lastEntry && '/' + lastEntry.tag === tag){\n                    data.notes[lastEntry.position].insertedTag = true;\n                    data.tagStack.pop();\n                } else {\n                    data.tagStack.push({\n                        tag: tag,\n                        position: index\n                    });\n                }\n            }\n            return data;\n        }, {notes: [], tagStack: []}).notes;\n    }\n\n    /**\n     * Wraps the contained tokens in tags based on output given by a map function. Each segment of\n     * tokens will be visited. A segment is a continuous run of either all wrappable\n     * tokens or unwrappable tokens. The given map function will be called with each segment of\n     * tokens and the resulting strings will be combined to form the wrapped HTML.\n     *\n     * @param {function(boolean, Array.<string>)} mapFn A function called with an array of tokens\n     *      and whether those tokens are wrappable or not. The result should be a string.\n     */\n    TokenWrapper.prototype.combine = function(mapFn, tagFn){\n        var notes = this.notes;\n        var tokens = this.tokens.slice();\n        var segments = tokens.reduce(function(data, token, index){\n            if (notes[index].insertedTag){\n                tokens[index] = tagFn(tokens[index]);\n            }\n            if (data.status === null){\n                data.status = notes[index].isWrappable;\n            }\n            var status = notes[index].isWrappable;\n            if (status !== data.status){\n                data.list.push({\n                    isWrappable: data.status,\n                    tokens: tokens.slice(data.lastIndex, index)\n                });\n                data.lastIndex = index;\n                data.status = status;\n            }\n            if (index === tokens.length - 1){\n                data.list.push({\n                    isWrappable: data.status,\n                    tokens: tokens.slice(data.lastIndex, index + 1)\n                });\n            }\n            return data;\n        }, {list: [], status: null, lastIndex: 0}).list;\n\n        return segments.map(mapFn).join('');\n    };\n\n    /**\n     * Wraps and concatenates a list of tokens with a tag. Does not wrap tag tokens,\n     * unless they are wrappable (i.e. void and atomic tags).\n     *\n     * @param {sting} tag The tag name of the wrapper tags.\n     * @param {Array.<string>} content The list of tokens to wrap.\n     * @param {string} dataPrefix (Optional) The prefix to use in data attributes.\n     * @param {string} className (Optional) The class name to include in the wrapper tag.\n     */\n    function wrap(tag, content, opIndex, dataPrefix, className){\n        var wrapper = new TokenWrapper(content);\n        dataPrefix = dataPrefix ? dataPrefix + '-' : '';\n        var attrs = ' data-' + dataPrefix + 'operation-index=\"' + opIndex + '\"';\n        if (className){\n            attrs += ' class=\"' + className + '\"';\n        }\n\n        return wrapper.combine(function(segment){\n            if (segment.isWrappable){\n                var val = segment.tokens.join('');\n                if (val.trim()){\n                    return '<' + tag + attrs + '>' + val + '</' + tag + '>';\n                }\n            } else {\n                return segment.tokens.join('');\n            }\n            return '';\n        }, function(openingTag){\n            var dataAttrs = ' data-diff-node=\"' + tag + '\"';\n            dataAttrs += ' data-' + dataPrefix + 'operation-index=\"' + opIndex + '\"';\n\n            return openingTag.replace(/>\\s*$/, dataAttrs + '$&');\n        });\n    }\n\n    /**\n     * OPS.equal/insert/delete/replace are functions that render an operation into\n     * HTML content.\n     *\n     * @param {Object} op The operation that applies to a prticular list of tokens. Has the\n     *      following keys:\n     *      - {string} action One of ['replace', 'insert', 'delete', 'equal'].\n     *      - {number} startInBefore The beginning of the range in the list of before tokens.\n     *      - {number} endInBefore The end of the range in the list of before tokens.\n     *      - {number} startInAfter The beginning of the range in the list of after tokens.\n     *      - {number} endInAfter The end of the range in the list of after tokens.\n     * @param {Array.<string>} beforeTokens The before list of tokens.\n     * @param {Array.<string>} afterTokens The after list of tokens.\n     * @param {number} opIndex The index into the list of operations that identifies the change to\n     *      be rendered. This is used to mark wrapped HTML as part of the same operation.\n     * @param {string} dataPrefix (Optional) The prefix to use in data attributes.\n     * @param {string} className (Optional) The class name to include in the wrapper tag.\n     *\n     * @return {string} The rendering of that operation.\n     */\n    var OPS = {\n        'equal': function(op, beforeTokens, afterTokens, opIndex, dataPrefix, className){\n            var tokens = afterTokens.slice(op.startInAfter, op.endInAfter + 1);\n            return tokens.reduce(function(prev, curr){\n                return prev + curr.string;\n            }, '');\n        },\n        'insert': function(op, beforeTokens, afterTokens, opIndex, dataPrefix, className){\n            var tokens = afterTokens.slice(op.startInAfter, op.endInAfter + 1);\n            var val = tokens.map(function(token){\n                return token.string;\n            });\n            return wrap('ins', val, opIndex, dataPrefix, className);\n        },\n        'delete': function(op, beforeTokens, afterTokens, opIndex, dataPrefix, className){\n            var tokens = beforeTokens.slice(op.startInBefore, op.endInBefore + 1);\n            var val = tokens.map(function(token){\n                return token.string;\n            });\n            return wrap('del', val, opIndex, dataPrefix, className);\n        },\n        'replace': function(){\n            return OPS['delete'].apply(null, arguments) + OPS['insert'].apply(null, arguments);\n        }\n    };\n\n    /**\n     * Renders a list of operations into HTML content. The result is the combined version\n     * of the before and after tokens with the differences wrapped in tags.\n     *\n     * @param {Array.<string>} beforeTokens The before list of tokens.\n     * @param {Array.<string>} afterTokens The after list of tokens.\n     * @param {Array.<Object>} operations The list of operations to transform the before\n     *      list of tokens into the after list of tokens, where each operation has the\n     *      following keys:\n     *      - {string} action One of {'replace', 'insert', 'delete', 'equal'}.\n     *      - {number} startInBefore The beginning of the range in the list of before tokens.\n     *      - {number} endInBefore The end of the range in the list of before tokens.\n     *      - {number} startInAfter The beginning of the range in the list of after tokens.\n     *      - {number} endInAfter The end of the range in the list of after tokens.\n     * @param {string} dataPrefix (Optional) The prefix to use in data attributes.\n     * @param {string} className (Optional) The class name to include in the wrapper tag.\n     *\n     * @return {string} The rendering of the list of operations.\n     */\n    function renderOperations(beforeTokens, afterTokens, operations, dataPrefix, className){\n        return operations.reduce(function(rendering, op, index){\n            return rendering + OPS[op.action](\n                    op, beforeTokens, afterTokens, index, dataPrefix, className);\n        }, '');\n    }\n\n    /**\n     * Compares two pieces of HTML content and returns the combined content with differences\n     * wrapped in <ins> and <del> tags.\n     *\n     * @param {string} before The HTML content before the changes.\n     * @param {string} after The HTML content after the changes.\n     * @param {string} className (Optional) The class attribute to include in <ins> and <del> tags.\n     * @param {string} dataPrefix (Optional) The data prefix to use for data attributes. The\n     *      operation index data attribute will be named `data-${dataPrefix-}operation-index`.\n     * @param {string} atomicTags (Optional) Comma separated list of atomic tag names. The \n     *     list has to be in the form `tag1,tag2,...` e. g. `head,script,style`. If not used, \n     *     the default list `iframe,object,math,svg,script,video,head,style` will be used.\n     *\n     * @return {string} The combined HTML content with differences wrapped in <ins> and <del> tags.\n     */\n    function diff(before, after, className, dataPrefix, atomicTags){\n        if (before === after) return before;\n\n        // Enable user provided atomic tag list.\n        atomicTags ? \n            (atomicTagsRegExp = new RegExp('^<(' + atomicTags.replace(/\\s*/g, '').replace(/,/g, '|') + ')'))\n            : (atomicTagsRegExp = defaultAtomicTagsRegExp);\n\n        before = htmlToTokens(before);\n        after = htmlToTokens(after);\n        var ops = calculateOperations(before, after);\n        return renderOperations(before, after, ops, dataPrefix, className);\n    }\n\n    diff.htmlToTokens = htmlToTokens;\n    diff.findMatchingBlocks = findMatchingBlocks;\n    findMatchingBlocks.findBestMatch = findBestMatch;\n    findMatchingBlocks.createMap = createMap;\n    findMatchingBlocks.createToken = createToken;\n    findMatchingBlocks.createSegment = createSegment;\n    findMatchingBlocks.getKeyForToken = getKeyForToken;\n    diff.calculateOperations = calculateOperations;\n    diff.renderOperations = renderOperations;\n\n    if (typeof define === 'function'){\n        define([], function(){\n          return diff;\n        });\n    } else if (typeof module !== 'undefined' && module !== null){\n        module.exports = diff;\n    } else {\n        this.htmldiff = diff;\n    }\n}).call(this);\n"]}