var Match, calculate_operations, consecutive_where, create_index, diff, find_match, find_matching_blocks, html_to_tokens, is_end_of_tag, is_start_of_tag, is_tag, is_whitespace, isnt_tag, op_map, recursively_find_matching_blocks, render_operations, wrap; is_end_of_tag = function (char) { return char === '>'; }; is_start_of_tag = function (char) { return char === '<'; }; is_whitespace = function (char) { return /^\s+$/.test(char); }; is_tag = function (token) { return /^\s*<[^>]+>\s*$/.test(token); }; is_closed_tag = function (token) { return /^\s*<\/[^>]+>\s*$/.test(token); }; isnt_tag = function (token) { return !is_tag(token); }; Match = (function () { function Match(start_in_before1, start_in_after1, length1) { this.start_in_before = start_in_before1; this.start_in_after = start_in_after1; this.length = length1; this.end_in_before = (this.start_in_before + this.length) - 1; this.end_in_after = (this.start_in_after + this.length) - 1; } return Match; })(); html_to_tokens = function (html) { var char, current_word, i, len, mode, words; mode = 'char'; current_word = ''; words = []; for (i = 0, len = html.length; i < len; i++) { char = html[i]; switch (mode) { case 'tag': if (is_end_of_tag(char)) { current_word += '>'; words.push(current_word); current_word = ''; if (is_whitespace(char)) { mode = 'whitespace'; } else { mode = 'char'; } } else { current_word += char; } break; case 'char': if (is_start_of_tag(char)) { if (current_word) { words.push(current_word); } current_word = '<'; mode = 'tag'; } else if (/\s/.test(char)) { if (current_word) { words.push(current_word); } current_word = char; mode = 'whitespace'; } else if (/[\w\#@]+/i.test(char)) { current_word += char; } else { if (current_word) { words.push(current_word); } current_word = char; } break; case 'whitespace': if (is_start_of_tag(char)) { if (current_word) { words.push(current_word); } current_word = '<'; mode = 'tag'; } else if (is_whitespace(char)) { current_word += char; } else { if (current_word) { words.push(current_word); } current_word = char; mode = 'char'; } break; default: throw new Error("Unknown mode " + mode); } } if (current_word) { words.push(current_word); } return words; }; find_match = function (before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, end_in_before, start_in_after, end_in_after) { var best_match_in_after, best_match_in_before, best_match_length, i, index_in_after, index_in_before, j, len, locations_in_after, looking_for, match, match_length_at, new_match_length, new_match_length_at, ref, ref1; best_match_in_before = start_in_before; best_match_in_after = start_in_after; best_match_length = 0; match_length_at = {}; for (index_in_before = i = ref = start_in_before, ref1 = end_in_before; ref <= ref1 ? i < ref1 : i > ref1; index_in_before = ref <= ref1 ? ++i : --i) { new_match_length_at = {}; looking_for = before_tokens[index_in_before]; locations_in_after = index_of_before_locations_in_after_tokens[looking_for]; for (j = 0, len = locations_in_after.length; j < len; j++) { index_in_after = locations_in_after[j]; if (index_in_after < start_in_after) { continue; } if (index_in_after >= end_in_after) { break; } if (match_length_at[index_in_after - 1] == null) { match_length_at[index_in_after - 1] = 0; } new_match_length = match_length_at[index_in_after - 1] + 1; new_match_length_at[index_in_after] = new_match_length; if (new_match_length > best_match_length) { best_match_in_before = index_in_before - new_match_length + 1; best_match_in_after = index_in_after - new_match_length + 1; best_match_length = new_match_length; } } match_length_at = new_match_length_at; } if (best_match_length !== 0) { match = new Match(best_match_in_before, best_match_in_after, best_match_length); } return match; }; recursively_find_matching_blocks = function (before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, end_in_before, start_in_after, end_in_after, matching_blocks) { var match; match = find_match(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, end_in_before, start_in_after, end_in_after); if (match != null) { if (start_in_before < match.start_in_before && start_in_after < match.start_in_after) { recursively_find_matching_blocks(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, match.start_in_before, start_in_after, match.start_in_after, matching_blocks); } matching_blocks.push(match); if (match.end_in_before <= end_in_before && match.end_in_after <= end_in_after) { recursively_find_matching_blocks(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, match.end_in_before + 1, end_in_before, match.end_in_after + 1, end_in_after, matching_blocks); } } return matching_blocks; }; create_index = function (p) { var i, idx, index, len, ref, token; if (p.find_these == null) { throw new Error('params must have find_these key'); } if (p.in_these == null) { throw new Error('params must have in_these key'); } index = {}; ref = p.find_these; for (i = 0, len = ref.length; i < len; i++) { token = ref[i]; index[token] = []; idx = p.in_these.indexOf(token); while (idx !== -1) { index[token].push(idx); idx = p.in_these.indexOf(token, idx + 1); } } return index; }; find_matching_blocks = function (before_tokens, after_tokens) { var index_of_before_locations_in_after_tokens, matching_blocks; matching_blocks = []; index_of_before_locations_in_after_tokens = create_index({ find_these: before_tokens, in_these: after_tokens }); return recursively_find_matching_blocks(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, 0, before_tokens.length, 0, after_tokens.length, matching_blocks); }; calculate_operations = function (before_tokens, after_tokens) { var action_map, action_up_to_match_positions, i, index, is_single_whitespace, j, last_op, len, len1, match, match_starts_at_current_position_in_after, match_starts_at_current_position_in_before, matches, op, operations, position_in_after, position_in_before, post_processed; if (before_tokens == null) { throw new Error('before_tokens?'); } if (after_tokens == null) { throw new Error('after_tokens?'); } position_in_before = position_in_after = 0; operations = []; action_map = { 'false,false': 'replace', 'true,false': 'insert', 'false,true': 'delete', 'true,true': 'none' }; matches = find_matching_blocks(before_tokens, after_tokens); matches.push(new Match(before_tokens.length, after_tokens.length, 0)); for (index = i = 0, len = matches.length; i < len; index = ++i) { match = matches[index]; match_starts_at_current_position_in_before = position_in_before === match.start_in_before; match_starts_at_current_position_in_after = position_in_after === match.start_in_after; action_up_to_match_positions = action_map[[match_starts_at_current_position_in_before, match_starts_at_current_position_in_after].toString()]; if (action_up_to_match_positions !== 'none') { operations.push({ action: action_up_to_match_positions, start_in_before: position_in_before, end_in_before: (action_up_to_match_positions !== 'insert' ? match.start_in_before - 1 : void 0), start_in_after: position_in_after, end_in_after: (action_up_to_match_positions !== 'delete' ? match.start_in_after - 1 : void 0) }); } if (match.length !== 0) { operations.push({ action: 'equal', start_in_before: match.start_in_before, end_in_before: match.end_in_before, start_in_after: match.start_in_after, end_in_after: match.end_in_after }); } position_in_before = match.end_in_before + 1; position_in_after = match.end_in_after + 1; } post_processed = []; last_op = { action: 'none' }; is_single_whitespace = function (op) { if (op.action !== 'equal') { return false; } if (op.end_in_before - op.start_in_before !== 0) { return false; } return /^\s$/.test(before_tokens.slice(op.start_in_before, +op.end_in_before + 1 || 9e9)); }; for (j = 0, len1 = operations.length; j < len1; j++) { op = operations[j]; if (((is_single_whitespace(op)) && last_op.action === 'replace') || (op.action === 'replace' && last_op.action === 'replace')) { last_op.end_in_before = op.end_in_before; last_op.end_in_after = op.end_in_after; } else { post_processed.push(op); last_op = op; } } return post_processed; }; consecutive_where = function (start, content, predicate) { var answer, i, index, last_matching_index, len, token; content = content.slice(start, +content.length + 1 || 9e9); last_matching_index = void 0; for (index = i = 0, len = content.length; i < len; index = ++i) { token = content[index]; answer = predicate(token); if (answer === true) { last_matching_index = index; } if (answer === false) { break; } } if (last_matching_index != null) { return content.slice(0, +last_matching_index + 1 || 9e9); } return []; }; wrap = function (tag, content) { var length, non_tags, position, rendering, tags; rendering = ''; position = 0; length = content.length; while (true) { if (position >= length) { break; } non_tags = consecutive_where(position, content, isnt_tag); position += non_tags.length; if (non_tags.length !== 0) { rendering += "<" + tag + ">" + (non_tags.join('')) + ""; } if (position >= length) { break; } tags = consecutive_where(position, content, is_tag); position += tags.length; rendering += tags.join(''); } return rendering; }; op_map = { equal: function (op, before_tokens, after_tokens) { return before_tokens.slice(op.start_in_before, +op.end_in_before + 1 || 9e9).join(''); }, insert: function (op, before_tokens, after_tokens) { var val; val = after_tokens.slice(op.start_in_after, +op.end_in_after + 1 || 9e9); return wrap('ins', val); }, "delete": function (op, before_tokens, after_tokens) { var val; val = before_tokens.slice(op.start_in_before, +op.end_in_before + 1 || 9e9); return wrap('del', val); } }; op_map.replace = function (op, before_tokens, after_tokens, only) { if (only === 'after') { return (op_map.insert(op, before_tokens, after_tokens)); } else if (only === 'before') { return (op_map["delete"](op, before_tokens, after_tokens)); } return (op_map["delete"](op, before_tokens, after_tokens)) + (op_map.insert(op, before_tokens, after_tokens)); }; render_operations = function (before_tokens, after_tokens, operations, only) { var i, len, op, rendering, bt, modify = false; rendering = ''; for (i = 0, len = operations.length; i < len; i++) { op = operations[i]; rendering += op_map[op.action](op, before_tokens, after_tokens, only); } return rendering; }; diff = function (before, after, only, prepareOutput) { var ops; if (before === after) { return before; } before = html_to_tokens(before); after = html_to_tokens(after); ops = calculate_operations(before, after); output = render_operations(before, after, ops, only); if (prepareOutput) { var prepare = html_to_tokens(output), tags = [], match = false; // checking tag has closed-tag prepare.forEach(function (item, index) { if (match = item.match(/<([^\/>\s]+)/i)) { tags.push([match[1], index]); } //console.log(index, tags[tags.length - 1][0], item, []+tags); if ((new RegExp('<\/' + tags[tags.length - 1][0], 'i')).test(item)) { tags.pop(); } }); tags.forEach(function (item) { prepare.splice(item[1] + 1, 0, ''); }); if (tags.length > 0) { output = prepare.join(''); } } /* else { var prepare = html_to_tokens(output), i, len, bt, modify = false; for (bt = 0, i = 0, len = prepare.length; i < len; i++) { if (/
'); } else { bt = 1; } } if (/<\/div/i.test(prepare[i])) { bt = 0; } } if (modify) { output = prepare.join(''); } }*/ return output; }; diff.html_to_tokens = html_to_tokens; diff.find_matching_blocks = find_matching_blocks; find_matching_blocks.find_match = find_match; find_matching_blocks.create_index = create_index; diff.calculate_operations = calculate_operations; diff.render_operations = render_operations; if (typeof define === 'function') { define([], function () { return diff; }); } else if (typeof module !== "undefined" && module !== null) { module.exports = diff; } else { this.htmldiff = diff; }